DevOps

Zero-downtime деплой в Kubernetes: rolling updates, canary и blue-green на практике

Ruslan Ismailov Опубликовано 14 мин чтения
Z

Введение: что такое zero-downtime деплой и почему это критично

Каждый раз, когда вы выкатываете новую версию приложения, возникает риск: пользователи могут столкнуться с ошибками, недоступностью сервиса или непредсказуемым поведением. Zero-downtime деплой — это набор практик и технических подходов, которые позволяют обновлять production-окружение без прерывания обслуживания запросов.

В контексте Kubernetes zero-downtime деплой перестал быть экзотикой — это базовое требование для любого серьёзного продукта. Платформа предоставляет встроенные механизмы (rolling updates), а также позволяет реализовать более сложные стратегии: blue-green и canary. В этой статье мы разберём каждую из них с реальными манифестами и командами.

Целевая аудитория статьи — DevOps-инженеры, SRE и backend-разработчики, которые уже работают с Kubernetes и хотят выстроить надёжные пайплайны доставки.

Rolling Update в Kubernetes: настройка и подводные камни

Rolling update — стратегия деплоя по умолчанию в Kubernetes. Новые поды запускаются постепенно, а старые завершаются по мере готовности новых. Это позволяет поддерживать доступность сервиса на протяжении всего обновления.

Ключевые параметры: maxSurge и maxUnavailable

Поведение rolling update контролируется двумя параметрами в секции strategy Deployment-манифеста:

  • maxSurge — максимальное количество подов сверх желаемого числа реплик, которые могут существовать одновременно. Можно задать как число или процент.
  • maxUnavailable — максимальное количество подов, которые могут быть недоступны в процессе обновления.

Пример манифеста Deployment с настроенным rolling update:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  replicas: 4
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: my-app
        version: v2
    spec:
      containers:
        - name: my-app
          image: my-registry/my-app:v2
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20

Readiness и Liveness пробы — обязательный элемент

Без корректно настроенных проб rolling update теряет смысл. Readiness probe сообщает Kubernetes, что под готов принимать трафик. Пока проба не пройдена, под не включается в балансировку. Liveness probe определяет, жив ли процесс — если нет, под перезапускается.

Типичная ошибка: приложение стартует, но ещё не завершило инициализацию (прогрев кешей, миграции). Без readiness probe Kubernetes начнёт слать трафик на неготовый инстанс. Используйте также startupProbe для приложений с долгим стартом.

Команды kubectl для управления rolling update

# Обновить образ
kubectl set image deployment/my-app my-app=my-registry/my-app:v2 -n production

# Следить за процессом
kubectl rollout status deployment/my-app -n production

# Просмотр истории
kubectl rollout history deployment/my-app -n production

# Откат к предыдущей версии
kubectl rollout undo deployment/my-app -n production

Blue-Green деплой: переключение трафика через Service и Ingress

Blue-green деплой предполагает наличие двух идентичных окружений: blue (текущая production-версия) и green (новая версия). Трафик в любой момент направлен только на одно из них. После успешного тестирования green-окружения вы переключаете трафик одной командой.

Манифест blue-деплоя и green-деплоя

# Blue Deployment (текущий production)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-blue
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      slot: blue
  template:
    metadata:
      labels:
        app: my-app
        slot: blue
        version: v1
    spec:
      containers:
        - name: my-app
          image: my-registry/my-app:v1
          ports:
            - containerPort: 8080
---
# Green Deployment (новая версия)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-green
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      slot: green
  template:
    metadata:
      labels:
        app: my-app
        slot: green
        version: v2
    spec:
      containers:
        - name: my-app
          image: my-registry/my-app:v2
          ports:
            - containerPort: 8080

Service и переключение трафика

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  namespace: production
spec:
  selector:
    app: my-app
    slot: blue   # Меняем на "green" для переключения
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

Переключение трафика выполняется патчем сервиса:

kubectl patch service my-app-svc -n production \
  -p '{"spec":{"selector":{"slot":"green"}}}'

При использовании Ingress с NGINX Ingress Controller или аналогами можно переключать трафик на уровне Ingress-ресурса, изменяя backend-сервис. Это даёт дополнительный контроль без изменения самого Service.

Преимущество blue-green: мгновенное переключение и такой же мгновенный откат. Недостаток: удвоенные ресурсы на период деплоя.

Canary деплой: постепенный роллаут с весовыми правилами

Canary-деплой позволяет направить небольшой процент трафика на новую версию приложения, не подвергая всех пользователей риску. Это идеальная стратегия для тестирования изменений на реальной нагрузке.

Canary через нативный Kubernetes (без service mesh)

Простой способ — запустить canary-deployment с малым числом реплик рядом со stable-deployment. Kubernetes балансирует трафик между подами пропорционально их количеству.

# Stable Deployment: 9 реплик = ~90% трафика
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-stable
  namespace: production
spec:
  replicas: 9
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        track: stable
    spec:
      containers:
        - name: my-app
          image: my-registry/my-app:v1
---
# Canary Deployment: 1 реплика = ~10% трафика
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-canary
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        track: canary
    spec:
      containers:
        - name: my-app
          image: my-registry/my-app:v2

Service при этом выбирает поды только по метке app: my-app, охватывая оба деплоя.

Canary через Ingress с весовыми аннотациями (NGINX)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-canary-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  rules:
    - host: my-app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-canary-svc
                port:
                  number: 80

Аннотация canary-weight: "10" направляет 10% трафика на canary. Значение меняется постепенно — 10%, 25%, 50%, 100% — по мере уверенности в стабильности новой версии.

Canary с Argo Rollouts

Для продвинутого управления canary-деплоем рекомендуется использовать Argo Rollouts — расширение Kubernetes, которое добавляет тип ресурса Rollout со встроенной поддержкой canary, blue-green и анализа метрик.

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
  namespace: production
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 10
        - pause: {duration: 5m}
        - setWeight: 30
        - pause: {duration: 10m}
        - setWeight: 60
        - pause: {duration: 10m}
        - setWeight: 100
      analysis:
        templates:
          - templateName: success-rate
        startingStep: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-registry/my-app:v2
          ports:
            - containerPort: 8080

Интеграция стратегий деплоя в CI/CD пайплайн

Стратегии деплоя без простоев раскрывают свой потенциал только в связке с автоматизированным CI/CD пайплайном. Рассмотрим типичный флоу на примере GitLab CI:

stages:
  - build
  - test
  - deploy-canary
  - promote
  - rollback

build:
  stage: build
  script:
    - docker build -t my-registry/my-app:$CI_COMMIT_SHA .
    - docker push my-registry/my-app:$CI_COMMIT_SHA

deploy-canary:
  stage: deploy-canary
  script:
    - kubectl set image deployment/my-app-canary
        my-app=my-registry/my-app:$CI_COMMIT_SHA -n production
    - kubectl rollout status deployment/my-app-canary -n production
  environment:
    name: production/canary

promote:
  stage: promote
  when: manual
  script:
    - kubectl set image deployment/my-app-stable
        my-app=my-registry/my-app:$CI_COMMIT_SHA -n production
    - kubectl rollout status deployment/my-app-stable -n production

rollback:
  stage: rollback
  when: manual
  script:
    - kubectl rollout undo deployment/my-app-stable -n production
    - kubectl rollout undo deployment/my-app-canary -n production

Ключевые принципы интеграции:

  • Тег образа Docker привязывается к коммиту ($CI_COMMIT_SHA) — это обеспечивает воспроизводимость.
  • Каждый шаг деплоя верифицируется через kubectl rollout status.
  • Promote (полный роллаут) выполняется вручную после наблюдения за метриками canary.
  • Rollback доступен как отдельный шаг пайплайна.

Откат (Rollback): автоматический и ручной

Даже идеальный деплой может пойти не так. Kubernetes хранит историю ревизий Deployment, что позволяет быстро откатиться.

Ручной откат

# Откат к предыдущей ревизии
kubectl rollout undo deployment/my-app -n production

# Откат к конкретной ревизии
kubectl rollout undo deployment/my-app --to-revision=3 -n production

# Просмотр ревизий с деталями
kubectl rollout history deployment/my-app -n production --revision=2

Автоматический откат через Argo Rollouts и анализ метрик

Argo Rollouts поддерживает AnalysisTemplate — описание условий успешности деплоя на основе метрик из Prometheus:

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
  namespace: production
spec:
  metrics:
    - name: success-rate
      interval: 1m
      successCondition: result[0] >= 0.95
      failureLimit: 3
      provider:
        prometheus:
          address: http://prometheus:9090
          query: |
            sum(rate(http_requests_total{status!~"5..",app="my-app"}[1m]))
            /
            sum(rate(http_requests_total{app="my-app"}[1m]))

Если success rate опускается ниже 95% три раза подряд, Argo Rollouts автоматически прерывает деплой и откатывается к предыдущей версии.

Также полезно настроить progressDeadlineSeconds в Deployment — если деплой не завершается за указанное время, Kubernetes помечает его как failed:

spec:
  progressDeadlineSeconds: 300

Мониторинг деплоя: как понять, что обновление прошло успешно

Zero-downtime деплой невозможен без наблюдаемости. Что нужно мониторить:

  • Error rate — процент ответов 5xx. Резкий рост сигнализирует о проблеме.
  • Latency (p50, p95, p99) — деградация задержек может быть признаком регрессии.
  • Pod restart count — частые рестарты говорят о крашах приложения.
  • Readiness probe failures — поды не проходят проверку готовности.
  • Kubernetes eventskubectl get events -n production --sort-by=.lastTimestamp.

Полезные команды для мониторинга деплоя в реальном времени

# Статус деплоя
kubectl rollout status deployment/my-app -n production

# Состояние подов
kubectl get pods -n production -l app=my-app -w

# Описание пода при проблемах
kubectl describe pod <pod-name> -n production

# Логи в реальном времени
kubectl logs -f -l app=my-app -n production --tail=100

# События namespace
kubectl get events -n production --sort-by=.lastTimestamp

Интегрируйте Prometheus + Grafana с дашбордами деплоя. Хорошей практикой является автоматическая аннотация дашборда Grafana при каждом деплое — это позволяет визуально сопоставить изменение метрик с моментом обновления.

Сравнение стратегий деплоя

Каждая стратегия подходит для своих сценариев. Ниже — ключевые характеристики:

  • Rolling Update: встроен в Kubernetes, минимальные накладные расходы по ресурсам, подходит для большинства сервисов. Недостаток — в процессе обновления одновременно работают две версии, что требует обратной совместимости API и схемы БД.
  • Blue-Green: мгновенное переключение и откат, идеален для критичных систем. Требует удвоенных ресурсов на время деплоя. Сложнее при stateful-компонентах.
  • Canary: наиболее безопасная стратегия — реальный трафик тестирует новую версию на малой доле пользователей. Требует качественного мониторинга и инструментов управления весами (Argo Rollouts, Istio, NGINX). Подходит для highload-систем с большим трафиком.

Рекомендации по выбору:

  1. Начните с Rolling Update — это разумный выбор по умолчанию для большинства приложений.
  2. Переходите к Blue-Green, если требуется гарантированный мгновенный откат и у вас есть запас ресурсов.
  3. Внедряйте Canary для highload-сервисов, где даже 1% ошибок критичен, и есть зрелая система мониторинга.

Заключение

Zero-downtime деплой в Kubernetes — это не просто техническая настройка, а инженерная культура. Rolling update подойдёт большинству команд как отправная точка. Blue-green даёт уверенность при критичных обновлениях. Canary — наиболее зрелая стратегия для production-систем с высокой нагрузкой.

Любая из этих стратегий работает надёжно только в комбинации с корректными readiness/liveness пробами, автоматизированным CI/CD пайплайном и полноценным мониторингом. Инвестируйте время в настройку этих компонентов — они окупятся спокойными ночами и уверенными релизами.

Лучший деплой — тот, который пользователи не замечают.

Технологии

Теги

Руслан Исмаилов

Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →