Как использовать blue-green-деплой: руководство по выкату одного и нескольких приложений

от автора

Всем привет! На связи Юрий Шахов, DevOps-инженер компании «Флант». Недавно мне нужно было организовать бесшовный деплой клиентских приложений. Я изучил различные подходы для этого и остановился на стратегии blue-green-деплоя. Но проблема была в том, что я не мог найти материалов с практическими примерами, в найденных статьях описывались только теоретические аспекты. Поэтому мне пришлось изучать подход blue-green-деплоя самостоятельно. И теперь мне захотелось поделиться этим опытом.

В статье я задеплою приложение в blue-green, а также покажу, как работает смена между blue и green на практике. Я не буду рассматривать различные стратегии деплоя, а также их преимущества. Для ознакомления с теорией blue-green и других вариаций рекомендую почитать наш материал про различные стратегии деплоя в Kubernetes.

Эту же статью я поделил на две части: сначала рассмотрим реализацию деплоя приложения в blue-green-стратегии, а затем попробуем werf bundle для деплоя нескольких приложений из одного репозитория. Есть разные способы реализовать эту стратегию, можно использовать дополнительные инструменты, такие как Service Mesh, Argo CD и другие. Я же буду деплоить с помощью werf, все ресурсы описывать как Helm-шаблоны, а для развёртывания использовать GitLab. Предполагается, что читатель знаком с этими технологиями. Особенность здесь в том, что применяются нативные сущности и механизмы в виде лейблов для Kubernetes. Далее green и blue будем называть «версиями» приложения. Также в этой статье не будем рассматривать вопросы миграции баз данных, хотя для некоторых приложений это может быть необходимо.

Простой blue-green

Предположим, что у нас есть приложение и мы хотим его задеплоить. Делать это будем в два этапа: 

  1. Деплой самого приложения (deploy_app), например Deployment и Service.

  2. Смена версии: деплой Ingress с нужным именем Service (deploy_ingress). В этой стадии будет переключение трафика между версиями приложения.

Представим это в виде пайплайна:

Для реализации определим переменную deploy_version (значение будем брать из GitLab CI, который рассмотрим позже), которая будет равна blue или green и будет подставляться в Helm-шаблоны. Для Deployment и Service добавляем лейблы:

{{ $deploy_version := "" }} {{ if .Values.werf.deploy_version }} {{ $deploy_version = print "-" .Values.werf.deploy_version }} {{ end }} --- apiVersion: apps/v1 kind: Deployment metadata:   name: {{ .Chart.Name }}{{ $deploy_version }}   labels:     app: {{ .Chart.Name }}{{ $deploy_version }} ... --- apiVersion: v1 kind: Service metadata:   name: {{ .Chart.Name }}{{ $deploy_version }} spec:   selector:     app: {{ .Chart.Name }}{{ $deploy_version }} ...

Чтобы трафик дошёл до пода, нужно создать Ingress. Для обращения к определённой версии приложения мы будем указывать Service с нужным именем (blue или green). В таком случае шаблон Ingress будет выглядеть следующим образом:

{{ $deploy_version := "" }} {{ if .Values.werf.deploy_version }} {{ $deploy_version = print "-" .Values.werf.deploy_version  }} {{ end }} --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata:   name: example   labels:     deploy-version: {{ .Values.werf.deploy_version | quote }} spec:   ingressClassName: nginx   rules:   - host: example.com     http:       paths:       - path: /         pathType: Prefix         backend:           service:             name: {{ .Chart.Name }}{{ $deploy_version }}             port:               name: http  tls:   - hosts:     - example.com     secretName: {{ .Chart.Name }}-tls

Вместо изменений на Ingress можно изменять Service, направляя трафик на blue или green Deployment по лейблам, но делать этого не рекомендуется. В таком случае мы потеряем возможность обращаться к Deployment по имени Service в кластере. Это может быть проблемой для проверки правильности обновления, так как не получится обратиться к новой версии до того, как на неё направится весь трафик. 

Ещё можно создать второй Ingress, ведущий на неактивную версию, с другим доменом для тестирования. В таком случае нужно закрыть его за авторизацией для ограничения доступа.

Теперь рассмотрим пайплайн. При деплое приложения необходимо объявлять переменную deploy_version — версию, на которую будет производиться деплой. Для werf это можно сделать так:

werf converge --set "werf.deploy_version=${DEPLOY_VERSION}"

Также во время деплоя будем проверять, что версия не является активной, то есть на неё не идёт трафик, и наш выкат не повлияет на пользователей. Сама проверка реализована следующим образом: мы получаем информацию о том, на какой Service указывает работающий в кластере Ingress, и находим там blue или green.

Ниже пример полного gitlab-ci.yml:

stages:   - deploy_app   - deploy_ingress  .check_upstreams: &check_upstreams   - APP_CURRENT_ACTIVE=$(werf kubectl -n ${WERF_NAMESPACE} get ingress example --output=custom-columns='SVCs:..service.name' --no-headers --ignore-not-found | awk -F '-' {'print $NF'})  .deploy_app:   stage: deploy_app   script:     - *check_upstreams     - if [[ ${KUBE_CURRENT_ACTIVE} == ${UPSTREAM} ]];       then         tput setaf 9 && echo "Обнаружена попытка деплоя на активную версию, деплой будет остановлен!" && exit 1;       else         werf converge \           --release example-${UPSTREAM} \           --set "werf.deploy_version=${UPSTREAM}";       fi;   allow_failure: false  .deploy_ingress:   stage: converge_ingresses   script:     - *check_upstreams     - if [ ${APP_CURRENT_ACTIVE} == ${DEPLOY_VERSION} ];       then         tput setaf 9 && echo "Обнаружена попытка переключения на активную версию, деплой будет остановлен!" && exit 1;       else         werf converge         --set "werf.deploy_version=${DEPLOY_VERSION}"       fi;  Deploy to blue:   extends: .deploy_app   environment:     name: production   variables:     UPSTREAM: "blue"  Deploy to green:   extends: .deploy_app   environment:     name: production   variables:     UPSTREAM: "green"  Switch to blue:   extends: .deploy_ingress   environment:     name: production   variables:     DEPLOY_VERSION: "blue"  Switch to green:   extends: .deploy_ingress   environment:     name: production   variables:     DEPLOY_VERSION: "green"

Что мы в итоге сделали:

  1. Скорректировали Helm-шаблоны для Deployment, Service и Ingress, добавив к ним «цвет» нашей версии.

  2. Написали CI, который:

    • деплоит приложение в blue и green;

    • деплоит Ingress, который переключает трафик на нужную версию;

    • проверяет, что деплои не происходят на активную версию.

А теперь перейдём к части с бандлами.

Деплой нескольких приложений с помощью werf bundle

Зачем могут понадобиться бандлы? Допустим, клиенту необходимо деплоить несколько приложений вместе, соответственно, делать это удобнее из одного репозитория. Механизм бандлов позволяет опубликовать чарт приложения и деплоить его в дальнейшем без доступа к конкретному Git-репозиторию. Всё, что требуется, — это доступ к container registry, где хранится бандл. Такой подход позволяет упростить процесс доставки чарта приложений.

Упаковывать приложения мы будем с помощью werf bundle. Подробно на описании инструмента останавливаться не будем, с его преимуществами и кейсами использования можно ознакомиться в документации.

Создание бандлов происходит в основном репозитории приложения, здесь же сконцентрируемся только на развёртывании. В CI-файле укажем названия приложений и соответствующие переменные для каждого из них: репозиторий, тег бандла и название Ingress:

variables:   FIRST_REPO_BUNDLE: registry.gitlab.awesome.ru/frontend/first   FIRST_TAG: "0.1"   FIRST_INGRESS: first ...  # apps_for_matrix & apps_for_bash должны содержать одинаковые значения!  .apps_for_matrix: &apps_for_matrix   ["FIRST", "SECOND", "THIRD", "FOURTH", "FIFTH"]  .apps_for_bash: &apps_for_bash   APPLICATIONS=("FIRST", "SECOND", "THIRD", "FOURTH", "FIFTH")

По сравнению с первой частью в пайплайне будет уже три стадии. Поскольку мы деплоим из одного репозитория несколько приложений, нам необходимо убедиться, что все они находятся в одном состоянии. Для этого реализуем джобу проверки состояния версий, назовём её check_upstream:

На этой стадии должны выполняться следующие условия:

  • Активная версия у всех приложений одинаковая.

  • У приложения нет активных версий (при условии, что оно ещё не было задеплоено в кластер).

stages:   - check_upstreams   - deploy_apps   - deploy_ingresses  .base_werf: &base_werf   - set -x   - type trdl && source $(trdl use werf 2)   - werf version   - type werf && source $(werf ci-env gitlab --verbose --as-file)  .check_upstreams: &check_upstreams   - *base_werf   - *apps_for_bash   - |     GREEN=false     BLUE=false     EMPTY=0      for APP in ${APPLICATIONS[@]}     do       REPOSITORY_INGRESS=${APP}_INGRESS       APP_CURRENT_ACTIVE=$(werf kubectl -n ${WERF_NAMESPACE} get ingress ${!REPOSITORY_INGRESS} --output=custom-columns='SVCs:..service.name' --no-headers --ignore-not-found | awk -F '-' {'print $NF'})        EMPTY=$((EMPTY+1))       if [[ ${APP_CURRENT_ACTIVE} == "green" ]];         then GREEN=true;       elif [[ ${APP_CURRENT_ACTIVE} == "blue" ]];         then BLUE=true;       elif [[ -z ${APP_CURRENT_ACTIVE} ]];         then EMPTY=$((EMPTY-1));       else         tput setaf 9 && echo "Что-то пошло не так! Статус версий некорректен" && exit 1;       fi;     done      if [[ ${GREEN} != ${BLUE} ]];       then       if [[ ${GREEN} ]]         COLOR="green"         then tput setaf 14 && echo "Статус версий для приложений одинаков — green, можно продолжать деплой";       elif [[ ${BLUE} ]]         COLOR="blue"         then tput setaf 14 && echo "Статус версий для приложений одинаков — blue, можно продолжать деплой";       fi;     elif [[ ${EMPTY} = 0 ]]       then tput setaf 14 && echo "Ingress для данных приложений в кластере не обнаружено, можно продолжать деплой";     else       tput setaf 9 && echo "Статус версий для приложений отличается, деплой будет остановлен!!!" && exit 1;     fi;  Check_upstreams:   stage: check_upstreams   script:     - *check_upstreams   environment:     name: production   when: always   allow_failure: false 

Деплой приложения будет происходить с применением бандла. В эту команду мы передаём все необходимые аргументы и не забываем указать разное имя релиза (флаг --release) для разных приложений, иначе деплой одного будет перезаписывать деплой предыдущего. Далее с помощью parallel:matrix на стадии деплоя будет автоматически создано нужное количество джоб деплоя, исходя из количества приложений:

Ниже пример реализации деплоя приложений в CI:

.deploy_apps: &deploy_apps   stage: deploy_apps   before_script:     - *base_werf     - REPOSITORY_BUNBLE=${REPOSITORY_NAME}_REPO_BUNDLE     - REPOSITORY_TAG=${REPOSITORY_NAME}_TAG     - REPOSITORY_INGRESS=${REPOSITORY_NAME}_INGRESS     - APP_CURRENT_ACTIVE=$(werf kubectl -n ${WERF_NAMESPACE} get ingress ${!REPOSITORY_INGRESS} --output=custom-columns='SVCs:..service.name' --no-headers --ignore-not-found | awk -F '-' {'print $NF'})     - |       if [[ ${APP_CURRENT_ACTIVE} = ${DEPLOY_VERSION} ]];         then tput setaf 9 && echo "Обнаружена попытка деплоя на активную версию, деплой будет остановлен!!!" && exit 1;       fi;   script:     - werf cr login -u nobody -p ${BUNDLE_PULLER_PASSWORD} ${!REPOSITORY_BUNBLE}     - werf bundle apply       --release $(echo ${!REPOSITORY_BUNBLE} | cut -d / -f4)-${DEPLOY_VERSION}-${CI_ENVIRONMENT_SLUG}       --repo ${!REPOSITORY_BUNBLE}       --tag ${!REPOSITORY_TAG}       --set "werf.deploy_version=${DEPLOY_VERSION}"   when: manual  Deploy to Green:   extends: .deploy_apps   stage: deploy_apps   environment:     name: production   parallel:     matrix:       - REPOSITORY_NAME: *apps_for_matrix   variables:     DEPLOY_VERSION: "green"

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

Заключение

Blue-green помогает надёжно и быстро выкатить обновления своих приложений. Эта стратегия упрощает процесс и даёт возможность тестировать новую версию перед её полноценным запуском. А бандлы особенно полезны для деплоя нескольких приложений одновременно. Это делает управление и обновление более наглядными и централизованными, что особенно важно для больших проектов.

В статье мы рассмотрели деплой приложений в стратегии blue-green с помощью GitLab-CI и модифицировали наш CI для деплоя нескольких приложений из одного репозитория. Это руководство помогает написать CI и развернуть своё приложение из GitLab. Надеюсь, оно будет полезным. 

P. S.

Читайте также в нашем блоге:


ссылка на оригинал статьи https://habr.com/ru/articles/834442/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *