Мы в True Engineering настроили процесс непрерывной доставки обновлений на сервера заказчика и хотим поделиться этим опытом.

Для начала мы разработали онлайн систему для заказчика и развернули её в собственном кластере Kubernetes. Теперь наше высоконагруженное решение переехало на платформу заказчика, для чего мы настроили полностью автоматический процесс Continuous Deployment. Благодаря этому, мы ускорили time-to-market – доставку изменений в продуктовую среду.

В этой статье мы расскажем обо всех этапах процесса Continuous Deployment (CD) или доставки обновлений на платформу заказчика:

  1. как стартует этот процесс,
  2. синхронизация с Git-репозиторием заказчика,
  3. сборка бекенда и фронтенда,
  4. автоматическое развертывание приложения в тестовой среде,
  5. автоматическое развертывание на Prod.

В процессе поделимся деталями настройки.



1. Старт CD


Continuous Deployment начинается с того, что разработчик выкладывает изменения в релизную ветку нашего Git-репозитория.

Наше приложение работает на базе микросервисной архитектуры и все его компоненты хранятся в одном репозитории. Благодаря этому собираются и устанавливаются все микросервисы, даже если изменился один из них.

Мы организовали работу через один репозиторий по нескольким причинам:

  • Удобство разработки — приложение активно развивается, поэтому можно работать сразу со всем кодом.
  • Единый пайплайн CI/CD, который гарантирует, что приложение как единая система проходит все тесты и доставляется в prod-окружение заказчика.
  • Исключаем путаницу в версиях — нам не приходится хранить карту версий микросервисов и описывать для каждого микросервиса свою конфигурацию в скриптах Helm.

2. Синхронизация с Git-репозиторием исходного кода заказчика


Сделанные изменения автоматически синхронизируются с Git-репозиторием заказчика. Там настроена сборка приложения, которая запускается после обновления ветки, и деплоймент в прод. Оба процесса происходят в их окружении из Git-репозитория.

Мы не можем работать с репозиторием заказчика напрямую, поскольку нам нужны собственные среды для разработки и тестирования. Мы используем для этих целей свой Git-репозиторий — он синхронизирован с их Git-репозиторием. Как только разработчик выкладывает изменения в соответствующую ветку нашего репозитория, GitLab сразу же отправляет эти изменения заказчику.



После этого нужно сделать сборку. Она состоит из нескольких этапов: сборки бекенда и фронтенда, тестирования и доставки в прод.

3. Сборка бекенда и фронтенда


Сборка бекенда и фронтенда — это две параллельные задачи, которые выполняются в системе GitLab Runner. Её конфигурация исходной сборки лежит в этом же репозитории.

Туториал для написания YAML-скрипта для сборки в GitLab.

GitLab Runner забирает код из нужного репозитория, командой сборки Java-приложения собрает и отправляет его в Docker registry. Здесь мы собираем бекенд и фронтенд, получаем Docker-образы, которые складываем в репозиторий на стороне заказчика. Для управления Doker-образами используем плагин Gradle.

Мы синхронизируем версии наших образов с версией релиза, который будет выложен в Docker. Для гладкой работы мы внесли несколько настроек:

1. Между тестовым окружением и продуктовым контейнеры не пересобираются. Мы сделали параметризации, чтобы один и тот же контейнер мог без пересборки работать со всеми настройками, переменными окружениями и сервисами как в тестовой среде, так и на проде.

2. Для обновления приложения через Helm необходимо указать его версию. У нас сборка бекенда, фронтенда и обновление приложения – это три разные задачи, поэтому важно использовать везде одну и ту же версию приложения. Для этой задачи мы используем данные из истории Git, поскольку у нас конфигурация K8S кластера и приложения находятся в одном Git-репозитории.

Версию приложения мы получаем из результатов выполнения команды
git describe --tags --abbrev=7.

4. Автоматическое развертывание всех изменений в тестовой среде (UAT)


Следующим этапом в этом скрипте сборки выполняется автоматическое обновление кластера K8S. Это происходит при условии, что все приложение собралось и все артефакты опубликованы в Docker Registry. После этого запускается обновление тестового окружения.

Обновление кластера запускается с помощью Helm Update. Если в результате что-то пошло не по плану, то Helm автоматически и самостоятельно откатит все свои изменения. Его работу не нужно контролировать.

Мы поставляем вместе со сборкой конфигурацию кластера K8S. Поэтому следующим шагом обновляется она: configMaps, deployments, services, secrets и любые другие конфигурации K8S, которые мы изменили.

После этого Helm запускает RollOut обновление самого приложения в тестовой среде. До того как приложение будет развернуто на проде. Это сделано для того, чтобы пользователи вручную проверили бизнес-фичи, которые мы выложили в тестовое окружение.

5. Автоматическое развертывание всех изменений на Prod


Чтобы развернуть обновление в продуктовое окружение, остаётся лишь нажать одну кнопку в GitLab — и контейнеры сразу доставляются в продуктовую среду.

Одно и то же приложение может без пересборки работать в разных окружениях — тестовом и проде. Мы используем одни и те же артефакты, не меняя ничего в приложении, а параметры задаём извне.

Гибкая параметризация настроек приложения зависит от того окружения, в котором это приложение будет выполняться. Мы вынесли все настройки окружений вовне: всё параметризуется через конфигурацию K8S и параметры Helm. Когда Helm разворачивает сборку в тестовое окружение, к ней применяются тестовые параметры, а продуктовом окружении — продуктовые параметры.

Самым сложным было параметризовать все используемые сервисы и переменные, которые зависят от окружения, и перевести их в переменные окружения и описание-конфигурации параметров окружения для Helm.

В параметрах приложения используются переменные окружения. Их значения задаются в контейнерах при помощи K8S configmap, который шаблонизируется при помощи Go-шаблонов. К примеру, задание переменной окружения на звание домена можно сделать так:

APP_EXTERNAL_DOMAIN: {{ (pluck .Values.global.env .Values.app.properties.app_external_domain | first) }}

.Values.global.env – в этой переменной хранится название окружения (prod, stage, UAT).
.Values.app.properties.app_external_domain – в этой переменной мы в файле .Values.yaml задаем нужный домен

При обновлении приложения Helm выполняет создание из шаблонов файла configmap.yaml и заполняет значение APP_EXTERNAL_DOMAIN нужным значением в зависимости от окружения, в котором стартует обновление приложения. Эта переменная проставляется уже в контейнере. Доступ к ней есть из приложения, соответственно, в каждом окружении приложения будет разное значение этой переменной.

Относительно недавное в Spring Cloud появилась поддержка K8S, в том числе работа с configMaps: Spring Cloud Kubernetes. Пока проект активно развивается и изменяется кардинально, мы не можем использовать его в проде. Но активно мониторим его состояние и используем его в DEV конфигурациях. Как только он стабилизируется — будем переключаться с использования переменных окружения на него.

Итого


Итак, Continuous Deployment настроен и работает. Все обновления происходят по одному нажатию клавиши. Доставка изменений в продуктовую среду — автоматическая. И, что важно, обновления не останавливают работы системы.



Планы на будущее: автоматическая миграция базы


Мы задумались о об апгрейде базы и возможности эти изменения откатить. Ведь одновременно работают две разные версии приложения: старая работает, а новая поднимается. И старую мы выключим только когда убедимся, что новая версия — работает. Миграция базы должна позволять работать с обеими версиями приложения.

Поэтому мы не можем просто так изменить название колонки или другие данные. Но мы можем создать новую колонку, скопировать в нее данные из старой колонки и написать триггеры, которые будут при обновлении данных будут одновременно копировать и обновлять их в другой колонке. И после успешного деплоя новой версии приложения, по прошествии периода post launch support, мы сможем удалить старую колонку и ставший ненужным триггер.

Если новая версия приложения работает некорректно, мы можем откатиться до прежней версии, в том числе и прежней версии базы. Словом, наши изменения позволят работать одновременно с несколькими версиями приложения.

Мы планируем сделать автоматизацию миграции базы через K8S job, встроив её в процесс CD. И обязательно поделимся этим опытом на Хабре.

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


  1. chemtech
    12.04.2019 08:33

    Добавьте пожалуйста больше технических подробностей


    1. true_engineering Автор
      15.04.2019 08:51

      Вы могли бы уточнить, каких подробностей не хватило в статье?


      1. DarkHost
        15.04.2019 12:35

        Вы в volumes храните данные или в контейнеры собираете? Если в volumes, то как у вас организовано версифицирование и rollout/rollback?


        1. true_engineering Автор
          15.04.2019 12:49

          Мы не храним ничего в контейнерах, все микросервисы у нас stateless.


  1. crazylh
    12.04.2019 08:42

    Хорошо когда у тебя моно-репа и никаких внешних зависимостей. Но в жизни так бывает очень очень редко.


  1. chemtech
    15.04.2019 19:45

    Continuous Deployment начинается с того, что разработчик выкладывает изменения в релизную ветку нашего Git-репозитория.

    А что именно он выкладывает в релизную ветку? релизная ветка имеет название версии вашего продукта?

    Наше приложение работает на базе микросервисной архитектуры и все его компоненты хранятся в одном репозитории.

    Сколько у вас компонентов?

    Благодаря этому собираются и устанавливаются все микросервисы, даже если изменился один из них.

    То есть при каждом релизе вы деплоите (удаляете и снова создаете) все докер образы в кубере?

    Единый пайплайн CI/CD, который гарантирует, что приложение как единая система проходит все тесты

    Аффектит ли ошибка в компоненте системы на другие системы? Я так понимаю другие разработчики ждут пока все у кого ошибка в тестах их не исправят. Верно? Хотя наверное у вас Feature branch и ошибки исправляются в тестах в Feature branch.

    GitLab Runner забирает код из нужного репозитория, командой сборки Java-приложения собрает и отправляет его в Docker registry.

    Какая версия Java? Какой вендор Java вы используете? Какой средний размер образа docker?

    Версию приложения мы получаем из результатов выполнения команды
    git describe --tags --abbrev=7.

    Почему 7?
    git describe --tags --abbrev=7 — получается какая-то расширенная версия…

    2. Синхронизация с Git-репозиторием исходного кода заказчика
    4. Автоматическое развертывание всех изменений в тестовой среде (UAT)

    Сначала вы синхронизируетесь с Git-репозиторием заказчика, а потом вы развертываете в тесте? Сначала обычно тестируют в тесте, а потом отправляют изменения заказчику.

    Если в результате что-то пошло не по плану, то Helm автоматически и самостоятельно откатит все свои изменения. Его работу не нужно контролировать.

    Как вы справляетесь с проблемами helm?
    habr.com/ru/company/southbridge/blog/429340
    habr.com/ru/company/flant/blog/438814

    Чтобы развернуть обновление в продуктовое окружение, остаётся лишь нажать одну кнопку в GitLab — и контейнеры сразу доставляются в продуктовую среду.

    Эта кнопка в продуктовом GitLab?

    Статья интересная. Спасибо за статью.