Как мы реализовали непрерывную доставку обновлений на платформу заказчика

    Мы в 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. И обязательно поделимся этим опытом на Хабре.
    True Engineering
    76,00
    Специалисты по цифровой трансформации бизнеса
    Поделиться публикацией

    Комментарии 7

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

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

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

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

        +1
        Хорошо когда у тебя моно-репа и никаких внешних зависимостей. Но в жизни так бывает очень очень редко.
          +2
          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?

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

            Релиз :)
            Изменения собираются в очередную RC ветку, которая проходит все необходимые предрелизные процедуры и потом эта ветка мержится в релизную ветку.


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

            На данный момент около 20


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

            Docker-образы в кубернетисе мы не удаляем, но да, всё приложение разворачивается из новых версий docker-образов.


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

            У нас разработка в feature-ветках, и на Merge Request обязательно запускается билд с прогоном всех тестов, так что ошибки изолируются.


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

            OpenJDK 8


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

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


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

            Это среда UAT — User Acceptance Testing. То есть та среда, где конечные бизнес-пользователи или владельцы продукта могли проверить главные бизнес-фичи.


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

            С какими именно? В этих статьях довольно большой обзор разных проблем.


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

            Да

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

          Самое читаемое