Привет, Хабр! Это Станислав Егоркин, инженер юнита k8s департамента разработки Infrastructure в Авито. В одной из своих прошлых статей я описал, как мы детектируем проблемы на нодах Kubernetes-кластеров. Логичным продолжением этой работы стала автоматизация их лечения. В этой статье я расскажу, как у нас устроены механики Auto Healing.
Все мы сталкивались с проблемами, которые регулярно возникают, но их легко детектировать и несложно решить. Когда серверов мало, можно сделать алерт, который побудит инженеров, например, зайти на ноду по ssh и выполнить нужную команду. Но в Авито сейчас более 70-ти кластеров из более чем 4-х тысяч нод. Для нас Auto Healing – это механики, которые в идеале должны предшествовать алерту. Только если автоматика не смогла сама вылечить ноду, тогда уже приходит инженер.
Простейшие варианты Auto Healing можно внедрить без особых усилий – они могут представлять собой, например, крон, который запускает скрипт, проверяющий что-то и выполняющий лечение. Но мы поговорим о более сложных инструментах, которые живут в Kubernetes-кластерах. Их ключевое преимущество – понимание контекста и состояния всего кластера, а не отдельной ноды.

Содержание:
Шаг первый — детекция
Если вы не читали статью о детекции деградаций, лучше начать с нее, а после этого вернуться. Здесь мы поговорим не о самой детекции, а про интеграцию Node Problem Detector (далее — NPD) с системой Auto Healing.
NPD работает с node conditions. Вам наверняка знакомы conditions Ready, DiskPressure, MemoryPressure – их навешивает на ноду kubelet. NPD по существу просто расширяет эту функциональность, выставляя conditions на любые проверки, которые вы опишите в его манифестах.
Обнаруженные состояния затем можно использовать четырьмя способами:
Выполнить kubectl node describe, чтобы составить представление о том, имеется ли на ноде сейчас та или иная проблема.
Собрать метрики с node conditions – это можно делать с помощью kube-state-metrics (kube_node_status_condition), но лучше забирать прямо с NPD ( problem_gauge).
Сделать на метрики алерты.
Настроить срабатывание механик Auto Healing.
Работа NPD в любом случае заканчивается на выставлении node conditions. Поэтому, если мы хотим автолечения нод, нам понадобятся другие инструменты. После обзора альтернатив я остановился на группе проектов medik8s. Речь идет о Kubernetes-операторах, призванных автоматизировать некоторые действия с нодами. В Авито мы используем три из них:
Node Healthcheck Operator (NHC) – принимает решение о применении механик Auto Healing;
Self Node Remediation (SNR) – умеет безопасно ребутать ноды и запускать на них команды или скрипты;
Node Maintenance Operator (NMO) – умеет кордонить и дрейнить ноды.
От обнаружения проблемы до ее устранения несколько шагов. Рассмотрим каждый из них в отдельности.
Шаг второй — принятие решения
На первый взгляд, это довольно простой шаг: увидел искомый node condition — применяй выбранное лечение. В реальности же последнее, что мы хотим видеть в production-окружении, это глупую автоматику с большими полномочиями.
Мы хотим, чтобы автоматика была безопасной, в том числе в критических ситуациях, например, когда весь кластер идет в разнос. Чтобы добиться этого, нам нужно, во-первых, аккуратно спроектировать пары «проблема-лечение», а во-вторых, сделать автоматику умной.
Первое подразумевает, что мы готовы применять автоматическое лечение только там, где проверка сама по себе надежна. Мы уверены, что она не флапает (не переходит из проваленной в нормальный статус и обратно) и обнаруживает именно ту деградацию, для которой у нас есть автопилюля.
Но даже если все спроектировано правильно, это не уберегло бы нас от случаев, когда деградации массовые. Скажем, в качестве лечения мы выбрали ребут. Естественно, мы не хотим, чтобы все ноды кластеры одновременно в него отправились.
Одной из главных задач Node Healthcheck Operator является как раз проведение проверок безопасности. Его конфигурирование производится через кастомные ресурсы NodeHealtchecks и позволяют установить порог по числу здоровых нод, ниже которого вся автоматика отключается.
Понадобилось немного доработать Node Healthcheck Operator с учетом специфики Авито, поэтому у нас он также:
может быть настроен по-разному для разных групп нод;
позволяет указать максимальное количество нод, в отношении которых одновременно применяются механики Auto Healing.
Логика работы оператора довольно проста. Допустим, мы хотим ребутать ноду, если condition PLEGisUnhealthy=True держится на ней более 5 минут. Для этого нужно создать NodeHealtchecks с соответствующими параметрами. Node Healthcheck Operator подхватит его и начнет следить за появлением этого node condition. Если на какой-либо ноде возникнет PLEGisUnhealthy=True, оператор подождет 5 минут, проведет проверки безопасности и создаст по шаблону кастомный ресурс.
Кастомный ресурс, о котором идет речь, — это по существу запрос на лечение, адресованный remediation-оператору. Поскольку в нашем примере мы хотим ребута, запрос нужно адресовать Self Node Remediation.
apiVersion: remediation.medik8s.io/v1alpha1
kind: NodeHealthCheck
metadata:
name: nodehealthcheck-reboot
spec:
concurrency: 2 # avito-specific
minHealthy: 90%
minHealthyInCustomPool: 66% # avito-specific
remediationTemplate:
apiVersion: self-node-remediation.medik8s.io/v1alpha1
kind: SelfNodeRemediationTemplate
name: self-node-remediation-reboot-template
namespace: self-node-remediation
selector:
matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
- key: node.kubernetes.io/role
operator: NotIn
values:
- master
unhealthyConditions:
- duration: 300s
status: "True"
type: PLEGisUnhealthy
apiVersion: self-node-remediation.medik8s.io/v1alpha1
kind: SelfNodeRemediationTemplate
metadata:
name: self-node-remediation-reboot-template
namespace: self-node-remediation
spec:
template:
spec:
remediationStrategy: ResourceDeletion
Работа Node Healthcheck Operator на этом этапе завершена. Он продолжит следить за лечением, ожидая, пока другие операторы выполнят свою часть работы.
Шаг третий — лечение
Мы используем для этого два оператора — Self Node Remediation и Node Maintenance Operator. Давайте рассмотрим каждый из них.
Self Node Remediation
Следит за ресурсами SelfNodeRemediation. Состоит из двух частей — контроллера и DaemonSet с агентами. «Из коробки» этот оператор умеет только безопасно ребутать ноды. Контроллер кордонит и дрейнит их, после чего агент, запущенный на нужной ноде, выполняет ребут.
Интересный нюанс – Self Node Remediation умеет перезагружать ноды несколькими разными способами: с помощью hardware watchdog, software watchdog и через /proc/sysrq-trigger. По умолчанию используется первый способ. Однако целый ряд наших серверов не возвращался самостоятельно после ребута. На эту тему я нашел старый issue на сайте вендора. По какой-то причине их сервера не считают ребут с помощью hardware watchdog штатным и после него при загрузке требуют от пользователя нажать F1. Мы решили эту проблему ребутом через sysrq-trigger, но сама ситуация, а также то, что вендор не спешил ничего менять, показались мне удивительными. Может кто-то в комментариях подскажет, чем это можно объяснить.
Вскоре после внедрения в Авито Node Problem Detector я понял, что есть по крайне мере одна часто возникающая проблема, которую можно решить, если зайти на ноду по ssh и выполнить определенную команду. Ребут тоже помогает, однако он явно чрезмерен. Кроме того, в нашем случае оператор производит сам ребут простым запуском команды echo b > /proc/sysrq-trigger. Почему бы не научить его выполнять и другие команды?
Так наш Self Node Remediation освоил новый способ remediation. Теперь агент на ноде может получать запрос на выполнение определенной команды. После этого остается только создать новый шаблон для Node Healthcheck Operator и привязать его к провалу конкретной проверки.
Чуть ранее я писал, что мы не хотим, чтобы все ноды ребутнулись в нашем кластере одновременно. Интересно, что Self Node Remediation содержит довольно любопытную логику оценки здоровья ноды для разных пограничных случаев (например, когда kube-apiserver недоступен с этой ноды, но доступен с соседних, либо недоступен ни с одной из них) — соответствующие схемы есть в официальной документации. Почитайте, если вас интересуют проблемы безопасности Auto Healing.
Node Maintenance Operator
Оригинальное назначение этого оператора в том, чтобы выводить ноды для обслуживания (cordon + drain). Авторы предполагали, что ресурсы NodeMaintenance, на которые подписан оператор, будут создаваться руками. Допустим, инженеру датацентра нужно поменять плашку памяти. Он создает ресурс с именем ноды через kubectl, нода дрейнится и инженер производит необходимые манипуляции с сервером. Затем он удаляет этот ресурс, в результате чего нода возвращается под нагрузку.
Но в Авито очень много серверов. Это значит, что аппаратный отказ нод является для нас рутиной. При этом в некоторых случаях отказ является частичным и вызывает деградации продуктовой нагрузки. Что если бы мы могли автоматически обнаруживать аппаратные проблемы, автоматически выводить больные ноды, а после устранения неполадок также автоматически возвращать их под нагрузку? Именно это и удалось достигнуть с помощью Node Maintenance Operator. Пришлось довольно сильно его поправить, но в конце концов он начал делать именно то, что от него ожидается.
Вот конкретный use case, который может вас заинтересовать:
Node Problem Detector раз в пять минут с помощью ipmitool выполняет проверку на аппаратные отказы. В случае провала он выставляет node condition HWFailure=True.
В конфиге Node Healthcheck Operator мы указываем, что в случае появления HWFailure=True нужно немедленно создать по шаблону ресурс NodeMaintenance.
Node Maintenance Operator подхватывает его, дрейнит ноду и держит ее закордоненной, пока ресурс NodeMaintenance не исчезнет. После этого нода спокойно ждет, когда ее починит инженер датацентра. Обычно в ДЦ выключают сервер, меняют память, вентилятор и т.п., после чего включают обратно.
Поскольку после включения Node Problem Detector больше не видит на ноде проблемы, он меняет статус HWFailure на False.
Node Healthcheck Operator видит это и удаляет ресурс NodeMaintenance. В результате Node Maintenance Operator раскардонивает ноду.
Возможно выглядит сложно, но в действительности взаимодействие операторов вполне прозрачно — у каждого есть своя понятная зона ответственности.

Пара сложностей на пути
Я рассказал о системе в том виде, в котором она сейчас работает во всех prod и staging кластерах Авито. Однако на пути к этому встретилось немало сложностей. Некоторые из них стоят того, чтобы о них упомянуть.
Баг в Node Problem Detector
Когда я впервые взялся тестировать операторы Auto Healing, то получил интересный эффект — в некоторых случаях ноды ребутались по кругу. Я связался с авторами операторов (их поддерживают доброжелательные ребята из RedHat), и в результате совместного дебага выяснилось, что повинен NPD. Дело в том, что при перезапуске он по умолчанию скидывает все выставляемые им node conditions в дефолтное значение (False). Таким образом, нода, которая ребутнулась, но вернулась нездоровой, какое-то время будет считаться здоровой. Если бы node condition не сбрасывался, Node Healthcheck Operator обрабатывал это нормально (то есть нода просто оставалась закордоненной). Иначе он считает, что она выздоровела, снова заболела и нужно повторить лечение.
Это должно решаться прописыванием в манифестах проверок параметра skip_initial_status: true. Проблема состояла в том, что этот параметр не работает так, как ожидается. Пришлось сделать коммит в NPD. Мой фикс был релизнут в версии v0.8.18. Поэтому если вы хотите использовать NPD для Auto Healing, вам нужна эта версия или старше.

Деплой операторов (отказ от OLM)
Все операторы medik8s заточены под автоматическое развертывание в кластере с помощью Operator Lifecycle Manager. OLM является частью OpenShift и в ванильном Kubernetes выглядит не слишком уместно. Он тяжелый, сложный и его непросто настроить для работы с приватным репозиторием. Тем не менее на первом этапе я не нашел варианта, как обойтись без него, поскольку OLM в числе прочего отвечает за выпуск сертификатов для webhooks.
Впоследствии оказалось, что это не так уж сложно. Все операторы написаны на базе Operator SDK и содержат заготовки для использования cert-manager вместо OLM. Поэтому если вы захотите использовать операторы medik8s без OLM в своих кластерах, вы вполне можете последовать нашему примеру.
Что в итоге?
Вот краткая статистика по нодам, на которых применялись механики Auto Healing, за последние 90 дней:
наблюдались — 2722 нод;
выводились для обслуживания — 9 нод;
отправлялись в ребут — 4 ноды;
излечивались с помощью запуска команд — 47 нод.
Автоматическое выведение нод для обслуживания сняло с нас часть рутинных задач. Вывести ноду вручную было несложно, но при автоматическом выведении мы избавились от необходимости возвращать ее обратно — а это очень здорово.
Автоматическое лечение нод уменьшило число деградаций. Реакция на известные деградации теперь может быть практически мгновенной.
Полтора года назад проблема с container runtime, возникавшая то на одной, то на другой ноде, доставляла нам много боли. Как правило, мы узнавали о ней от разработчиков, чей сервис деградировал. От момента возникновения до реакции иногда проходило несколько часов. Примерно год назад мы настроили авторебут для таких нод. Проблема ушла, но ноды дрейнились и на ребут уходило по 5-10 минут. В последние же месяцы оператор при ее возникновении просто выполняет нужную команду на ноде, так что все проходит без downtime, совершенно незаметно для рабочей нагрузки. Operators are software SREs.
Отдельную радость вызывает расширяемость построенной системы. Сейчас, если мы неоднократно сталкиваемся с какой-либо проблемой, она становится кандидатом на добавление в Auto Healing. Иными словами, мы приобрели не только решение для существующих проблем, но и инструмент для решения многих будущих.
Этой статье я постарался сформировать общее представление о том, как работают механики Auto Healing в наших Kubernetes-кластерах. Разумеется, есть много тонкостей, которые остались за ее рамками. Если у вас возникли какие-то вопросы, пожалуйста, задавайте их в комментариях.
А если хотите вместе с нами помогать людям и бизнесу через технологии — присоединяйтесь к командам. Свежие вакансии есть на нашем карьерном сайте.