Статья посвящена так называемым review-окружениям, реализуемым в рамках кластеров Kubernetes. Ранее эта тема затрагивалась, например, в нашем докладе «Лучшие практики CI/CD с Kubernetes и GitLab», но не была там основной темой, поэтому раскрывалась не во всех деталях. Попробую восполнить этот пробел, рассказав, для чего нужны и/или обычно используют review-окружения, как сделать pipeline c review-окружением в GitLab CI/CD, какие могут быть потенциальные проблемы и способы их решения.
Review-окружения. Что это и зачем нужны?
Общая теория
Начну с примечания, что есть разные переводы самого понятия, но здесь и далее в статье используется термин «окружение» (от англ. environment). Под ним понимается некоторая «среда» или «контур», в соответствии с которым настроено и непосредственно в котором работает приложение. Обычно для каждого окружения используются свои переменные (они так и называются — переменные окружения), которые определяют для запущенного приложения, например, параметры подключения к БД и т.п.
Review-окружения, они же динамические или preview-окружения, как правило, применяют для программного и визуального тестирования нового функционала или его демонстрации. Это очень удобно для команд разработки и тестирования. Разрабатывая в своей Git-ветке какую-то фичу, разработчики могут оперативно деплоить код в кластер Kubernetes. При этом у них есть отдельное окружение, которое используется для проверки корректной работы кода или для других целей. А команда тестирования может оперативно и удобно тестировать новый функционал…
Приведем простой и довольно типичный пример: небольшая команда разработки, окружения stage и production.
Работая над новым функционалом (и не только), разработчик делает отдельную ветку, вносит необходимые правки и хочет быстро выкатить это в существующий кластер. Но при этом ему важно не пересекаться с командой тестировщиков и своими коллегами-разработчиками: ведь они уже проводят отладку и тестирование других правок в развернутых окружениях. Здесь ему и поможет использование review-окружений.
В зависимости от того, как устроен workflow конкретной команды/компании, у каждого разработчика может быть одно или несколько «своих» review-окружений или окружения, создаваемые из конкретных веток. Если не вдаваться в детали (о них ниже), то типичный рабочий процесс получится примерно таким:
Разработчик вносит правки в своей ветке локально и делает push в репозиторий.
Далее — либо автоматически, либо вручную — код готового приложения деплоится в кластер Kubernetes.
При этом в Git-репозитории есть заранее заготовленный DevOps-командой Helm-чарт. Он выкатывает в кластер все необходимое ПО, от которого зависит работа приложения: СУБД MySQL, Redis, Ingress, Secret’ы с доступами к каким-то внешним сервисам (S3-хранилище и т.п.), — а также само приложение или его компоненты.
Также в review-окружениях запускаются миграции для БД — различные job’ы, чтобы обеспечить работу с актуальной копией базы и приблизить review-окружение к «боевому».
Получается, что по своей сути review-окружение — это более удобный и гибкий аналог того, как разработчик запускал бы код локально, только при этом не нужно делать множество дополнительных манипуляций:
устанавливать кучу зависимого ПО на свою рабочую машину или поднимать Docker-контейнеры со всем ПО, от которого зависит приложение;
городить какие-то скрипты для запуска;
обеспечивать наличие всех переменных окружения для приложения…
Всё это делается силами DevOps-инженера, который реализует сборку и деплой приложения (в условном Helm-чарте), а это, как правило, довольно удобно и универсально.
Иными словами, review-окружение дает возможность тестировать свой код в условиях, близких или приближенных к production-окружению, не захламляя при этом свою машину.
Подводя итог: как правило, review-окружение — это «маленькое», условно однопользовательское окружение, урезанное по ресурсам. Оно быстро разворачивается в кластер, недолго живет и удаляется за ненадобностью. Для таких окружений очень важна воспроизводимость. Это очень важный момент, т.к. мы должны быть уверены в том, что код и образ, который проходил все этапы (отладку, тестирование, оптимизацию…), будет задеплоен в production в точно таком виде, а результат работы приложения будет предсказуем.
Для достижения максимальной воспроизводимости мы у себя используем утилиту werf. Если приложение и его зависимости выкатываются в кластер со всеми настройками, переменными окружения и т.п., а Docker-образы собираются и тегируются с использованием content-based-тегов (актуально при использовании werf 1.1 и выше), то нужная воспроизводимость достигается.
NB. К слову, чтобы быть уверенными в результате работы приложения еще больше, в werf версии 1.2 гарантии воспроизводимости расширены благодаря фиче под названием гитерминизм (подробнее см. в документации).
«Неполные» review-окружения
Зачастую используется неполное review-окружение. Под ним подразумевается такое окружение, которое урезано не только по ресурсам (CPU, память, диск) относительно других, но и по функциональности (т.е. инфраструктурным компонентам).
Пример из жизни. Разработчик дорабатывает ту часть приложения, которая взаимодействует только с MySQL, и ему совершенно не важна часть, задействующая Redis и другую инфраструктуру. Тогда достаточно задеплоить в кластер MySQL и само приложение, а также (скорее всего) Ingress для внешнего взаимодействия по HTTP/HTTPS.
Таким образом, с помощью неполного окружения разработчик может быстро задеплоить в кластер приложение, убедиться в работоспособности кода и при этом не занимать большое кол-во ресурсов кластера. Это позволяет экономить вполне ощутимые суммы за оплату серверов, не ущемляя команду разработки в скорости и удобстве разработки. Стоит обратить внимание, что такой подход уместен только в случаях, когда совершенно точно ясно, что недостающее в окружении ПО не окажет влияния на работу.
Поскольку review-окружения обычно single user (к задеплоенному приложению обращается один или несколько разработчиков), то на них не требуется выделять большое количество ресурсов: реально необходимое может в десятки раз(!) отличаться от production и даже stage-окружений, а разработчик счастлив.
Практика. Реализуем review-окружения в GitLab
Разберем конкретный пример workflow и работу с review-окружениями в системе GitLab, которая у нас принята за стандарт, но не исключает использование других CI/CD систем.
Примеры будут приведены с использованием нашего инструмента werf, который не является обязательным в этой схеме, но упрощает реализацию и является стандартом для нас.
Создание репозитория и pipeline с review-окружением
Pipeline в GitLab описывается в файле .gitlab-ci.yml
. Создаем репозиторий в нужном проекте в GitLab и описываем наш pipeline в .gitlab-ci.yml
в корне этого репозитория. Вот содержимое файла с подробными комментариями:
# Указываем "глобальные" для пайплайна переменные.
# В данном случае это только версия утилиты werf, которую используем для сборки необходимых образов, деплоя всего окружения и его удаления из кластера
variables:
WERF_VERSION: "1.2 beta"
# Описываем стадии, на которые логически разделен pipeline:
stages:
# стадия сборки образов
- build
# деплой/запуск и остановка review-окружения
- review
before_script:
# проверяем наличие утилиты multiwerf и, если успешно, то просим её «подгрузить» нужную версию werf
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
# генерируем и добавляем переменные окружения, которые необходимы для работы werf с GitLab
- type werf && source <(werf ci-env gitlab)
Build:
stage: build
# в секции script — список команд, выполняемых на gitlab-runner при запуске стадии
script:
# запускаем сборку образов, описанных в werf.yaml
- werf build
# исключаем запуск стадии из планировщиков
except:
- schedules
# указываем тег gitlab-runner’а(ов), на которых может быть запущена стадия
tags:
- werf
# описываем шаблон деплоя review-окружения
Deploy Review:
stage: review
script:
# вызываем werf converge для деплоя чарта в кластер. Опция --skip-build, пропускает сборку, т.к. стадия сборки образов у нас вынесена отдельно
- werf converge
--skip-build
# указываем что этот шаг, зависит от шага Build, т.е. если на этапе сборки образов что-то пошло не так, шаг с деплоем будет недоступен
needs:
- Build
tags:
- werf
# задаем доп. переменные окружения
environment:
name: review/${CI_COMMIT_BRANCH}/${CI_COMMIT_REF_SLUG}
# указываем, какой шаг вызывается для остановки/удаления review-окружения
on_stop: Stop Review
# указываем период, через который GitLab автоматически удалит review-окружение
auto_stop_in: 1 day
# шаг может быть вызван только из какой-то ветки
only:
- branches
# … и запущен вручную
when: manual
# исключаем запуск планировщиком
except:
- schedules
# описываем остановку/удаление review-окружения:
Stop Review:
# стадия, к которой относится шаг
stage: review
# вызываем werf dismiss с указанием окружения, namespace и именем helm-release для удаления,
# а также с опцией для удаления не только ресурсов Helm release, но и namespace, куда он был задеплоен
script:
- werf dismiss --with-namespace
when: manual
environment:
name: review/${CI_COMMIT_BRANCH}/${CI_COMMIT_REF_SLUG}
action: stop
only:
- branches
tags:
- werf
except:
- schedules
Готово! Так мы создали pipeline, в котором запускается процесс сборки образов, после чего, если сборка прошла успешно, будут доступны deploy в кластер и возможность остановки/удаления всего задеплоенного Helm-релиза.
А вот как описанный выше pipeline будет представлен в GitLab визуально:
Потенциальные проблемы и способы решения
За время использования review-окружений мы столкнулись и с рядом сложностей. Вот главные из них.
1. Чрезмерное потребление ресурсов
Поскольку за сутки разработчики могут поработать с большим количеством веток, сделать множество merge requests и выкатить много review-окружений, становится обыденной проблема чрезмерного использования ресурсов.
Текущая версия пайплайна, выше реализованного в GitLab, предусматривает удаление окружений, запущенных более одного дня, и ручное удаление/остановку.
NB. Стоит отметить, что из-за ограниченности ресурсов GitLab фоновый worker, который останавливает окружения автоматически, запускается только раз в час. Это означает, что ваше review-окружение может не остановиться сразу по истечении времени, которое указано в auto_stop_in
. Остановка сработает только когда «worker-часовой» обнаружит, что у вашего окружения истек срок действия.
Однако этого может оказаться недостаточно. Ниже представлен вариант ограничения количества review-окружений в кластере, с которым мы всегда будем знать, сколько их может быть запущено и сколько (максимум) ресурсов на это тратится.
Итак, пример .gitlab-ci.yml
с лимитом на максимальное число review-окружений:
variables:
WERF_VERSION: "1.2 beta"
Deploy to Review:
before_script:
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
- type werf && source <(werf ci-env gitlab)
# максимальное кол-во review окружений
- export MAX_REVIEW=${MAX_REVIEW:-3}
# берем имя проекта из werf.yaml
- "export PROJNAME=$(cat werf.yaml | grep project: | awk '{print $2}')"
# проверяем сколько review-окружений задеплоено
- export DEPLOYED_REVIEW=$(helm ls -A -a | grep -F "$PROJNAME-review" | sort | uniq | grep -cv "$PROJNAME-$CI_ENVIRONMENT_SLUG") || export DEPLOYED_REVIEW=”0”
- if (( "$DEPLOYED_REVIEW" >= "$MAX_REVIEW" )) && [[ "$CI_ENVIRONMENT_SLUG" =~ "review" ]]; then ( echo "Максимальное кол-во ревью окружений $MAX_REVIEW достигнуто, необходимо остановить неиспользуемые" && exit 1; ); fi
script:
- werf converge
--skip-build
- echo "DYNAMIC_ENVIRONMENT_URL=http://${PROJNAME}-${CI_ENVIRONMENT_SLUG}.kube.some.domain" >> deploy.env
artifacts:
reports:
dotenv: deploy.env
needs:
- Build
tags:
- werf
stage: review
allow_failure: false
environment:
name: review/${CI_COMMIT_BRANCH}/${CI_COMMIT_REF_SLUG}
url: $DYNAMIC_ENVIRONMENT_URL
on_stop: Stop Review
auto_stop_in: 1 day
only:
- branches
except:
- develop
- master
- schedules
when: manual
Stop Review:
stage: review
script:
- werf dismiss --with-namespace
environment:
name: review/${CI_COMMIT_BRANCH}/${CI_COMMIT_REF_SLUG}
when: manual
except:
- develop
- master
- schedules
tags:
- werf
stages:
- build
- review
Такой подход позволит заранее планировать необходимое количество серверных мощностей, делает прогнозируемой оплату за инфраструктуру и не позволяет «захламить» кластер ненужными релизами.
В дополнение к нему неплохо бы иметь выделенные узлы под различные не-production-окружения. Во-первых, это позволит не влиять на production при сбоях/чрезмерном потреблении ресурсов, а во-вторых, гарантирует на уровне железа, что тестовые/review/stage-окружения не будут потреблять слишком много ресурсов, что дает еще большую прогнозируемость затрат на инфраструктуру.
2. Лимиты Let’s Encrypt
Второй и не самой очевидной проблемой может стать использование LE-сертификатов для доменов review-окружений. В зависимости от рабочих процессов review-окружений может оказаться огромное количество. В каждом из них есть домены/поддомены, для которых всякий раз при выкате заказываются SSL-сертификаты… И вот в какой-то момент можно получить неприятную новость о том, что превышен тот или иной лимит.
Самый простой вариант решения — это wildcard-сертификат для всех поддоменов. Он заказывается отдельно от review-окружений и каким-то автоматизированным образом* копируется в namespace созданного review-окружения.
* В нашей Kubernetes-платформе (Deckhouse) для этого есть secret-copier. Его успех легко повторить в нужном вам виде с помощью shell-operator.
То же самое применимо к доменным или wildcard-сертификатам, выданным платными сертификационными центрами. Вы один раз добавляете в кластер полученные сертификаты и используете их в каждом review-окружении, не выпуская новые (и не забывая обновлять до окончания периода действия).
3. Pull-лимиты Docker Hub
Относительно недавно стала возникать неприятная порой ситуация, когда при большом количестве деплоев в течение дня можно упереться в лимит на pull образов с Docker Hub и завалить деплой. Причина всем известна:
The rate limits of 100 container image requests per six hours for anonymous usage, and 200 container image requests per six hours for free Docker accounts are now in effect. Image requests exceeding these limits will be denied until the six hour window elapses.
Чтобы не столкнуться с этой проблемой (или минимизировать её), достаточно:
Следить за тем, чтобы в описании контейнера без необходимости не было указано
imagePullPolicy: Always
(впрочем, за этим параметром стоит следить и по ряду других причин).Кэшировать требующиеся образы в свой registry и использовать для контейнеров уже их.
4. Multipipeline
Multipipeline — это про построение пайплайна с использованием зависимостей или связей с другим репозиториями. Когда это может быть нужно? Вот простой пример: вы выкатываете review-окружение с приложением, но приложению, помимо прочего, нужны еще и инфраструктурные сервисы вроде Redis и MySQL (их деплой описан в отдельных репозиториях) с возможностью деплоить в разные namespace и окружения.
Что будет, если не использовать multipipeline? После выката review-окружения или перед ним потребуется вручную «пойти» в репозитории инфраструктурных компонентов и задеплоить их в нужный namespace и окружение. Кроме того, зависимость приложения от других компонентов не всегда очевидна, а это сильно усложняет процесс деплоя приложения.
С multipipeline можно вызывать (trigger’ить) job’ы из других репозиториев, т.е. можно написать пайплайн, который при выкате приложения приведет к деплою компонентов, от которых зависит это приложение. Не нужно «бегать» по соседним репозиториям и вспоминать зависимости приложения — деплой происходит одним нажатием или автоматически.
Multipipeline в GitLab стал бесплатным начиная с версии 12.8, а документация по нему доступна здесь. Также есть полезное видео, где рассказывают о базовых возможностях и вариантах применения. И не забудьте обратить внимание на существующие ограничения.
Заключение
Подводя итог, можно с уверенностью сказать, что review-окружения в современной, динамичной разработке являются практически неотъемлемой частью. Именно поэтому большинство наших клиентов этот тип окружений использует с самого начала. А те немногие проекты, где review-окружений нет, либо действительно не испытывают в них надобности, либо еще не осознали всю пользу такого подхода.
Надеюсь, эта статья достаточно проясняет преимущества и вероятные проблемы/специфику review-окружений, чтобы возможность их использования была как минимум рассмотрена.
P.S.
Тот факт, что мы рассмотрели работу с review-окружениями конкретно в GitLab, вовсе не говорит о невозможности их реализации и в других CI-системах.
ДОБАВЛЕНО (8 ноября): Пример альтернативного взгляда на реализацию review-окружений — недавняя статья коллег из Typeable.
P.P.S.
Читайте также в нашем блоге: