Как обновить ОС на семи нодах Kubernetes так, чтобы никто не заметил
Поочерёдное обновление Ubuntu в кластере, который хранит всё своё состояние сам. Предварительные проверки превратили страшную работу в скучную, а темп задавал Longhorn.
Кластер моей домашней лаборатории — семь VM на одном хосте Proxmox без ИБП, и эти семь нод держат всё: тома Longhorn, Postgres на CloudNativePG, трёхнодовый Vault на Raft и оба туннеля Cloudflare, через которые опубликованы мои сайты. Пакеты отстали, и я хотел обновить ОС до того, как браться за сам Kubernetes. Обновлять ноды, на которых лежит всё состояние, звучит рискованно. Скучным это сделала предварительная проверка.
Что нашла предварительная проверка
Прежде чем что-то запускать, я посмотрел, что обновление реально затронет:
- Обновлений ядра не было, значит, ребут нужен не ради ядра.
- Ни один компонент Kubernetes не был dpkg-пакетом. kubelet, kubeadm, containerd, runc и etcd были положены бинарниками в
/usr/local/binисходным инструментом развёртывания, apt-репозитория Kubernetes не было.aptпросто не мог их тронуть, поэтому пустойapt-mark showholdздесь не был проблемой. - Среди 48 пакетов не было libc, OpenSSH и ядра systemd.
nftables.serviceбыл выключен, поэтому обновление пакетаnftablesне могло перезагрузить набор правил и снести правила Calico. kube-proxy к тому же работал в режиме IPVS.- Ноль обновлений безопасности в очереди: unattended-upgrades уже ставил их сам.
Процедура
По одной ноде, начиная с канарейки. Канарейку я выбирал по скучности: воркер, на котором были только Grafana, утилиты и один том Longhorn. Воркер с пиром Vault, членом Postgres, контроллером Argo CD и репликой обоих туннелей шёл последним среди воркеров. Control-plane ноды — после всех воркеров.
Для каждой ноды: снапшот в Proxmox, kubectl drain, обновление с NEEDRESTART_MODE=l, ребут, пока нода ещё выведена из работы, uncordon и самое важное — дождаться, пока все тома Longhorn станут healthy, и только потом переходить к следующей.
Несущим был флаг NEEDRESTART_MODE=l. needrestart не был настроен, и под apt -y он мог либо зависнуть на вопросе, либо перезапустить containerd — а это убивает все контейнеры на ноде. l значит «только покажи, решаю я». На всех семи нодах он сообщил, что перезапуск контейнеров не нужен.
Что удивило
- Drain одной ноды деградировал тома, подключённые к другим нодам, потому что реплики разнесены. После одного drain шесть томов стали degraded, крупные перестраивались около четырёх минут на общем диске. Ждать
healthyмежду нодами — обязательно, а не перестраховка. - Наивный подсчёт «не healthy» врёт: у отключённых томов поле robustness пустое, и они попадают в «не healthy». Пришлось смотреть на реальные значения.
- CloudNativePG переключался сам, когда нода primary становилась недоступной для планирования. Ждите одно переключение на каждый такой drain и проверяйте
2/2 healthyперед ребутом. - AppArmor выставил
reboot-requiredтолько после обновления, заранее флага не было. Планируйте ребут для каждой ноды в любом случае. - Размещение реплик туннелей решало, останутся ли сайты доступны. С одной репликой каждого туннеля на разных воркерах сайты всё время отвечали 200. Обе реплики на одной ноде означали бы настоящий простой.
Итог
48 пакетов на ноду, Ubuntu 24.04.4, ядро без изменений, kubelet не тронут, все семь нод Ready, все тома Longhorn healthy, etcd 3/3 с одинаковыми raft-индексами, Postgres 2/2, Vault 3/3, все приложения Argo CD Synced, все эндпоинты отвечают. Снапшоты удалены после проверки.
Выводы
- Сначала предварительная проверка: что именно затронет обновление и может ли оно тронуть компоненты самого кластера?
- Канарейку выбирайте по тому, как мало на ней лежит.
- Пусть темп задаёт хранилище. Следующая нода ждёт томов, а не часов.
- Перед drain проверяйте размещение реплик всего публичного.