Стратегии деплоя в Kubernetes: Часть 2

Стратегии деплоя в Kubernetes: Часть 2


Сегодня мы разберём еще пару популярных стратегий деплоя — Blue/Green и Canary. Для начала освежим в памяти, о чём речь:

  • Blue/Green-деплой — это когда у нас есть два идентичных окружения: существующее Blue с текущей версией приложения и новое Green с обновлённой. Если после проверки всё работает как надо, Blue считается устаревшим, а трафик перенаправляется на Green.
  • Canary-деплой — это подход «выкатываем медленно». Две версии приложения работают параллельно: стабильная продакшен-версия и новая. Небольшая часть реального трафика направляется на новую версию (обычно она развернута в уменьшенном масштабе, с меньшим числом реплик) для тестирования и мониторинга. Проверка может выполняться автоматически или с помощью эмуляции действий пользователей. Когда тестирование проходит успешно, старую версию постепенно сворачивают, а весь трафик переводят на новую.

Canary Deployment

Давайте разберёмся, как можно сделать canary-деплой с помощью нативного Kubernetes.

Мы будем запускать две версии приложения в продакшене параллельно. Когда убедимся, что новая версия работает стабильно, постепенно развернём её для всех пользователей. Для финального раскатывания после проверки можно использовать любую из стратегий, о которых мы говорили ранее — Rolling Update или Recreate.

В примере будем использовать тот же простой nginx-приложение, что и в предыдущем посте.

Подготавливаем стабильную версию

Сначала создадим стабильную версию v1 — файл my-webapp.yaml.

В нём деплоймент с тремя подами, которые слушают порт 80.

Кроме того, создаём сервис типа NodePort, чтобы приложение можно было открыть снаружи на Kubernetes-нодах.

На нодах сервис будет доступен по порту 32001.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  labels:
    app: webapp
    version: v1
spec:
  selector:
    matchLabels:
      app: webapp
  replicas: 3
  template:
    metadata:
      labels:
        app: webapp
        version: v1
    spec:
      containers:
      - name: webapp
        image: yaiiteng/my-nginx:v1
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webapp-service
  labels:
    app: webapp
spec:
  selector:
    app: webapp
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: 32001

Таким образом, сейчас у нас развернуто приложение v1 с сервисом, который прокидывает его наружу через NodePort.

Подготавливаем canary-релиз

Создадим новый манифест my-webapp-canary.yaml с образом версии 2:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-canary                   ## Даем другое имя, чтобы не пересекалось с текущим деплойментом
  labels:
    app: webapp            
    version: v2                         ## Обновили версию на V2
spec:
  selector:
    matchLabels:
      instance: webapp-canary
      version: v2
  replicas: 1
  template:
    metadata:
      labels:
        instance: webapp-canary
        version: v2
    spec:
      containers:
        - name: webapp
          image: yaiiteng/my-nginx:v2
          ports:
            - containerPort: 80
---

Здесь мы явно создаём новый деплоймент с другим именем, чтобы две версии приложения могли работать параллельно.

  • Количество реплик установлено в 1 — мы выкатываем новую версию пока только для тестирования.
  • Метки указывают, что это canary-деплой — webapp-canary-v2.
  • Контейнер запускается с образом v2.

Разворачиваем canary-версию

Команда уже знакома:

kubectl apply -f my-webapp-canary.yaml

Готово — теперь у нас есть два деплоймента:

  • webapp (v1) — стабильная версия с 3 подами, доступная пользователям.
  • webapp-canary (v2) — новая версия, запущенная одним подом для проверки.

После того как pod’ы новой версии поднимутся и пройдут базовые проверки (sanity-check), можно будет направить на них часть реального трафика.

Подаём живой трафик

Чтобы направить часть трафика на webapp-canary, нам нужно “научить” webapp-service регистрировать pod’ы версии v2. Селектор у webapp-service ищет метку app=webapp, чтобы подхватывать новые pod’ы.

selector:
    app: webapp

Значит, если мы просто добавим в canary-версию такую же метку, сервис зарегистрирует новый endpoint. Для теста можно прометить pod напрямую командой:

kubectl label pods -l instance=webapp-canary app=webapp

Эта команда находит pod’ы с меткой instance=webapp-canary и добавляет им новую метку app=webapp.

После расстановки меток у нас будут вот такие ресурсы с нужными лейблами.

Тестируем

Теперь наш NodePort-сервис webapp-service отправляет трафик как на стабильную версию v1, так и на v2 webapp-canary.

Соотношение трафика напрямую зависит от количества реплик.

Например:

если приложение получает 20 запросов, то примерно 15 запросов (75%) обслужат pod’ы версии v1 (3 реплики), а оставшиеся 5 запросов (25%) попадут на pod версии v2 (1 реплика).

Главное преимущество стратегии Canary — возможность тестировать новую версию прямо в продакшене на реальном трафике.

Но стоит понимать: при использовании нативного Kubernetes мы почти не контролируем, какой процент запросов уходит на canary-версию — всё зависит от числа реплик.

Если хочется гибко управлять трафиком (например, постепенно увеличивать долю для новой версии, основываясь на результатах тестов), придётся подключить дополнительные инструменты экосистемы Kubernetes — например, Istio.

Istio — это сервисная mesh-платформа, которая даёт полный контроль над маршрутизацией и распределением трафика.

Откат (Roll Back)

А что если новая версия v2 нас не устроила? Нужно быстро откатиться на старую.

Для этого достаточно удалить у pod’ов canary-версии метку app=webapp:

kubectl label pods -l instance=webapp-canary app-

Эта команда уберёт у pod’ов label app=webapp, и сервис перестанет слать на них трафик.

Дальше вы можете либо отлаживать деплоймент webapp-canary, либо просто удалить его из кластера, если он больше не нужен.

Вывод в продакшен (Production GO-Live)

Если всё прошло хорошо и новая версия работает стабильно, пора выкатывать её всем пользователям.

Есть несколько способов сделать это:

Обновить основной деплоймент:

меняем образ в my-webapp.yaml на версию v2 и применяем файл:

kubectl apply -f my-webapp.yaml

Это запустит rollout новой версии по выбранной стратегии (Rolling Update или Recreate — какая указана в манифесте).

Переключить реплики:

снизить количество реплик у старого деплоймента до 0, а у canary — увеличить.

Это можно сделать изменив my-webapp-canary.yaml или прямо командой:

kubectl scale --replicas=3 deploy/webapp-canary

После этого весь трафик начнёт обслуживаться новой версией.

Blue/Green Deployment

Blue/Green предполагает два идентичных окружения.

Мы запускаем одинаковое количество реплик двух версий приложения и при необходимости можем быстро откатиться, просто вернув трафик на предыдущую версию.

Давайте сразу к практике.

Подготавливаем стабильную версию (Blue)

Назовём стабильную версию webapp-v1 и создадим 3 реплики:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-v1
  labels:
    app: webapp
    version: v1
spec:
  selector:
    matchLabels:
      app: webapp
  replicas: 3
  template:
    metadata:
      labels:
        app: webapp
        version: v1
    spec:
      containers:
      - name: webapp
        image: yaiiteng/my-nginx:v1
        ports:
        - containerPort: 80

Для сервиса добавим ещё один селектор — будем направлять трафик только на поды с меткой version=v1:

apiVersion: v1
kind: Service
metadata:
  name: webapp-service
  labels:
    app: webapp
    
spec:
  selector:
    app: webapp
    version: v1
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: 32001

Разворачиваем новую версию (Green)

Чтобы поднять версию v2, достаточно поменять имя деплоймента, обновить метки и указать новый образ:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-v2                       ## Обновили имя на v2
  labels:
    app: webapp
    version: v2                         ## Версия обновлена на V2
spec:
  selector:
    matchLabels:
      app: webapp
      version: v2  
  replicas: 3
  template:
    metadata:
      labels:
        app: webapp
        version: v2  
    spec:
      containers:
        - name: webapp
          image: yaiiteng/my-nginx:v2     ## Новый образ
          ports:
            - containerPort: 80

На этом этапе у нас уже есть два набора подов в кластере:

  • webapp-v1 — стабильная версия (Blue),
  • webapp-v2 — новая версия (Green).

Сервис пока что направляет трафик только на v1.

Переводим продакшен на новую версию (Production Go-Live)

Стратегия Blue/Green считается immutable — мы не трогаем существующий деплоймент v1.

Вместо этого просто перенаправляем трафик на новую версию v2.

Для этого достаточно обновить webapp-service, чтобы он выбирал pod’ы с меткой version: v2:

apiVersion: v1
kind: Service
metadata:
  name: webapp-service
  labels:
    app: webapp
spec:
  selector:
    app: webapp
    version: v2
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: 32001

После обновления сервис начнёт отправлять весь трафик на pod’ы версии v2.

Можно сделать то же самое прямо командой kubectl patch без редактирования YAML:

kubectl patch service webapp-service -p '{"spec":{"selector":{"app": "webapp", "version":"v2"}}}'

Если новая версия полностью устраивает и откат больше не нужен, можно смело уменьшить количество реплик у старого деплоймента:

kubectl scale --replicas=0 deploy/webapp-v1

Теперь вся нагрузка обслуживается v2, а старую среду (Blue) можно выключить.

Откат (Roll Back)

Сильная сторона Blue/Green — быстрый откат. Чтобы вернуть старую версию, достаточно пропатчить сервис так, чтобы он снова указывал на v1:

kubectl patch service webapp-service -p '{"spec":{"selector":{"app": "webapp", "version":"v1"}}}'

В Blue/Green у нас нет истории релизов внутри самого кластера Kubernetes. То есть команда kubectl rollout history здесь не покажет историю раскатки.

Надеюсь, это было полезно.

Report Page