Автоматическая генерация CI/CD пайплайна для развёртывания инфраструктуры25.11.2023 09:15
Подход «Инфраструктура как код» означает, что инфраструктура создаётся, развёртывается и управляется при помощи кода. Это позволяет автоматизировать процессы, делать их более гибкими и масштабируемыми. Код для инфраструктуры фиксирует конфигурацию, обеспечивает воспроизводимость и упрощает управление настройками. Также благодаря этому подходу возрастает эффективность работы команды, поскольку он позволяет вести совместное развитие инфраструктуры и обеспечивает удобство отслеживания изменений.
Именно этот подход мы используем при нашей работе. Однако в процессе его использования мы столкнулись с проблемой написания пайплайнов для инфраструктуры.
Мы были вынуждены сделать процесс выкатки инфраструктуры максимально точечным из-за использования terragrunt. Каждый его модуль должен выкатываться отдельно, иначе будут получены десятки планов, и понять, что делает каждый из них, будет невозможно. Это означает, что каждому модулю terragrunt нужна отдельная джоба в пайплайне на plan и apply, но для каждого модуля они во многом повторяют друг друга. Подобное постоянное написание одинаковых частей CI/CD пайплайна при добавлении новых баз и бакетов навевало тоску.
Меня зовут Татьяна Мигулаева, я DevOps-инженер в «Магните». Поделюсь тем, как мы создали генератор джоб в GitLab CI/CD и навсегда забыли о ручном написании пайплайнов для развёртывания элементов инфраструктуры.
Что мы имеем
Инфраструктура на базе Yandex Cloud, описанная в terragrunt-файлах, код которых хранится в GitLab.
Периодически приходится создавать новые файлы или изменять существующие — например, чтобы поправить название пользователя в managed базе или создать новый топик в Кафке. После внесения этих изменений их необходимо выкатить в облако, предварительно проверив, что при выкатке ничего не сломается. Для этого используются CI/CD пайплайны со следующим флоу:
При создании merge request должен запускаться terragrunt plan, который проверяет допустимость вносимых в коде изменений и показывает, что произойдёт, если их применить. При мерже этих изменений в main должен повторно запускаться terragrunt plan, а после него — terragrunt apply, который применит эти изменения на инфраструктуре.
Есть два примерных варианта, как это реализовать:
1. Большой статичный пайплайн, который включает в себя все сервисы сразу. Плюсы: не надо ничего менять, для выкатки достаточно изменений в terragrunt-файле. Удобно, когда инфраструктура небольшая и не меняется слишком часто. Минусы: Такой пайплайн может выполняться довольно долго. При этом результатом его работы будут сотни планов и отчетов в гитлабе. По ним будет очень тяжело понять, какие изменения были внесены в код и как они отразятся на инфраструктуре. Также такой пайплайн будет не полностью управляем: не получится нажать кнопку деплоя отдельно для того элемента инфраструктуры, в который были внесены изменения.
2. Для каждого элемента — отдельный шаг в пайплайне. Автоматически запускаются только те шаги, в которые были внесены изменения. terragrunt apply применяется отдельно для каждого элемента инфраструктуры и запускается только вручную. Плюсы: Это удобно, пайплайн выполняется относительно быстро. При этом нет риска случайно выкатить не то. Минусы: Придётся постоянно дописывать новые шаги в CI/CD, высок риск ошибки. Учитывая, что наша инфраструктура динамична и много и часто меняется, делать такие изменения пришлось бы постоянно. И это просто скучно :)
Наше решение
В нашем решении объединены плюсы обоих вариантов. По сути мы изменяем только код инфраструктуры, а наш пайплайн автоматически подстраивается под это. При создании merge request автоматически генерируется и запускается шаг пайплайна, который запускает terragrunt plan только для тех terragrunt-файлов, в которые были внесены изменения.
Например, мы решили создать postgres-базу в облаке для сервиса my-lovely-service. Для этого создали в репозитории в нужной папке terragrunt-файл с конфигурацией бакета. Далее создали merge request с этими изменениями. При этом был автоматически создан и запущен следующий шаг пайплайна.
Есть возможности зайти в пайп и посмотреть вывод команды terragrunt plan. Стоит обратить внимание, что для остальных файлов в репозитории пайплайн не запускался, что значительно ускоряет работу.
После слияния изменений в main запускается пайплайн целиком, но при этом триггерятся только те шаги terragrunt plan, которые запускались на merge request. У нас этот пайплайн выглядит немного страшно.
Но это зависит от количества сервисов и размера инфраструктуры.
Далее в нём можно вручную запустить terragrunt apply для тех элементов инфраструктуры, которые необходимо развернуть.
Реализация
Далее рассмотрим подробнее, как мы это реализовали. Для примера будет взято использование dev-окружения, при необходимости можно добавлять аналогичные шаги соответственно количеству окружений.
Здесь важно обратить внимание на то, как работает include в GitLab CI/CD. Если кратко — он объединяет имеющийся CI/CD файл и те, которые указаны в include. При этом шаги, которые называются одинаково в нескольких файлах, сливаются между собой. Подробнее можно изучить в документации Gitlab: https://docs.gitlab.com/ee/ci/yaml/#include
Предположим, нам надо создать s3 бакет для сервиса my-service в dev-окружении. Для этого в папке dev создаём папку my-service и создаём в ней terragrunt.hcl файл, из которого будет создаваться бакет.
Делаем коммит с этими изменениями в отдельную ветку. В этот момент pre-commit hook запускает следующий скрипт.
generate-mr-jobs.sh
#!/bin/sh
echo '' > .gitlab-ci-mr-jobs.yml
for env in "dev" "uat" "mm-dev" "mm-uat"
do
isMmProject=false
if [[ "$env" =~ ^mm-.*$ ]]
then
isMmProject=true
fi
for root in $(git ls-files -s |grep -oE "\s${env}/.*terragrunt.hcl$" | xargs dirname)
do
prefix=$(echo "$root" | tr / -)
cat >>.gitlab-ci-mr-jobs.yml <
В данном скрипте сначала очищается имеющийся файл с джобами, после чего прописывается ci/cd шаг для каждого terragrunt файла в репозитории. Таким образом, мы получаем файл .gitlab-ci-mr-jobs.yml со списком шагов следующего вида:
В gitlab-ci-terragrunt.yml описаны шаги подключения к облаку и работы с ним. В нашем случае это Yandex Cloud, но может быть и любое другое. В качестве хранилища секретов используется Hashicorp Vault.
Таким образом, этот шаг будет запускаться только на те ресурсы, в которых произошло изменение, то есть на созданный terragrunt файл s3 бакета сервиса my-service. Далее на раннеры ставятся нужные теги, а terragrunt настраивается на использование нужного нам dev окружения в облаке.
Далее, когда всё это прошло успешно, merge request вливается в main ветку. В этот момент запускается скрипт generate-default-jobs.sh
generate-default-jobs.sh
#!/bin/sh
# directory is in the first argument
directory=$1
# Environment is in the second argument
env=$2
# Prefix is the directory name with slashes replaced by dashes
parent_prefix=$(echo "$directory" | tr / -)
cat >.gitlab-ci-${parent_prefix}.yml <>.gitlab-ci-${parent_prefix}.yml <>.gitlab-ci-${parent_prefix}.yml <
Суть этого скрипта, что он генерирует gitlab-ci файл аналогично предыдущему, с той разницей, что появится шаг развёртывания файлов terraform apply. Результат выглядит следующим образом:
Первые шаги пайплайна
Шаг деплоя S3-бакетов
Таким образом, мы создали механизм автоматической генерации пайплайнов, который значительно упрощает работу инженера. При его использовании достаточно писать вручную исключительно terragrunt-файлы, а пайплайны в репозитории будут появляться и запускаться автоматически.