Мы в True Engineering настроили процесс непрерывной доставки обновлений на сервера заказчика и хотим поделиться этим опытом.
Для начала мы разработали онлайн систему для заказчика и развернули её в собственном кластере Kubernetes. Теперь наше высоконагруженное решение переехало на платформу заказчика, для чего мы настроили полностью автоматический процесс Continuous Deployment. Благодаря этому, мы ускорили time-to-market – доставку изменений в продуктовую среду.
В этой статье мы расскажем обо всех этапах процесса Continuous Deployment (CD) или доставки обновлений на платформу заказчика:
В процессе поделимся деталями настройки.
Continuous Deployment начинается с того, что разработчик выкладывает изменения в релизную ветку нашего Git-репозитория.
Наше приложение работает на базе микросервисной архитектуры и все его компоненты хранятся в одном репозитории. Благодаря этому собираются и устанавливаются все микросервисы, даже если изменился один из них.
Мы организовали работу через один репозиторий по нескольким причинам:
Сделанные изменения автоматически синхронизируются с Git-репозиторием заказчика. Там настроена сборка приложения, которая запускается после обновления ветки, и деплоймент в прод. Оба процесса происходят в их окружении из Git-репозитория.
Мы не можем работать с репозиторием заказчика напрямую, поскольку нам нужны собственные среды для разработки и тестирования. Мы используем для этих целей свой Git-репозиторий — он синхронизирован с их Git-репозиторием. Как только разработчик выкладывает изменения в соответствующую ветку нашего репозитория, GitLab сразу же отправляет эти изменения заказчику.
После этого нужно сделать сборку. Она состоит из нескольких этапов: сборки бекенда и фронтенда, тестирования и доставки в прод.
Сборка бекенда и фронтенда — это две параллельные задачи, которые выполняются в системе GitLab Runner. Её конфигурация исходной сборки лежит в этом же репозитории.
Туториал для написания YAML-скрипта для сборки в GitLab.
GitLab Runner забирает код из нужного репозитория, командой сборки Java-приложения собрает и отправляет его в Docker registry. Здесь мы собираем бекенд и фронтенд, получаем Docker-образы, которые складываем в репозиторий на стороне заказчика. Для управления Doker-образами используем плагин Gradle.
Мы синхронизируем версии наших образов с версией релиза, который будет выложен в Docker. Для гладкой работы мы внесли несколько настроек:
1. Между тестовым окружением и продуктовым контейнеры не пересобираются. Мы сделали параметризации, чтобы один и тот же контейнер мог без пересборки работать со всеми настройками, переменными окружениями и сервисами как в тестовой среде, так и на проде.
2. Для обновления приложения через Helm необходимо указать его версию. У нас сборка бекенда, фронтенда и обновление приложения – это три разные задачи, поэтому важно использовать везде одну и ту же версию приложения. Для этой задачи мы используем данные из истории Git, поскольку у нас конфигурация K8S кластера и приложения находятся в одном Git-репозитории.
Версию приложения мы получаем из результатов выполнения команды
Следующим этапом в этом скрипте сборки выполняется автоматическое обновление кластера K8S. Это происходит при условии, что все приложение собралось и все артефакты опубликованы в Docker Registry. После этого запускается обновление тестового окружения.
Обновление кластера запускается с помощью Helm Update. Если в результате что-то пошло не по плану, то Helm автоматически и самостоятельно откатит все свои изменения. Его работу не нужно контролировать.
Мы поставляем вместе со сборкой конфигурацию кластера K8S. Поэтому следующим шагом обновляется она: configMaps, deployments, services, secrets и любые другие конфигурации K8S, которые мы изменили.
После этого Helm запускает RollOut обновление самого приложения в тестовой среде. До того как приложение будет развернуто на проде. Это сделано для того, чтобы пользователи вручную проверили бизнес-фичи, которые мы выложили в тестовое окружение.
Чтобы развернуть обновление в продуктовое окружение, остаётся лишь нажать одну кнопку в GitLab — и контейнеры сразу доставляются в продуктовую среду.
Одно и то же приложение может без пересборки работать в разных окружениях — тестовом и проде. Мы используем одни и те же артефакты, не меняя ничего в приложении, а параметры задаём извне.
Гибкая параметризация настроек приложения зависит от того окружения, в котором это приложение будет выполняться. Мы вынесли все настройки окружений вовне: всё параметризуется через конфигурацию K8S и параметры Helm. Когда Helm разворачивает сборку в тестовое окружение, к ней применяются тестовые параметры, а продуктовом окружении — продуктовые параметры.
Самым сложным было параметризовать все используемые сервисы и переменные, которые зависят от окружения, и перевести их в переменные окружения и описание-конфигурации параметров окружения для Helm.
В параметрах приложения используются переменные окружения. Их значения задаются в контейнерах при помощи K8S configmap, который шаблонизируется при помощи Go-шаблонов. К примеру, задание переменной окружения на звание домена можно сделать так:
.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. И обязательно поделимся этим опытом на Хабре.
Для начала мы разработали онлайн систему для заказчика и развернули её в собственном кластере Kubernetes. Теперь наше высоконагруженное решение переехало на платформу заказчика, для чего мы настроили полностью автоматический процесс Continuous Deployment. Благодаря этому, мы ускорили time-to-market – доставку изменений в продуктовую среду.
В этой статье мы расскажем обо всех этапах процесса Continuous Deployment (CD) или доставки обновлений на платформу заказчика:
- как стартует этот процесс,
- синхронизация с Git-репозиторием заказчика,
- сборка бекенда и фронтенда,
- автоматическое развертывание приложения в тестовой среде,
- автоматическое развертывание на 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)
crazylh
12.04.2019 08:42Хорошо когда у тебя моно-репа и никаких внешних зависимостей. Но в жизни так бывает очень очень редко.
chemtech
15.04.2019 19:45Continuous 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?
Статья интересная. Спасибо за статью.
chemtech
Добавьте пожалуйста больше технических подробностей
true_engineering Автор
Вы могли бы уточнить, каких подробностей не хватило в статье?
DarkHost
Вы в volumes храните данные или в контейнеры собираете? Если в volumes, то как у вас организовано версифицирование и rollout/rollback?
true_engineering Автор
Мы не храним ничего в контейнерах, все микросервисы у нас stateless.