Стенды разработки без очередей и простоев

от автора

Цель статьи — показать один из возможных подходов для организации гибкого развёртывания dev/test стендов. Показать какие преимущества предоставляет нам IaC подход в сочетании с современными инструментами.


Предыстория

Имеется несколько стендов для разработчиков — devs, tests, production. Новые версии компонентов продукта появляются несколько раз в день. 

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

Создание дополнительных статичных стендов решит проблему, но приведёт к их переизбытку во время снижения активности разработчиков и, как следствие, повысит затраты компании на инфраструктуру.

Задача

Дать возможность разработчикам разворачивать и удалять стенды самостоятельно, исходя из текущих потребностей.

Стек

Gitlab CI, Terraform, Bash, любое приватное/публичное облако.

Технические сложности: 

  1. Terraform state file — “из коробки” у нас нет возможности использовать переменную в значении имени state файла. Нужно что-то придумывать или использовать другой продукт.

  2. Subnets — каждое новое окружение должно быть создано в изолированной подсети. Нужно контролировать свободные/занятые подсети, некий аналог DHCP, но для подсетей.

Алгоритм

  1. Gitlab CI выполняет pipeline. Связывает все остальные компоненты воедино.

  2. Terraform создаёт и удаляет экземпляры виртуальных машин.

  3. Configuration manager(CM) — разворачивает сервисы.

  4. 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  
  1. Сценарий env.shпринимает два параметра — имя окружения и действие(создание\удаление).

  2. При создании нового окружения:

  3. В каталоге DEPLOY_DIRсоздаётся директория с именем окружения.

  4. Из файла scripts/subnets.txt мы извлекаем одну свободную подсеть.

  5. На основе полученных данных генерируем конфигурационные файлы для Terraform.

  6. Для фиксации результата отправляем каталог с файлами окружения в git репозиторий.

  7. При удалении — сценарий удаляет каталог с файлами окружения и возвращает освободившуюся подсеть в файл scripts/subnets.txt

scripts/subnets.txt:

172.28.50.0 172.28.51.0 172.28.52.0 ...

В данном файле мы храним адреса наши подсетей. Размер подсети определяется переменной CIDR в файле scripts/create_env.sh

Результат

  1. Мы получили фундамент который позволяет нам развернуть новый стенд путём запуска pipline’a в Gitlab CI.

  2. Снизили затраты нашей компании на инфраструктуру.

  3. Разработчики не простаивают и могут создавать и удалять стенды когда им это потребуется.

  4. Также получили возможность создания виртуальных машин в любом облаке  написав новый модуль Terraform и немного модифицировав сценарий созданиях\удаления окружений

  5. Можем поиграться с триггерами Gitlab и разворачивать новые стенды из пайплайнов команд разработчиков передавая версии сервисов.

ссылка на оригинал статьи https://habr.com/ru/post/548426/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *