Menu
Szybki wybór
Hosting Domeny VPS SSL Kalkulator Porównania FAQ
Aktywne kody
Wszystkie kody rabatowe

Zero-downtime deployment z Nginx — blue-green, rolling update, health checks i rollback

Opublikowano: 9 kwietnia 2026 · Kategoria: VPS / DevOps

Deploy o 3 w nocy żeby uniknąć użytkowników? Okno maintenance w niedzielę? To już przeszłość. Zero-downtime deployment pozwala wdrażać nowe wersje aplikacji w ciągu dnia roboczego, bez błędów 502 i bez utraty sesji użytkownika. W tym przewodniku omówimy mechanizm graceful reload Nginx, blue-green deployment, rolling update z health checkami i automatyczny rollback gdy coś pójdzie nie tak.

Nginx graceful reload — jak to działa

Zanim przejdziemy do złożonych strategii — zrozum fundamentalny mechanizm. nginx -s reload nie restartuje serwera, lecz zastępuje worker procesy zachowując ciągłość obsługi:

# Sprawdź PID master procesu przed reload
ps aux | grep "nginx: master"
cat /var/run/nginx.pid

# Przetestuj konfigurację (ZAWSZE przed reload)
sudo nginx -t

# Graceful reload — zero dropped connections
sudo nginx -s reload
# lub:
sudo systemctl reload nginx

# Sprawdź PID po reload — master PID się nie zmienia!
cat /var/run/nginx.pid

# Obserwuj stare workery kończące obsługę żądań
watch -n 0.5 "ps aux | grep nginx"

Stary worker może żyć kilka sekund lub minut (zależy od aktywnych WebSocket i long-polling połączeń). Parametr worker_shutdown_timeout ustawia limit:

# /etc/nginx/nginx.conf
worker_processes auto;
worker_shutdown_timeout 30s;  # Maksymalny czas na graceful shutdown workerów

Rolling update — wymiana instancji po jednej

Rolling update działa gdy masz load balancer i wiele instancji aplikacji. Wyłączasz backend z rotacji Nginx, wdrażasz nową wersję, weryfikujesz zdrowie, przywracasz do rotacji:

# /etc/nginx/sites-available/app.conf
upstream app_backend {
    server 127.0.0.1:3001;  # instancja 1
    server 127.0.0.1:3002;  # instancja 2
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;

        # Retry na zdrowy backend gdy jeden padnie
        proxy_next_upstream error timeout http_502 http_503;
        proxy_next_upstream_tries 2;
    }

    # Health check endpoint (Nginx Plus lub przez limit_except)
    location /nginx-health {
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

Skrypt rolling update — wdrożenie po jednej instancji:

#!/bin/bash
# rolling-deploy.sh
set -euo pipefail

APP_DIR="/var/www/app"
PORTS=(3001 3002)
NGINX_CONF="/etc/nginx/sites-available/app.conf"
HEALTH_URL="http://localhost"
NEW_VERSION=${1:-"latest"}

log() { echo "[$(date '+%H:%M:%S')] $1"; }

health_check() {
  local port=$1
  local retries=10
  while [ $retries -gt 0 ]; do
    if curl -sf "http://localhost:${port}/health" > /dev/null 2>&1; then
      return 0
    fi
    sleep 2
    retries=$((retries - 1))
  done
  return 1
}

for PORT in "${PORTS[@]}"; do
  log "=== Wdrażam na port $PORT ==="

  # 1. Wyłącz instancję z load balancera
  log "Wyłączam port $PORT z rotacji Nginx..."
  sudo sed -i "s/server 127.0.0.1:${PORT};/server 127.0.0.1:${PORT} down;/" "$NGINX_CONF"
  sudo nginx -t && sudo nginx -s reload
  sleep 5  # Daj czas na drenaż aktywnych połączeń

  # 2. Zatrzymaj starą instancję
  log "Zatrzymuję starą instancję..."
  pm2 stop "app-${PORT}" || true

  # 3. Wdróż nowy kod
  log "Wdrażam $NEW_VERSION..."
  cd "$APP_DIR"
  git pull origin main
  npm ci --production
  PORT=$PORT pm2 start ecosystem.config.js --only "app-${PORT}"

  # 4. Health check nowej instancji
  log "Sprawdzam zdrowie na porcie $PORT..."
  if ! health_check "$PORT"; then
    log "BŁĄD: Health check nieudany na porcie $PORT — rollback!"
    sudo sed -i "s/server 127.0.0.1:${PORT} down;/server 127.0.0.1:${PORT};/" "$NGINX_CONF"
    sudo nginx -s reload
    exit 1
  fi

  # 5. Przywróć do rotacji
  log "Przywracam port $PORT do rotacji..."
  sudo sed -i "s/server 127.0.0.1:${PORT} down;/server 127.0.0.1:${PORT};/" "$NGINX_CONF"
  sudo nginx -t && sudo nginx -s reload

  log "Port $PORT zaktualizowany pomyślnie."
  sleep 10  # Stabilizacja przed przejściem do kolejnej instancji
done

log "=== Rolling deploy zakończony ==="

Blue-green deployment

Blue-green to dwa kompletne środowiska. Aktywne środowisko (blue) obsługuje ruch produkcyjny. Nowa wersja wdrażana jest na green, testowana, a następnie load balancer przełącza 100% ruchu:

# Struktura katalogów
/var/www/
  app-blue/      # Blue environment — port 3001
  app-green/     # Green environment — port 3002
  app-current -> app-blue  # Symlink na aktywne środowisko

# /etc/nginx/conf.d/blue-green.conf
# ACTIVE=blue (zmień na green żeby przełączyć)
upstream app_active {
    server 127.0.0.1:3001;  # blue
    # server 127.0.0.1:3002;  # green (odkomentuj po wdrożeniu)
}
#!/bin/bash
# blue-green-switch.sh
set -euo pipefail

NGINX_CONF="/etc/nginx/conf.d/blue-green.conf"
BLUE_PORT=3001
GREEN_PORT=3002

# Wykryj aktualnie aktywne środowisko
if grep -q "server 127.0.0.1:${BLUE_PORT};" "$NGINX_CONF"; then
  ACTIVE="blue"
  ACTIVE_PORT=$BLUE_PORT
  STANDBY="green"
  STANDBY_PORT=$GREEN_PORT
else
  ACTIVE="green"
  ACTIVE_PORT=$GREEN_PORT
  STANDBY="blue"
  STANDBY_PORT=$BLUE_PORT
fi

echo "Aktywne: $ACTIVE (port $ACTIVE_PORT)"
echo "Standby: $STANDBY (port $STANDBY_PORT)"

# 1. Wdróż na standby
echo "Wdrażam na $STANDBY..."
cd "/var/www/app-${STANDBY}"
git pull origin main
npm ci --production
pm2 restart "app-${STANDBY}" --update-env

# 2. Sprawdź zdrowie standby
echo "Health check na porcie $STANDBY_PORT..."
sleep 5
HEALTH=$(curl -sf "http://localhost:${STANDBY_PORT}/health" || echo "FAIL")
if [ "$HEALTH" = "FAIL" ]; then
  echo "Health check nieudany! Przerywam bez przełączenia."
  exit 1
fi

# 3. Przełącz ruch (atomic swap)
echo "Przełączam ruch z $ACTIVE na $STANDBY..."
sed -i "s/server 127.0.0.1:${ACTIVE_PORT};/server 127.0.0.1:${STANDBY_PORT};/" "$NGINX_CONF"
nginx -t && nginx -s reload

# 4. Aktualizuj symlink
ln -sfn "/var/www/app-${STANDBY}" /var/www/app-current

echo "Przełączono na $STANDBY. Poprzednie środowisko $ACTIVE pozostaje gotowe do rollback."
echo "Rollback: bash blue-green-switch.sh (przełączy z powrotem)"

Health checks — endpointy aplikacji

Każda aplikacja powinna eksponować endpoint /health sprawdzający stan wewnętrznych zależności:

// health.ts — endpoint Node.js/Express
app.get('/health', async (req, res) => {
  const checks = {
    uptime: process.uptime(),
    timestamp: Date.now(),
    db: 'unknown' as string,
    redis: 'unknown' as string,
  };

  try {
    // Sprawdź połączenie DB
    await db.query('SELECT 1');
    checks.db = 'ok';
  } catch {
    checks.db = 'error';
  }

  try {
    // Sprawdź Redis
    await redis.ping();
    checks.redis = 'ok';
  } catch {
    checks.redis = 'error';
  }

  const isHealthy = checks.db === 'ok' && checks.redis === 'ok';
  const status = isHealthy ? 200 : 503;

  res.status(status).json({
    status: isHealthy ? 'ok' : 'degraded',
    checks,
  });
});

Porównanie strategii deployment

Strategia Downtime Zasoby Rollback Kiedy używać
In-place restart Tak (sekundy) Trudny Nigdy na produkcji z ruchem
Rolling update Nie Możliwy Standardowy deploy z LB i 2+ instancjami
Blue-green Nie Natychmiastowy Gdy potrzebujesz gwarantowanego rollback w 30s
Canary Nie 1.1×–2× Natychmiastowy Ryzykowne zmiany, testowanie na % ruchu

Najczęstsze pytania

Czym jest zero-downtime deployment? +
Zero-downtime deployment (ZDD) to wdrożenie nowej wersji aplikacji bez widocznego dla użytkownika przestoju — żadne żądanie HTTP nie dostaje błędu 502/503 ani nie jest odrzucane. Osiąga się to przez utrzymanie przynajmniej jednej działającej instancji podczas wymiany pozostałych. Techniki: rolling update (wymieniaj po jednej instancji), blue-green (przełącz cały ruch na nowe środowisko), canary (5% ruchu na nową wersję, obserwuj, skaluj).
Jak działa nginx -s reload — czy to bezpieczne? +
Komenda nginx -s reload (lub systemctl reload nginx) wysyła sygnał HUP do master procesu Nginx. Master otwiera nowe worker procesy z nową konfiguracją, a stare workery kończą obsługę aktywnych żądań (graceful shutdown) przed zamknięciem. Trwa to kilka milisekund do sekund. Nowe połączenia trafiają od razu do nowych workerów. To jest bezpieczne — zero dropped requests. Natomiast nginx -s stop / systemctl restart nginx natychmiast zabija wszystkie workery — nie używaj tego na produkcji podczas ruchu.
Jaka jest różnica między blue-green a canary deployment? +
Blue-green: masz dwa identyczne środowiska (blue=produkcja, green=staging). Po wdrożeniu na green przełączasz 100% ruchu przez zmianę konfiguracji load balancera. Rollback = przełącz z powrotem na blue. Prosto, ale drogie (podwójne zasoby). Canary: stopniowo przenosisz % ruchu na nową wersję — np. 5%, 20%, 50%, 100%. Obserwujesz metryki na każdym etapie. Wykryjesz problem zanim dotknie wszystkich. Wymaga bardziej zaawansowanego routingu (nagłówki, cookies, weight w Nginx upstream).
Jak wdrożyć zero-downtime dla Node.js z PM2? +
PM2 obsługuje zero-downtime przez cluster mode i graceful reload: pm2 reload app-name. PM2 uruchamia nową instancję, czeka aż zacznie odpowiadać (health check przez listen_timeout), następnie wysyła SIGINT do starej instancji. Stara instancja obsługuje aktywne żądania przez kill_timeout (domyślnie 1600ms) i się zamyka. Klucz: aplikacja musi obsługiwać SIGINT gracefully — zamknąć serwer HTTP i poczekać na zakończenie aktywnych połączeń przed exit(0).

Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.