Стратегии деплоя в 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 здесь не покажет историю раскатки.
Надеюсь, это было полезно.