Всем привет! На связи Юрий Шахов, 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
Предположим, что у нас есть приложение и мы хотим его задеплоить. Делать это будем в два этапа:
-
Деплой самого приложения (
deploy_app), например Deployment и Service. -
Смена версии: деплой 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"
Что мы в итоге сделали:
-
Скорректировали Helm-шаблоны для Deployment, Service и Ingress, добавив к ним «цвет» нашей версии.
-
Написали 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/
Добавить комментарий