← Все статьи

«Дрейф ядер» оказался дрейфом аптайма: патчи ставились, ребутов не было

Три разных ядра на семи нодах выглядели как незавершённые волны патчей и увели расследование по ложному следу. Патчи на всех нодах были одинаковые, различался только аптайм.

В Optimacros семинодовый self-managed Kubernetes работал на трёх разных ядрах. Прежнее прочтение, записанное в наших заметках о среде, звучало как «незавершённые волны патчей». Ноды на самом старом ядре считались главными подозреваемыми в несвязанном инциденте.

Это прочтение было неверным и увело расследование по ложному следу.

Что происходило на самом деле

Патчи дошли до всех нод одинаково. unattended-upgrades был включён по всему парку:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade  "1";

Unattended-Upgrade::Automatic-Reboot не был задан, а по умолчанию он false. Поэтому новые ядра ставились на все ноды и никогда не активировались. Различалось между нодами только время с последнего ребута:

Работающее ядро Новейшее установленное Нод Аптайм
6.8.0-101 6.8.0-137 3 152 дня
6.8.0-124 6.8.0-137 2 69 дней
6.8.0-134 6.8.0-137 2 29 дней

«Дрейф» был просто записью того, когда каждую ноду в последний раз перезапускали по какой-то посторонней причине. libc6 тоже обновился везде, а значит, исправления glibc не действовали ни в одном работающем процессе, и /var/run/reboot-required лежал на всех семи нодах.

Две вещи выглядели тревожно, но ими не были. /boot был заполнен на 11%, ядер там всего два, старые удалялись автоматически. А reboot-required.pkgs перечислял около десяти пакетов ядра, что читается как «установлено десять ядер», — на самом деле это накопленная история пакетов, которые просили ребут с прошлого раза.

Проверка, которая отвечает на вопрос «мы пропатчены?»

Сравнивать работающие ядра между нодами — неправильный вопрос. Правильный задаётся для каждой ноды: работающее против новейшего установленного, плюс аптайм.

uname -r
ls -1 /boot/vmlinuz-* | sed 's|.*vmlinuz-||' | sort -V | tail -1
awk '{print int($1/86400)}' /proc/uptime
[ -f /var/run/reboot-required ] && echo reboot-required

Исправление

Поочерёдный ребут всех семи нод — drain, ребут, uncordon, дождаться здоровья хранилища, — а затем второй проход для 31 отложенного пользовательского обновления. После этого все ноды работали на 6.8.0-137, reboot-required исчез, а apt сообщил, что обновлять больше нечего. kubelet и containerd остались нетронутыми.

Ловушки, найденные по пути

  • apt-mark showhold был пуст. Ничто не защищало kubelet, kubeadm и containerd от apt upgrade. Всегда сначала смотрите список обновлений: проскочивший компонент Kubernetes ломает кластер через рассинхрон версий.
  • kubeconfig указывал напрямую на одну control-plane ноду, без HA-эндпоинта. Ребут этой ноды обрубил kubectl посреди процедуры, хотя сам кластер был в порядке. HA control plane — не то же самое, что HA доступа к нему.
  • Пакеты остаются «kept back» просто потому, что apt upgrade не ставит новые зависимости. Проверьте apt-get -s full-upgrade и продолжайте, только если он ничего не удаляет.
  • Не автоматизируйте ребуты, пока у Vault нет auto-unseal. Каждый ребут, который переносит под Vault, оставляет его запечатанным, так что ребуты без присмотра создают тихие простои.

Выводы

  • Разные ядра на нодах обычно означают разный аптайм, а не разный уровень патчей.
  • «Установлено» — не «работает». Соответствие патчам — это сравнение двух величин на каждой ноде.
  • Unattended upgrades без ребутов — половина политики патчинга.
  • Записывайте то, что знаете, и пересматривайте. Одна неверная строчка в заметках о среде стоила больше времени, чем сами ребуты.