Автообновления Linux: почему сервер моргает по утрам, а кластер теряет кворум
⚠️ Ахтунг: тут пробегал ИИ.
💡 Не все знали, но всё это время: Ubuntu Server и Debian устанавливают security-обновления автоматически и по умолчанию. Если на серверной инсталляции ничего специально не отключали — этот механизм уже работает прямо сейчас.
У одного и того же механизма — пакетных автообновлений — два типичных артефакта в эксплуатации.
На одиночном сервере — сервис «моргает» утрами. По журналу видно, что в 06:xx или около того он был остановлен и сразу же запущен, причиной никто конкретно не назначен, и админ месяцами ищет «плавающий ночной даунтайм», списывая его то на сеть, то на GC.
На кластере из трёх–пяти узлов — кластер устроен правильно с точки зрения избыточности, переживает падение одного узла, и в какой-то момент сам себя роняет: на всех узлах в одно и то же утро прилетело обновление с перезапуском сервиса. Кворума не осталось, клиент видит «недоступно».
Источник у обоих один. Разберём.
Откуда вообще приходят автообновления
Debian / Ubuntu — unattended-upgrades
В Ubuntu 24.04 и Debian 12 пакет unattended-upgrades устанавливается по умолчанию и включён, как минимум для канала security. Раскладка такая:
-
/etc/apt/apt.conf.d/20auto-upgrades— общий рубильник: включено ли вообще автоматическое обновление и нужно ли каждый день делатьapt update. -
/etc/apt/apt.conf.d/50unattended-upgrades— политика: какие репозитории трогать (Origins-Pattern), какие пакеты исключить (Package-Blacklist), писать ли отчёт на почту, разрешать ли автоматическую перезагрузку хоста (Automatic-Reboot).
Сам процесс запускается двумя systemd-таймерами:
-
apt-daily.timer— обновляет индекс пакетов и скачивает то, что разрешено. Срабатывает дважды в сутки, со случайной задержкой до 12 часов. -
apt-daily-upgrade.timer— собственно ставит обновления. По умолчаниюOnCalendar=*-*-* 6:00сRandomizedDelaySec=60m.
Здесь уже видна потенциальная ловушка: окно установки — один час после шести утра, плюс-минус секундное распределение. У вас три одинаково настроенных узла — все три попали в это окно. Если для двух из них таймер случайно выпал в один и тот же 10-минутный интервал — обновление пакета с вашим сервисом запустилось на обоих почти синхронно.
Fedora и RedOS — dnf-automatic
В семействе RPM есть свой аналог: пакет dnf-automatic. В отличие от Debian-семейства, он не установлен по умолчанию ни в Fedora, ни в RedOS — администратор включает его сознательно:
sudo dnf install dnf-automaticsudo systemctl enable --now dnf-automatic.timer
Конфигурация — в /etc/dnf/automatic.conf. Ключевые поля:
-
download_updates— скачивать ли заранее. -
apply_updates— устанавливать или только скачать (по умолчаниюno— то есть «только скачать», но это часто меняют). -
upgrade_type—default(всё) илиsecurity(только security-канал).
Триггер — таймер dnf-automatic.timer. По умолчанию OnCalendar=*-*-* 6:00 с RandomizedDelaySec=1h. Та же узкая одночасовая полоса, что и в Ubuntu, та же проблема для кластера.
В RedOS Murom 7.x используется тот же dnf-automatic. Семантически ничего нового.
Что происходит при обновлении пакета
Допустим, в репозиторий вышла новая версия пакета, в нём — бинарь сервиса с systemd-юнитом. Дальше пакетный менеджер делает следующее:
-
Распаковывает новый бинарь поверх старого.
-
Запускает скриптлеты пакета (
postinstв.deb,%post/%posttransв.rpm). Посмотреть их можно черезdpkg -eилиrpm -q --scripts <pkg>. -
В типовом случае postinstall дёргает
systemctl daemon-reload, а дальше —systemctl try-restart <service>илиrestart, если юнит был запущен. Точная команда зависит от того, как пакет собрали:dh_installsystemdв Debian-семействе по умолчанию делает перезапуск; в RPM это решение мейнтейнера. -
В Ubuntu 22.04+ поверх этого работает ещё
needrestart: он сканирует все процессы в системе и через/proc/PID/mapsнаходит те, у которых обновлённый (а на самом деле — уже удалённый из файловой системы и заменённый новым) исполняемый файл или библиотека остались примонтированными в адресное пространство. Такие процессы он помечает к перезапуску.
Важная деталь: перезапуск касается не только сервиса, чей пакет обновился. Если в очередное unattended-upgrade попал security-фикс на популярную системную библиотеку — libssl3, libcurl4, libsystemd0, libc6, zlib1g — за ней перезапустится всё, что с ней динамически слинковано: nginx, postgresql, sshd, redis, ваши собственные демоны. На практике это самая частая причина «кластер падает раз в две недели по непонятной причине»: обновляется не сам сервис, а маленький пакет в его транзитивных зависимостях.
Посмотреть, кто что держит:
sudo needrestart -b # сводный список того, # что собирается перезапуститьsudo lsof | grep '(deleted)' # процессы с уже удалёнными # файлами в адресном пространстве
Поведение needrestart в неинтерактивном режиме (то есть в unattended-upgrades) задаётся в /etc/needrestart/needrestart.conf ключом $nrconf{restart}:
-
'a'— перезапускать всё автоматически (типовая настройка в Ubuntu Server по умолчанию); -
'i'— спрашивать у пользователя (на сервере без TTY означает «не перезапускать»); -
'l'— только записать список в журнал, ничего не трогать.
Заодно полезно знать: статически слинкованные бинари (Go с CGO_ENABLED=0, Rust с musl-target, нативные с -static) от обновления системных библиотек не страдают — у их процессов в /proc/PID/maps нет внешних .so, и needrestart их не трогает.
Итог: внутри одного apt upgrade или dnf upgrade ваш сервис — и не один он — действительно может быть перезапущен. Без вашего вмешательства, без уведомления и одновременно с обновлением на других узлах того же кластера.
Артефакт 1: одиночный сервер моргает по утрам
Одиночный сервер. На нём крутится сервис: веб-приложение, прокси, база, брокер очередей — не важно. Жалоба от пользователей или из мониторинга: «каждое утро около шести сервис недоступен 10–30 секунд». В течение дня — никаких проблем. Утром — повторяется.
Это не утечка памяти, не GC-пауза, не сетевой провал и не зависание после первой большой нагрузки. Скорее всего это автообновление какого-то пакета, в чьих транзитивных зависимостях оказался ваш сервис или одна из его системных библиотек.
Проверить за две минуты:
# Что вообще запускалось ночью?sudo journalctl -u apt-daily-upgrade.service --since "7 days ago"sudo journalctl -u dnf-automatic.service --since "7 days ago"# Что обновлялось — список пакетов с временемsudo tail -n 200 /var/log/apt/history.logsudo tail -n 200 /var/log/dnf.log# Перезапускался ли ваш сервис в эти же моменты?sudo journalctl -u <ваш-сервис>.service --since "7 days ago" \ | grep -E "Stopping|Started|Stopped"
Если временные метки совпадают — диагноз подтверждён. Дальше варианты:
-
Принять. Если сессии переподключаются автоматически и пользователь это не замечает — пусть моргает; зато безопасность накатывается сама.
-
Разнести по времени (см. ниже), чтобы окно обновления было не во время бизнес-часов и не в одинаковое время каждый день.
-
Запретить трогать конкретный пакет: добавить его в
Package-Blacklistв/etc/apt/apt.conf.d/50unattended-upgradesили вexclude=в/etc/dnf/automatic.conf. Сервис не будет обновляться вообще, перезапусков не будет — но и security-фиксов не будет; принимаемое решение. -
Запретить автоматический перезапуск сервисов через
needrestartв режиме «только список» (см. ниже) — обновлять пакеты можно, но рестарт делает админ или CI.
Артефакт 2: кластер теряет большинство по совпадению окон
Простой счёт. Допустим:
-
Три узла кластера, одинаково настроенные.
-
Стандартный таймер
apt-daily-upgrade.timerсRandomizedDelaySec=60m. Распределение равномерное по 60-минутному окну. -
Обновление пакета сервиса занимает 20 секунд, перезапуск — ещё 5–10.
Вероятность того, что два узла из трёх попадут в окно перекрытия в 30 секунд — порядка 1,5–2 % на одно ночное обновление. На дистанции в год это срабатывает примерно раз. На пяти узлах — уже заметно чаще; и достаточно одного раза, чтобы кластер на короткое время потерял большинство и оборвал текущие сессии.
Если ваше приложение допускает потерю одного узла, но не двух одновременно (типовая ситуация для систем с кворумом — Raft, Paxos, большинство брокеров с N+1 резервом), это проблема обнаружения: вы её увидите как редкие, плохо воспроизводимые «ночные провалы» доступности.
Как разнести во времени
Идея простая: расписание автообновления не должно быть одинаковым на всех узлах. Дальше вопрос — насколько системно вы это разносите.
Минимум: разный OnCalendar на каждом узле
На каждом узле кластера переопределяете таймер локальным drop-in:
sudo systemctl edit apt-daily-upgrade.timer
Содержимое drop-in для первого узла:
[Timer]OnCalendar=OnCalendar=*-*-* 03:00RandomizedDelaySec=10m
Для второго:
[Timer]OnCalendar=OnCalendar=*-*-* 05:00RandomizedDelaySec=10m
И так далее. Пустая OnCalendar= обязательна — она сбрасывает значение из юнита, заданное по умолчанию, иначе расписаний будет два. Применить:
sudo systemctl daemon-reloadsudo systemctl restart apt-daily-upgrade.timersudo systemctl list-timers --all | grep apt-daily-upgrade
Для dnf-automatic.timer всё то же самое, имя только другое: systemctl edit dnf-automatic.timer.
Получше: убрать автоматический перезапуск сервиса
Если вы не хотите, чтобы пакетное обновление трогало ваш сервис в принципе — даже если бинарь обновился — можно настроить needrestart в режиме «только список, без действий»:
sudo $EDITOR /etc/needrestart/needrestart.conf# $nrconf{restart} = 'l'; # list only
В режиме l он только запишет в журнал, какие сервисы следовало бы перезапустить — и оставит решение администратору.
Для Debian/Ubuntu без needrestart можно временно блокировать запуск сервисов через policy-rc.d:
sudo install -m 0755 /dev/stdin /usr/sbin/policy-rc.d <<'EOF'#!/bin/shexit 101EOF
exit 101 означает «действие не разрешено» — dh_installsystemd это понимает и перезапуск не выполняет. Сервис при этом продолжит работать со старым бинарём, пока вы не перезапустите его вручную. На системах с настроенными Linux capabilities и долго живущими подключениями это часто как раз то, что нужно — перезапуск делает дежурный или CI.
Серьёзно: координация через внешний инструмент
Когда узлов много или они в нескольких регионах — разные OnCalendar становятся хрупкими (легко забыть один при добавлении). Тогда нужна координация уровнем выше пакетного менеджера:
-
Ansible с
serial: 1(илиserial: "30%") — обновляет кластер по одному, с проверкой работоспособности между шагами; -
Kubernetes через
PodDisruptionBudgetи rolling-update — гарантирует, что одновременно недоступно не более N узлов; -
Salt orchestrate / Chef runlist — то же.
Автообновление пакета при этом обычно отключают совсем:
# Debian/Ubuntusudo systemctl disable --now apt-daily-upgrade.timer# Fedora/RedOSsudo systemctl disable --now dnf-automatic.timer
И полагаются на оркестратор, у которого есть карта кластера и понимание кворума.
На первый взгляд кажется лишней суетой ради «всё равно когда обновится» — но цена разъехавшегося NAS, ушедшего в split-brain посреди ночи, обычно сильно выше, чем час, разово потраченный на правильный конфиг расписания и перезапусков.
Оригинал статьи — с подсветкой кода и актуальной версией текста — лежит в блоге: ololo.tech/articles/sysadmin/auto-updates-cluster.
ссылка на оригинал статьи https://habr.com/ru/articles/1051978/