В одном из рабочих Kubernetes-кластеров мне потребовалось привести конфигурацию kubelet к единому состоянию. Для каждого класса нод в Git хранился свой эталон с настройками резервирования ресурсов, порогами реакции kubelet на нехватку ресурсов и параметрами очистки образов.
С точки зрения Kubernetes ноды выглядели исправными: kubectl get nodes показывал Ready. Однако отдельная проверка /var/lib/kubelet/config.yaml обнаружила расхождения между фактической конфигурацией и эталоном. Ноды работали, но при нехватке памяти или места на диске могли повести себя не так, как остальные ноды того же класса.
Ready и соответствие эталону — разные проверки
Условие Ready=True означает, что Kubernetes считает ноду здоровой и готовой принимать поды.1 Оно описывает текущее рабочее состояние ноды, но ничего не знает о правилах, принятых внутри конкретной платформы.
Kubernetes не знает, какой файл должен соответствовать классу worker-large, какой коммит Git считается эталонным и какая контрольная сумма ожидается на хосте. Поэтому два результата не противоречат друг другу:
Ready=True
desired_config != actual_config
Kubelet мог успешно запуститься с существующим файлом, публиковать статус ноды и обслуживать поды. Проблема заключалась не в том, что конфигурация была синтаксически недопустимой, а в том, что она отличалась от принятого нами состояния.
Почему дрейф долго не проявляется
Многие параметры kubelet определяют поведение ноды на границе доступных ресурсов. Пока запас памяти и диска достаточен, две ноды с разными настройками могут выглядеть одинаково.
Различия в kubeReserved и systemReserved меняют расчёт Node Allocatable, а при соответствующей настройке enforceNodeAllocatable позволяют kubelet применять эти резервы к cgroup системных процессов.2 Значения evictionHard и evictionSoft определяют пороги сигналов нехватки ресурсов. После их достижения kubelet пытается освободить ресурсы ноды, а если этого недостаточно, может начать завершать поды.3 Параметры очистки образов также могут изменить момент, когда нода начнёт возвращать дисковое пространство.
Поэтому дрейф конфигурации чаще выглядит не как немедленная поломка, а как отложенное расхождение в поведении. Оно становится заметным только тогда, когда нагрузка достигает порога, заданного в отличающейся конфигурации.
Как я превратил эталон в проверку
Для проверки я развернул внутренний агент как DaemonSet. DaemonSet обеспечивал наличие Pod на каждой выбранной ноде, но сам агент не был контроллером: в нём не было постоянного цикла сверки, polling и автоматического исправления. Режим работы задавался в конфигурации DaemonSet, а повторная проверка запускалась управляемым обновлением его Pod. Агент определял класс ноды по метке, выбирал соответствующий файл из набора конфигураций, доставленного через GitOps, читал фактическую конфигурацию с хоста и сравнивал контрольные суммы. При расхождении он показывал различия в формате unified diff, чтобы было видно не только наличие дрейфа, но и конкретные изменённые поля.
Сокращённый пример результата выглядел так:
node: <node-name>
class: worker-large
desired_sha256: <desired-checksum>
actual_sha256: <actual-checksum>
status: drift_detected
Проверка ничего не исправляла автоматически. Обнаружение и применение были разделены: сначала check на одной ноде, затем контролируемый apply с проверкой восстановления kubelet и только после этого постепенное расширение применения. Для возврата к предыдущему файлу был предусмотрен отдельный rollback.
Такое разделение было важнее постоянного приведения к эталону. Изменение конфигурации kubelet требует работы с файлом на хосте и перезапуска системного сервиса, поэтому для этой задачи явная управляемая операция была предпочтительнее фоновой коррекции.
Совпадение файла тоже нужно правильно понимать
Сравнивать следует тот файл, который действительно использует kubelet. Путь задаётся параметром --config, а параметры командной строки могут переопределять совпадающие значения из конфигурационного файла.4
В моём окружении источники конфигурации были известны и ограничены, поэтому сравнение /var/lib/kubelet/config.yaml имело однозначный смысл. В другом кластере перед такой проверкой нужно сначала изучить параметры запуска kubelet. Иначе совпадение удобного для сравнения файла создаст такое же ложное чувство уверенности, как и один зелёный статус Ready.
Три вопроса о состоянии ноды
После этой задачи я разделяю проверку ноды на три независимых вопроса:
| Вопрос | Источник ответа |
|---|---|
| Доступна ли нода Kubernetes прямо сейчас? | Условие Ready и другие сигналы состояния |
| Соответствует ли её конфигурация принятому эталону? | Отдельная проверка дрейфа |
| Поведёт ли она себя ожидаемо под нагрузкой? | Наблюдаемость и проверка выбранных порогов |
Ready отвечает на первый вопрос. Проверка контрольных сумм и различий отвечает на второй, но не доказывает правильность самого эталона. Третий требует наблюдать ноду в тех режимах, для которых заданы резервирование ресурсов, пороги нехватки ресурсов и очистка образов.
Вывод
Статус Ready остаётся полезным, если не приписывать ему лишний смысл. Он показывает, что Kubernetes сейчас считает ноду достаточно здоровой для работы, но не подтверждает соответствие локальной конфигурации внутреннему стандарту платформы.
Когда одинаковое поведение нод является частью требований, соответствие эталону должно иметь собственную проверку. Нода может быть Ready и одновременно отличаться от соседних нод именно в тех настройках, которые определят её поведение при следующем дефиците ресурсов.
