«Дрейф ядер» оказался дрейфом аптайма: патчи ставились, ребутов не было
Три разных ядра на семи нодах выглядели как незавершённые волны патчей и увели расследование по ложному следу. Патчи на всех нодах были одинаковые, различался только аптайм.
В 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 без ребутов — половина политики патчинга.
- Записывайте то, что знаете, и пересматривайте. Одна неверная строчка в заметках о среде стоила больше времени, чем сами ребуты.