Blue-Green deployment bez Kubernetes: realizacja na Docker Compose z zerowym downtime'em
Realizacja blue-green deploymentu jest możliwa nawet bez orkiestratorów pokroju Kubernetes — wystarczy Docker Compose, nginx i dopracowana logika graceful shutdown. Takie podejście jest szczególnie aktualne dla zespołów pracujących na dedykowanych serwerach lub maszynach wirtualnych, gdzie użycie pełnego klastra jest nieopłacalne z powodów ekonomicznych lub architektonicznych. W artykule omawiana jest techniczna implementacja ciągłego deploymentu dla web services i background workerów z uwzględnieniem specyfiki przetwarzania wiadomości i zarządzania stanem.
Architektura blue-green na Docker Compose
W bazowej konfiguracji każde aplikacja jest dublowana: istnieją dwa niezależne instancje — blue i green. Oba uruchamiane są jako oddzielne serwisy w docker-compose.prod.yml, ale tylko jeden z nich obsługuje ruch w danym momencie. Do routingu używany jest reverse proxy (w przykładzie — nginx), który przełącza upstream-adres podczas deploymentu. Kluczowa zasada: nowa instancja jest w pełni uruchamiana i przechodzi health-check, zanim stary instancja zostanie zatrzymana.
Konfiguracja wymaga jawnego wskazania portów dla każdej instancji:
web-blue:
ports:
- "127.0.0.1:8002:8000"
web-green:
ports:
- "127.0.0.1:8003:8000"
To pozwala nginxowi na odwoływanie się do odpowiedniej instancji poprzez localhost i konkretny port. Taki schemat eliminuje kolizje i upraszcza zarządzanie cyklem życia.
Zarządzanie background workerami
Procesy w tle (workery), konsumujące wiadomości z kolejki (np. RabbitMQ), wymagają szczególnego podejścia. Proste zatrzymanie kontenera doprowadzi do utraty wiadomości lub ich ponownego przetwarzania w przypadku nieidempotentnych operacji. Rozwiązanie — wdrożenie mechanizmu graceful shutdown na poziomie aplikacji.
Realizowane jest to poprzez klasę singleton z flagą is_accepting. Przed przetworzeniem każdej zadania worker sprawdza tę flagę:
- Jeśli
is_accepting == True— zadanie jest wykonywane. - Jeśli
is_accepting == False— wiadomość jest zwracana do kolejki zrequeue=True.
Po otrzymaniu sygnału SIGTERM flaga jest przełączana na False, po czym aplikacja czeka na zakończenie wszystkich bieżących zadań (z zapasem 5–10 sekund) i dopiero wtedy kończy pracę. To gwarantuje, że żadna wiadomość nie zostanie utracona i nie dojdzie do niespójności danych.
Ważne niuanse:
- W przypadku wieloprocesowych workerów (np. ProcessPoolExecutor) stan
is_acceptingmusi być dostępny we wszystkich procesach potomnych. W przeciwnym razie część konsumentów będzie kontynuować przetwarzanie nowych zadań. - Nieidempotentne operacje wymagają dodatkowej ochrony: ponowne przetwarzanie może naruszyć integralność danych.
- Użycie rozproszonej flagi (np. w Redis) komplikuje architekturę, ale może być uzasadnione w systemach o dużym obciążeniu.
Skrypt deploymentu: logika krok po kroku
Skrypt deploymentu automatyzuje przełączanie między instancjami. Logika jest taka sama dla web services i workerów, ale z różnymi punktami wejścia.
Główne etapy:
- Określenie aktywnej instancji (poprzez analizę konfiguracji nginx lub listy uruchomionych kontenerów).
- Uruchomienie nieaktywnej instancji (
docker compose up -d). - Sprawdzenie gotowości nowej instancji poprzez health-check (do 30 prób co 2 sekundy).
- Aktualizacja konfiguracji nginx i wykonanie
nginx -s reload. - Oczekiwanie na zakończenie wszystkich aktywnych żądań HTTP (zazwyczaj 5 sekund).
- Zatrzymanie starej instancji (
docker compose stop).
Przykład dla web service:
echo "server 127.0.0.1:${INACTIVE_PORT};" > "$UPSTREAM_CONF"
nginx -t && nginx -s reload
sleep 5
$COMPOSE stop web-${ACTIVE}
Taki porządek działań gwarantuje, że ruch nigdy nie jest kierowany na niegotowy serwis, a stara instancja kończy pracę dopiero po pełnym przełączeniu.
Kompatybilność schematu bazy danych i migracje
Jedna z głównych trudności blue-green deploymentu — jednoczesna praca dwóch wersji aplikacji z jedną bazą danych. Aby uniknąć błędów, wszystkie migracje muszą być backward compatible.
Zalecane podejście — dwuetapowa aktualizacja:
- Pierwszy deployment: usuwane jest użycie przestarzałego pola/tabeli w kodzie, ale struktura pozostaje w bazie danych. Nowa i stara wersja działają poprawnie.
- Drugi deployment (po pełnym przełączeniu ruchu): wykonywana jest migracja usuwająca nieużywane elementy schematu.
To zwiększa liczbę operacji, ale eliminuje ryzyko awarii starej instancji z powodu niekompatybilności schematu.
Co ważne
- Blue-green deployment na Docker Compose jest możliwy bez Kubernetes, ale wymaga ręcznego zarządzania instancjami i proxy.
- Graceful shutdown dla workerów jest kluczowy — bez niego możliwa jest utrata lub duplikacja wiadomości.
- Wszystkie migracje bazy danych muszą być backward compatible; usuwanie pól wymaga dwuetapowego podejścia.
- Health-check nowej instancji jest obowiązkowy przed przełączeniem ruchu.
- Konfiguracja nginx musi być aktualizowana atomowo i weryfikowana przez
nginx -t.
— Editorial Team
Brak komentarzy.