Цель статьи — показать один из возможных подходов для организации гибкого развёртывания dev/test стендов. Показать какие преимущества предоставляет нам IaC подход в сочетании с современными инструментами.
Предыстория
Имеется несколько стендов для разработчиков — devs, tests, production. Новые версии компонентов продукта появляются несколько раз в день.
В результате, имеющиеся стенды заняты, разработчики простаивают ожидая освобождения одного из стендов.
Создание дополнительных статичных стендов решит проблему, но приведёт к их переизбытку во время снижения активности разработчиков и, как следствие, повысит затраты компании на инфраструктуру.
Задача
Дать возможность разработчикам разворачивать и удалять стенды самостоятельно, исходя из текущих потребностей.
Стек
Gitlab CI, Terraform, Bash, любое приватное/публичное облако.
Технические сложности:
-
Terraform state file — “из коробки” у нас нет возможности использовать переменную в значении имени state файла. Нужно что-то придумывать или использовать другой продукт.
-
Subnets — каждое новое окружение должно быть создано в изолированной подсети. Нужно контролировать свободные/занятые подсети, некий аналог DHCP, но для подсетей.
Алгоритм
-
Gitlab CI выполняет pipeline. Связывает все остальные компоненты воедино.
-
Terraform создаёт и удаляет экземпляры виртуальных машин.
-
Configuration manager(CM) — разворачивает сервисы.
-
Bash сценарии подготавливают конфигурационные файлы специфичные для каждого стенда.
Структура репозитория
development-infrastructure/ deploy/ env1/ main.tf backend.tf ansible-vars.json subnets.txt env2/ ... cm/ ... modules/ azure/ main.tf variables.tf scripts/ env.sh subnets.txt .gitlab-ci.yml
-
deploy — содержит специфичные для каждого окружения файлы — файл с переменными для terraform и CM, файл содержащий адрес подсети стенда.
-
cm — в моём случае, тут хранятся Ansible плейбуки для настройки ОС и разворачивания сервисов.
-
modules — модули terraform которые будут получать в качестве параметров имя окружения и адрес подсети
-
scripts — bash сценарии для создания и удаления стендов и их конфигураций
.gitlab-ci.yml:
stages: - create environment - terraform apply - cm - destroy environment .template: variables: ENV: $NAME_ENV when: manual tags: [cloudRunner01] only: refs: - triggers Create environment: stage: create environment extends: .template script: - ./scripts/create_env.sh -e $ENV -a create artifacts: paths: - deploy/${ENV}/backend.tf - deploy/${ENV}/main.tf - deploy/${ENV}/vars.json Create instances: stage: terraform apply extends: .template script: - cd ./deploy/$ENV - terraform init -input=false - terraform validate - terraform plan -input=false -out=tf_plan_$ENV - terraform apply -input=false tf_plan_$ENV Deploy applications: stage: cm extends: .template script: - # мы можем передать имя окружения в качестве параметра нашей CM - # в моём случае, на основе переменной $ENV генерируются сертификаты, - # конфигурируется обратный прокси сервер и т.п. - # также мы можем использовать данные из terraform Destroy instances and environment: stage: destroy environment extends: .template script: - cd ./deploy/$ENV - terraform init -input=false - terraform destroy -auto-approve - ./scripts/delete_env.sh -e $ENV -a delete
Остановимся подробнее на каждом шаге нашего пайплайна:
-
Create environment — на основе имени окружения, полученного из переменно NAME_ENV, создаём уникальные для окружения файлы, после чего помещаем их в наш git репозиторий.
-
Create instances — создаём инстансы(виртуальные машины) и подсети, которые будут использоваться окружением.
-
Deploy applications — разворачиваем наши сервисы с помощью любимого Configuration Manager.
-
Destroy instances and environment — с помощью bash сценария данный шаг удалит наши инстансы, после чего удалит из репозитория каталог с файлами окружения. Освободившаяся подсеть будет возвращена в файл scripts/subnets.txt.
Запуск пайплайна происходит с объявлением переменной NAME_ENV, содержащей имя нового стенда:
Разработчики не имеют доступа к Git репозиторию и могут вносить в него изменения только через запуск pipeline.
modules/base/main.tf:
# лучшие практики и личный опыт настоятельно рекомендуют фиксировать версию провайдера provider "azurerm" { version = "=1.39.0" } … # создаём новую группу ресурсов, это особенность Azure. Имя группы уникально, в этом случае будет удобно использовать имя окружения resource "azurerm_resource_group" "product_group" { name = "${var.env_name}" location = "East Europe" } # создаем сеть resource "azurerm_virtual_network" "vnet" { name = "product-vnet" resource_group_name = azurerm_resource_group.product_group.name location = azurerm_resource_group.product_group.location address_space = [var.vnet_address] } # используем подсеть полученную с помощью bash сценария resource "azurerm_subnet" "subnet" { name = "product-subnet" resource_group_name = azurerm_resource_group.product_group.name virtual_network_name = azurerm_virtual_network.vnet.name address_prefix = var.subnet_address } # теперь можем создать виртуальную машину resource "azurerm_virtual_machine" "product_vm" { name = "main-instance" location = azurerm_resource_group.product_group.location resource_group_name = azurerm_resource_group.product_group.name network_interface_ids = [azurerm_network_interface.main_nic.id] … } # прочие ресурсы и виртуальные машины...
Чтобы сократить листинг я убрал большую часть обязательных, но, в нашем случае, неважных ресурсов.
Для создания ресурсов, имена которых должны быть уникальными, в рамках нашего облачного аккаунта, мы используем имя окружения.
scripts/env.sh:
#!/bin/bash set -eu CIDR="24" DEPLOY_DIR="./deploy" SCRIPT_DIR=$(dirname "$0") usage() { echo "Usage: $0 -e [ENV_NAME] -a [create/delete]" echo " -e: Environment name" echo " -a: Create or delete" echo " -h: Help message" echo "Examples:" echo " $0 -e dev-stand-1 -a create" echo " $0 -e issue-1533 -a delete" } while getopts 'he:a:' opt; do case "${opt}" in e) ENV_NAME=$OPTARG ;; a) ACTION=$OPTARG ;; h) usage; exit 0 ;; *) echo "Unknown parameter"; usage; exit 1;; esac done if [ -z "${ENV_NAME:-}" ] && [ -z "${ACTION:-}" ]; then usage exit 1 fi # приводим имя окружения к нижнему регистру ENV_NAME="${ENV_NAME,,}" git_push() { git add ../"${ENV_NAME}" case ${1:-} in create) git commit -am "${ENV_NAME} environment was created" git push origin HEAD:"$CI_COMMIT_REF_NAME" -o ci.skip echo "Environment ${ENV_NAME} was created.";; delete) git commit -am "${ENV_NAME} environment was deleted" git push origin HEAD:"$CI_COMMIT_REF_NAME" -o ci.skip echo "Environment ${ENV_NAME} was deleted.";; esac } create_env() { # создаём каталог для нового окружения if [ -d "${DEPLOY_DIR}/${ENV_NAME}" ]; then echo "Environment ${ENV_NAME} exists..." exit 0 else mkdir -p ${DEPLOY_DIR}/"${ENV_NAME}" fi # получаем адрес подсети NET=$(sed -e 'a$!d' "${SCRIPT_DIR}"/subnets.txt) sed -i /"$NET"/d "${SCRIPT_DIR}"/subnets.txt echo "$NET" > ${DEPLOY_DIR}/"${ENV_NAME}"/subnets.txt if [ -n "$NET" ] && [ "$NET" != "" ]; then echo "Subnet: $NET" SUBNET="${NET}/${CIDR}" else echo "There are no free subnets..." rm -r "./${DEPLOY_DIR}/${ENV_NAME}" exit 1 fi pushd "${DEPLOY_DIR}/${ENV_NAME}" || exit 1 # Создаем main.tf terraform файл с нужными нам переменными нового окружения cat > main.tf << END module "base" { source = "../../modules/azure" env_name = "${ENV_NAME}" vnet_address = "${SUBNET}" subnet_address = "${SUBNET}" } END # Cоздаём backend.tf terraform файл , в котором указываем имя нового state файла cat > backend.tf << END terraform { backend "azurerm" { storage_account_name = "terraform-user" container_name = "environments" key = "${ENV_NAME}.tfstate" } } END } delete_env() { # удаляем каталог окружения и высвобождаем подсеть if [ -d "${DEPLOY_DIR}/${ENV_NAME}" ]; then NET=$(sed -e '$!d' ./${DEPLOY_DIR}/"${ENV_NAME}"/subnets.txt) echo "Release subnet: ${NET}" echo "$NET" >> ./"${SCRIPT_DIR}"/subnets.txt pushd ./${DEPLOY_DIR}/"${ENV_NAME}" || exit 1 popd || exit 1 rm -r ./${DEPLOY_DIR}/"${ENV_NAME}" else echo "Environment ${ENV_NAME} does not exist..." exit 1 fi } case "${ACTION}" in create) create_env git_push "${ACTION}" ;; delete) delete_env git_push "${ACTION}" ;; *) usage; exit 1;; esac
-
Сценарий
env.sh
принимает два параметра — имя окружения и действие(создание\удаление). -
При создании нового окружения:
-
В каталоге
DEPLOY_DIR
создаётся директория с именем окружения. -
Из файла scripts/subnets.txt мы извлекаем одну свободную подсеть.
-
На основе полученных данных генерируем конфигурационные файлы для Terraform.
-
Для фиксации результата отправляем каталог с файлами окружения в git репозиторий.
-
При удалении — сценарий удаляет каталог с файлами окружения и возвращает освободившуюся подсеть в файл
scripts/subnets.txt
scripts/subnets.txt:
172.28.50.0 172.28.51.0 172.28.52.0 ...
В данном файле мы храним адреса наши подсетей. Размер подсети определяется переменной CIDR в файле scripts/create_env.sh
Результат
-
Мы получили фундамент который позволяет нам развернуть новый стенд путём запуска pipline’a в Gitlab CI.
-
Снизили затраты нашей компании на инфраструктуру.
-
Разработчики не простаивают и могут создавать и удалять стенды когда им это потребуется.
-
Также получили возможность создания виртуальных машин в любом облаке написав новый модуль Terraform и немного модифицировав сценарий созданиях\удаления окружений
-
Можем поиграться с триггерами Gitlab и разворачивать новые стенды из пайплайнов команд разработчиков передавая версии сервисов.
ссылка на оригинал статьи https://habr.com/ru/post/548426/
Добавить комментарий