переключать продакшен-трафик атомарно с быстрым откатом

Blue-green развёртывание в Kubernetes: выкатка без простоя и лишней сложности

12 минут

При поэтапном rolling update старая и новая версии одновременно принимают трафик. Blue-green держит два полных стека, проверяет неактивный цвет через preview Service и переключает продакшен одним изменением селектора — откат тем же патчем назад.

Почему rolling update отдаёт пользователям новую версию раньше, чем вы успеваете среагировать

По умолчанию Kubernetes выкатывает релиз поэтапно — это удобно, но под старой и новой ревизией одновременно идёт боевой трафик. Сломанный API или утечка памяти уже бьют по клиентам, пока вы решаете прервать выкатку. Откат медленный: дождаться drain подов, подтянуть образы, дождаться readiness — на высокой нагрузке даже полминуты превращаются в потерянные платежи и обращения в поддержку. Миграции схемы усугубляют риск: откатить приложение нельзя вместе с уже применённой миграцией БД. Blue-green держит два полных стека — blue на текущем продакшене, green на кандидате — и ведёт пользователей через один Service. Green проверяют изолированно, затем переключают селектор. Если проверки провалились, продакшен остаётся на blue. Откат — один patch селектора назад, а не гонка с повторным деплоем.

Blue-green на чистом Kubernetes: два Deployment, preview Service и один боевой Service

Отдельного CRD blue-green в Kubernetes нет, но Deployment, Service и метки достаточны — service mesh не обязателен. Запустите app-blue и app-green с меткой version и одинаковым числом реплик в продакшене. myapp-service указывает на активный цвет. Добавьте myapp-green-preview и myapp-blue-preview — они всегда смотрят на свой цвет, чтобы пробные запросы шли в неактивный стек, не трогая продакшен. При промоушене обновляйте в селекторе и app, и version — если оставить только version, трафик может уйти в никуда. После переключения держите предыдущий цвет включённым заданное время для отката, затем масштабируйте вниз. Ingress подхватывает Endpoints за секунды; всё равно проверьте публичный health после switch. Стратегию blueGreen в Argo Rollouts можно использовать вместо скриптов, если нужен контроллер.

Выкатить green, проверить через preview, переключить атомарно

Примените или обновите green Deployment с неизменяемым тегом образа. Дождитесь rollout status, затем выполните curl к preview Service из пода в кластере — не обращайтесь к DNS, для которого нет Service. Patch боевого Service с обоими ключами селектора. Проверьте публичный health endpoint и оставьте blue работать два часа или пока не утихнут алерты. Скрипт промоушена чередует цвета, поэтому имена Deployment остаются постоянными от релиза к релизу.

YAML · Deployment blue с readiness probe
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-blue
  labels:
    app: myapp
    version: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
        - name: app
          image: myregistry/myapp:v1.4.2
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
YAML · боевой и preview Service
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
    version: blue
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-green-preview
spec:
  selector:
    app: myapp
    version: green
  ports:
    - port: 8080
      targetPort: 8080
YAML · green Deployment с новым тегом образа
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-green
  labels:
    app: myapp
    version: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
        - name: app
          image: myregistry/myapp:v1.5.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
Bash · ручной сценарий промоушена
kubectl apply -f app-green.yaml
kubectl rollout status deployment/app-green --timeout=120s

kubectl run smoke-green --rm -i --restart=Never --image=curlimages/curl:8.5.0 -- \
  curl -sf http://myapp-green-preview.production.svc.cluster.local:8080/healthz

kubectl patch svc myapp-service --type=merge -p \
  '{"spec":{"selector":{"app":"myapp","version":"green"}}}'

curl -sf https://myapp.example.com/healthz
Bash · скрипт промоушена с безопасным patch селектора
#!/usr/bin/env bash
set -euo pipefail

NAMESPACE=production
NEW_VERSION=${1:?usage: promote.sh <image-tag>}

CURRENT=$(kubectl get svc myapp-service -n "$NAMESPACE" -o jsonpath='{.spec.selector.version}')
if [[ "$CURRENT" == "blue" ]]; then NEW_COLOR=green; else NEW_COLOR=blue; fi

kubectl set image deployment/app-"$NEW_COLOR" app=myregistry/myapp:"$NEW_VERSION" -n "$NAMESPACE"
kubectl rollout status deployment/app-"$NEW_COLOR" -n "$NAMESPACE" --timeout=180s

kubectl run smoke-"$NEW_COLOR" --rm -i --restart=Never -n "$NAMESPACE" \
  --image=curlimages/curl:8.5.0 -- \
  curl -sf "http://myapp-${NEW_COLOR}-preview.${NAMESPACE}.svc.cluster.local:8080/healthz"

kubectl patch svc myapp-service -n "$NAMESPACE" --type=merge -p \
  "{\"spec\":{\"selector\":{\"app\":\"myapp\",\"version\":\"$NEW_COLOR\"}}}"

echo "Active color: $NEW_COLOR. Previous color $CURRENT kept for rollback."

Практика: ёмкость, проверки здоровья, данные и гибрид с canary

На время промоушена держите оба цвета на полном числе реплик — простаивающий под дешевле сорванного переключения и холодного старта. Фиксируйте digest или версионный тег образа; mutable latest на обоих стеках убивает детерминизм. Расширьте /healthz: не только «процесс жив», но и ping БД, прогрев кэша, доступность критичных зависимостей. Для компонентов с состоянием применяйте expand-and-contract миграции отдельным шагом «только вперёд» и держите код совместимым с обеими версиями схемы в окне перекрытия. Дашборды и алерты привязывайте к метке version, чтобы green был виден до приёма продакшен-трафика. На рискованных релизах совместите blue-green с короткой canary-фазой — пять процентов через вес Ingress или Argo Rollouts, проверка ошибок и задержки, затем полный switch селектора. Blue-green не универсален, но два Deployment, preview Service и одно атомарное переключение дают большинству команд выкатку без простоя и откат за секунды без mesh.

Если нужен постепенный перевод трафика и метрические гейты вместо мгновенного переключения, см. progressive delivery с canary и feature flags в Kubernetes.

Когда смена схемы БД должна быть совместима с обеими версиями, согласуйте порядок выкатки с миграциями схемы базы данных в CI/CD.