Привіт! Я Kernelka і сьогодні ми зробимо ваш Nginx справжньою ракетою 🚀: безперервні релізи, перезавантаження без обриву з’єднань і надійний план відкату. Ми поєднаємо перевірені техніки graceful reload, atomic-підміну конфігів та systemd-магію. Результат — zero‑downtime на бойовому сервер Linux.

План гри: що дає zero‑downtime

Безпростаєве розгортання — це коли ви оновлюєте конфіги/сертифікати/бекенди, а користувачі навіть не помічають змін. Ключові елементи:

  • Попередня перевірка конфігурації (nginx -t) і лише потім graceful reload.
  • Atomic-підміна директорії з конфігами через символічне посилання (symlink swap).
  • reuseport для одночасної роботи старих і нових воркерів на одному порту.
  • systemd для організації залежностей і автоматизації задач (так, це саме ті cron та systemd timers).

Готуємо Nginx до безперервних релізів

1) Включаємо reuseport для справді м’яких перезапусків

SO_REUSEPORT дозволяє старим і новим процесам Nginx ділити той самий порт, поки йде перезавантаження. Створіть сніпет і підключіть його у ваших server {} блоках.

sudo mkdir -p /etc/nginx/snippets
sudo tee /etc/nginx/snippets/reuseport.conf >/dev/null <<'EOF'
# Додайте відповідні рядки для ваших віртуальних хостів
listen 80 reuseport;
# Якщо є TLS
# listen 443 ssl http2 reuseport;
EOF

# Приклад підключення у вашому vhost
# у файлі сервера додайте:  include /etc/nginx/snippets/reuseport.conf;

sudo nginx -t && sudo systemctl reload nginx

2) Atomic-підміна конфігів через "live"-посилання

Структура: усі релізи у /etc/nginx/conf.d/releases/<build-id>, а робоча збірка — це посилання /etc/nginx/conf.d/live. Основний конфіг Nginx підтягує лише live/*.conf.

# 2.1. Завантажувач конфігу
sudo tee /etc/nginx/conf.d/000-loader.conf >/dev/null <<'EOF'
# Підхоплюємо лише активний реліз
include /etc/nginx/conf.d/live/*.conf;
EOF

# 2.2. Створимо перший реліз і активуємо його
REL=init-$(date +%Y%m%d-%H%M%S)
sudo mkdir -p /etc/nginx/conf.d/releases/$REL
# Покладіть сюди ваші *.conf, наприклад app.conf
sudo tee /etc/nginx/conf.d/releases/$REL/app.conf >/dev/null <<'EOF'
server {
    include /etc/nginx/snippets/reuseport.conf;
    server_name _;
    location / { return 200 'OK'; }
}
EOF

# Активуємо атомарно (ln -sfn не рве існуючі з’єднання)
sudo ln -sfn /etc/nginx/conf.d/releases/$REL /etc/nginx/conf.d/live
sudo nginx -t && sudo systemctl reload nginx

3) Скрипт деплою з перевіркою і миттєвим відкатом

Нижче — простий деплой-скрипт. Він копіює нові конфіги у нову директорію релізу, перевіряє їх, атомарно перемикає live і робить graceful reload. У разі помилки — відкат.

sudo tee /usr/local/bin/nginx-zero-downtime-deploy >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
RELEASE="$(date +%Y%m%d-%H%M%S)"
SRC_DIR="/srv/nginx-config"         # тут ваш git-клон з *.conf
REL_DIR="/etc/nginx/conf.d/releases/$RELEASE"
LIVE_LINK="/etc/nginx/conf.d/live"

# 1) Оновлюємо джерело (можна замінити на rsync з CI)
if [[ -d "$SRC_DIR/.git" ]]; then
  git -C "$SRC_DIR" pull --ff-only
fi

# 2) Готуємо новий реліз
sudo mkdir -p "$REL_DIR"
sudo cp -a "$SRC_DIR"/*.conf "$REL_DIR"/

# 3) Тимчасово перемикаємо live на новий реліз і перевіряємо
PREV="$(readlink -f "$LIVE_LINK" || true)"
sudo ln -sfn "$REL_DIR" "$LIVE_LINK"
if ! sudo nginx -t; then
  echo "Config test failed. Rolling back..." >&2
  [[ -n "$PREV" ]] && sudo ln -sfn "$PREV" "$LIVE_LINK"
  exit 1
fi

# 4) Graceful reload
sudo systemctl reload nginx

echo "Deployed release: $RELEASE"
EOF
sudo chmod +x /usr/local/bin/nginx-zero-downtime-deploy

systemd socket activation: де вона дійсно корисна

Чесно: сам Nginx у мейнстримі не підтримує прийом дескрипторів від systemd для HTTP-портів. Зате socket activation ідеальна для бекендів — наприклад, php-fpm чи uvicorn на UNIX-сокетах. Це прибирає 502-помилки під час перезапусків бекенда.

# Приклад: вмикаємо php-fpm через сокет
sudo tee /etc/systemd/system/php-fpm.socket >/dev/null <<'EOF'
[Unit]
Description=PHP-FPM Socket (on-demand)

[Socket]
ListenStream=/run/php-fpm.sock
SocketMode=0660
SocketUser=www-data
SocketGroup=www-data

[Install]
WantedBy=sockets.target
EOF

sudo tee /etc/systemd/system/php-fpm.service >/dev/null <<'EOF'
[Unit]
Description=PHP-FPM Service
Requires=php-fpm.socket
After=network.target

[Service]
Type=simple
ExecStart=/usr/sbin/php-fpm --nodaemonize --fpm-config /etc/php-fpm.conf
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now php-fpm.socket

Тепер у nginx налаштування використовуйте upstream на unix:/run/php-fpm.socksystemd сам підніме php-fpm при першому зверненні.

Автоматизація через systemd timers (замість cron)

Щоб деплой ішов сам (CI/CD, nightly або з гіту) — запустіть таймер. Це і є приємна автоматизація задач на нормальному сервер Linux.

sudo tee /etc/systemd/system/deploy-nginx.service >/dev/null <<'EOF'
[Unit]
Description=Deploy Nginx configs atomically

[Service]
Type=oneshot
ExecStart=/usr/local/bin/nginx-zero-downtime-deploy
EOF

sudo tee /etc/systemd/system/deploy-nginx.timer >/dev/null <<'EOF'
[Unit]
Description=Run Nginx deploy every 5 minutes

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now deploy-nginx.timer

Логи запусків дивіться так:

journalctl -u deploy-nginx.service -n 100 --no-pager

Альтернативні способи

  • Blue-Green: тримайте дві групи server {} і перемикайте трафік через map або окремий L4-проксі. Добре для великих релізів.
  • Containerized: тримайте Nginx у контейнері, а конфіги — як том; перемикайте тег/том атомарно й робіть graceful reload всередині контейнера.
  • Path-активація: замість таймера можна зробити systemd.path, який реагує на зміни у /srv/nginx-config.

GUI-спосіб через Cockpit

Якщо вам зручніше через веб-інтерфейс, встановіть Cockpit і керуйте службами/таймерами:

sudo apt install -y cockpit || sudo dnf install -y cockpit
sudo systemctl enable --now cockpit.socket

Далі в браузері відкрийте https://<IP>:9090 → Services → знайдіть nginx і ваш deploy-nginx.timer: вмикайте/вимикайте, викликайте Reload, дивіться журнали. Зручно й безпечно 🛡️

FAQ

Q: Після reload бачу короткі 502. Чому?
A: Зазвичай падає бекенд. Увімкніть для нього systemd socket activation або додайте proxy_next_upstream з кількома upstream’ами. Переконайтеся, що Nginx говорить з бекендом по unix-сокету або по TCP з health-check.

Q: “address already in use” під час запуску?
A: Перевірте, що ви не намагаєтеся одночасно тримати порт у systemd .socket і у Nginx. Для zero‑downtime використовуйте reuseport, а не socket activation на 80/443.

Q: Як швидко відкочуватися?
A: Знайдіть попередній реліз у /etc/nginx/conf.d/releases/ і перемкніть посилання назад:

PREV="/etc/nginx/conf.d/releases/<prev-id>"
sudo ln -sfn "$PREV" /etc/nginx/conf.d/live
sudo nginx -t && sudo systemctl reload nginx

Q: Чому таймер не запускається?
A: Перевірте systemctl list-timers, час сервера і що таймер увімкнено (enabled). Дивіться логи сервісу деплою, а не таймера.

Q: Чи достатньо nginx -s reload?
A: Так, якщо конфіги валідні і ввімкнено reuseport. Для великих змін інколи краще staged-підміна й поетапний reload.

Порада від Kernelka

Додайте у nginx налаштування worker_shutdown_timeout 10s; — старі воркери коректно догризуть активні з’єднання перед виходом. І завжди тримайте напоготові сценарій відкату в тому ж репозиторії, що й деплой.

Підсумок

  • Перевіряйте конфіги і робіть graceful reload замість рестарту.
  • Використовуйте reuseport і atomic-підміну через live-посилання.
  • Вмикайте systemd socket activation для бекендів, не для 80/443 у Nginx.
  • Заведіть таймери cron та systemd timers для стабільного CI/CD.
  • Майте швидкий rollback і журнали на долоні.

Готово! Ваш Nginx тепер розгортається без простою й чемно обслуговує трафік навіть під час змін.