Привет всем! Перед деплоем нового релиза приложения в продакшен хорошей практикой является оценка его работы с запросами от реальных пользователей. Если разом заменить текущий релиз приложения на новый, есть вероятность в случае ошибки в программном обеспечении повредить данные большого числа пользователей или получить непрогнозируемое поведение нового релиза под нагрузкой. Чтобы избежать указанных трудностей используют Canary-релизы - это когда рядом с актуальным релизом приложения в продакшене развертывают релиз с новой версией приложения и направляют на него часть запросов от реальных пользователей. В данной статье мы расскажем о нашем опыте внедрения Canary-релизов в CI/CD платформу Gitorion.
Схема стенда
Варианты реализации Canary-релизов рассмотрим на примере, приведенном на рисунке ниже. В кластере Kubernetes развернуто приложение, состоящее из микросервисов frontend и backend. В контуре production развернут текущий актуальный релиз приложения, с которым работают клиенты в данный момент. В контуре staging развернут, протестирован и продемонстрирован заказчику новый релиз приложения. Точку входа в кластер и доступ клиентов к приложению выполняет контроллер Ingress-nginx. Теперь нужно протестировать в production новый релиз приложения из контура staging.

Canary-релизы c помощью контроллера Ingress-nginx
Данный вариант подойдет для микросервисов, подключенных как бэкенд непосредственно к контроллеру Ingress-nginx. В нашем примере это микросервис frontend. В контуре production создайте службу (Service) с именем "frontend-canary" и Deployment с именем "frontend-canary", который запускает модуль с контейнером, созданным из Docker-образа "Image: frontend:30257". Данный Docker-образ "Image: frontend:30257" использован для запуска контейнера с новой версией приложения в контуре staging (на картинке ниже выделили красным цветом).

Ниже приведем yaml-манефест Ingress уже созданный в production контуре, который направляет трафик для домена gitorion.ru в службу с именем "frontend", которая в свою очередь передает запросы в модуль с текущей версией приложения.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
meta.helm.sh/release-name: frontend
meta.helm.sh/release-namespace: production
generation: 1
labels:
app.kubernetes.io/managed-by: Helm
name: frontend
namespace: production
spec:
ingressClassName: nginx
rules:
- host: gitorion.ru
http:
paths:
- backend:
service:
name: frontend
port:
number: 80
path: /
pathType: Prefix
tls:
- hosts:
- gitorion.ru
secretName: infrastructure-tlsДобавьте в production еще один Ingress с именем "frontend-canary", который направит 10% трафика, предназначенного тому же домену gitorion.ru, в службу с именем "frontend-canary", которая в свою очередь направит запрос в модуль с новой версией приложения.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
meta.helm.sh/release-name: frontend-canary
meta.helm.sh/release-namespace: production
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: canary
nginx.ingress.kubernetes.io/canary-weight: "10"
generation: 1
labels:
app.kubernetes.io/managed-by: Helm
name: frontend-canary
namespace: production
spec:
ingressClassName: nginx
rules:
- host: gitorion.ru
http:
paths:
- backend:
service:
name: frontend-canary
port:
number: 80
path: /
pathType: Prefix
tls:
- hosts:
- gitorion.ru
secretName: infrastructure-tlsВ аннотации "nginx.ingress.kubernetes.io/canary-weight" задайте, какой процент запросов реальных пользователей контроллер Ingress-nginx должен направить на Canary-релиз приложения. Постепенно увеличивайте значение "nginx.ingress.kubernetes.io/canary-weight" в ходе тестирования, наращивая количество запросов от реальных пользователей в модуль с новым релизом приложения.
Непрерывную доставку (Continuous Delivery) в CI/CD платформе Gitorion выполняет Jenkins, поэтому мы добавили параметризованный пайплайн, создающий в production контуре Service, Deployment и Ingress с именами ''frontend-canary" и позволяющий менять значение параметра "nginx.ingress.kubernetes.io/canary-weight" в аннотации Ingress "backend-canary".

В случае успешного завершения тестирования, задайте значение параметра 0 в списке "Какой процент запросов направить на Canary-релиз", и пайплайн удалит Ingress, Service и Deployment с именем "frontend-canary". Теперь тимлид может уверенно запустить пайплайн, который развернет контейнер с новой версией приложения из Docker-образа "Image: frontend:30257" в контур production.
Canary-релизы с помощью Kubernetes Service Mesh
Теперь разберем, как развернуть Canary-релиз, в случае если микросервис не подключен непосредственно к контроллеру Ingress-nginx или точка входа в кластер Kubernetes реализована не с помощью Ingress-nginx. На нашем стенде это микросервис backend.
В Kubernetes вновь созданные модули получают динамический IP-адрес из подсети IP-адресов для модулей. Если Deployment или StatefulSet запускает несколько модулей приложения при горизонтальном масштабировании, точку единого входа организуют с помощью службы (Service). На модули навешивают метки (Labels), и служба с помощью селектора (Selector) направляет запросы в модули микросервиса с заданной меткой. Трафик равномерно балансируется между всеми модулями.
На рисунке ниже новая версия приложения запущена в staging контуре в контейнере, созданном из Docker-образа "Image:backend:cad09" (выделили красным цветом). Заказчик протестировал и одобрил новую версию приложения, и пришло время протестировать его в продакшене.
В production контуре создайте еще один Deployment с именем "backend-canary", запускающий модуль с контейнером, созданным из Docker-образа "Image:backend:cad09", с новой версией приложения. Навесьте метку "app:backend" на модули нового релиза, точно такую же, как и на модулях старого релиза, и служба (Service) с именем "backend" направит часть запросов в модуль с новой версией приложения.

Cписок модулей сервиса backend в контуре production:
control@dc1-plane:~$ kubectl get pod -n production -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
backend-55bd4f7b68-ckttw 1/1 Running 0 30m 10.10.137.22 dc1-worker1 <none> <none>
backend-55bd4f7b68-m865n 1/1 Running 0 22s 10.10.137.14 dc1-worker1 <none> <none>
backend-55bd4f7b68-t432g 1/1 Running 0 22s 10.10.137.15 dc1-worker1 <none> <none>
backend-canary-59674db64b-rt76l 1/1 Running 0 5m52s 10.10.137.17 dc1-worker1 <none> <none>
frontend-777d84c7cd-5d2bc 1/1 Running 0 30m 10.10.137.38 dc1-worker1 <none> <none>Описание службы "backend" в контуре production:
control@dc1-plane:~$ kubectl describe svc backend -n production
Name: backend
Namespace: production
Labels: app=backend
app.kubernetes.io/managed-by=Helm
Annotations: meta.helm.sh/release-name: backend
meta.helm.sh/release-namespace: production
Selector: app=backend
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.98.154.72
IPs: 10.98.154.72
Port: <unset> 9000/TCP
TargetPort: 9000/TCP
Endpoints: 10.10.137.22:9000,10.10.137.14:9000,10.10.137.15:9000,10.10.137.17:9000
Session Affinity: None
Events: <none>Среди конечных точек Endpoints службы "backend" видим три IP-адреса модулей со старой версией приложения (10.10.137.22, 10.10.137.14, 10.10.137.15) и один IP-адрес (10.10.137.17) модуля с новой версией приложения. Cоответственно, 3/4 запросов реальных пользователей (или 75%) пойдет на старую версию приложения, 1/4 запросов (или 25%) - на новую. Добейтесь требуемого вам процентного соотношения запросов запуском соответствующего количества модулей в Deployment-ах "backend" и "backend-canary".
Мы добавили в Jenkins параметризованный пайплайн, в котором можно изменить количество реплик старого и нового релиза.

По кнопке "Собрать" пайплайн cоздаст в production контуре Deployment c именем "backend-canary", запускающий в модулях контейнеры из Docker-образа "Image: backend:cad09" с новой версией приложения. Тут же можно менять количество реплик для Deployment-ов старого и нового релизов. После завершения удачного тестирования, задайте значение 0 в поле "Количество реплик Canary-релиза", и пайплайн удалит Deployment с именем "backend-canary". Теперь тимлид может запустить пайплайн, выполняющий промоушен новой версии приложения из контура staging в контур production.
Заключение
В данной статье мы рассказали о нашем опыте внедрения Canary-релизов в CI/CD платформу Gitorion. Продемонстрированные практики предназначены для CI/CD цикла, развернутого в кластере Kubernetes, и реализуют канареечные развертывания как для фронтенда, так и для бэкенда. Спасибо за внимание!
