Привет всем! Перед деплоем нового релиза приложения в продакшен хорошей практикой является оценка его работы с запросами от реальных пользователей. Если разом заменить текущий релиз приложения на новый, есть вероятность в случае ошибки в программном обеспечении повредить данные большого числа пользователей или получить непрогнозируемое поведение нового релиза под нагрузкой. Чтобы избежать указанных трудностей используют 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:30 257». Данный Docker‑образ «Image: frontend:30 257» использован для запуска контейнера с новой версией приложения в контуре staging (на картинке ниже выделили красным цветом).

Canary-релиз для фронтенда
Canary-релиз для фронтенда

Ниже приведем 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».

Пайплайн Jenkins для Canary-релиза фронтенда
Пайплайн Jenkins для Canary-релиза фронтенда

В случае успешного завершения тестирования, задайте значение параметра 0 в списке «Какой процент запросов направить на Canary‑релиз», и пайплайн удалит Ingress, Service и Deployment с именем «frontend‑canary». Теперь тимлид может уверенно запустить пайплайн, который развернет контейнер с новой версией приложения из Docker‑образа «Image: frontend:30 257» в контур 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» направит часть запросов в модуль с новой версией приложения.

Canary-релиз для бэкенда
Canary-релиз для бэкенда

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 параметризованный пайплайн, в котором можно изменить количество реплик старого и нового релиза.

Пайплайн Jenkins для Canary-релиза бэкенда
Пайплайн Jenkins для Canary-релиза бэкенда

Покнопке «Собрать» пайплайн 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, и реализуют канареечные развертывания как для фронтенда, так и для бэкенда. Спасибо за внимание!

Комментарии (0)