Простая оболочка для Terraform

Terragrunt — Держите ваш код Terraform сухим

Расширьте возможности IAC

Проблемы с голым Terraform

Terraform — это инструмент, который позволяет вам писать код, определяющий вашу инфраструктуру, но есть несколько известных проблем с использованием terraform. Первый — это файл состояния терраформирования, второй, который мы постараемся решить сегодня, — это проблема с сохранением кода терраформирования СУХИМ (не повторяйтесь) — особенно если вы используете несколько окружений.

Решений немного — вроде рабочих пространств, оплаты за terraform cloud, скриптов с переменными. Мы сосредоточимся на гибком и элегантном решении, использующем обертку для terraform — Terragrunt.

Структуры папок Terragrunt

Terragrunt (вкратце) позволяет вам выбирать каталог кода терраформирования и заполнять переменные с помощью простого управления состоянием. Входные данные для переменных терраформирования определены в .hcl файлах, которые могут ссылаться друг на друга и многое другое.

Это пример структуры папок, в которой используется terragrunt для стека AWS kubernetes:

.
├── common.hcl
├── dev
│ └── terragrunt.hcl
├── prod
│ └── terragrunt.hcl
├── staging
│ └── terragrunt.hcl
└── tf_files
 ├── envs
 │ ├── asg.tf
 │ ├── eks.tf
 │ ├── iam.tf
 │ ├── kubernetes.tf
 │ ├── lb.tf
 │ ├── main.tf
 │ ├── s3.tf
 │ ├── security_groups.tf
 │ ├── variables.tf
 │ └── vpc.tf
  • common.hcl — входные данные и настройки, передаваемые в каждую среду.
  • tf_files — код терраформирования.
  • dev/prod/staging terragunt.hcl — входные данные используются только для указанной среды.

Теперь для развертывания среды используйте команду terragrunt apply в одном из каталогов среды (dev/staging/prod), и происходит волшебство. Давайте обсудим, как .hcl файлы позволяют быть более гибкими с входными данными terrafrom.

Структура конфигурационного файла Terragrunt

Конфиг Terragrunt содержит блоки и атрибуты. Начнем с файла common.hcl. Он содержит определение вещей, которые будет использовать каждая среда:

# common.hcl
# block locals
locals {
  region         = "us-east-1"
  bucket         = "example"
  key            = "terraform/${path_relative_to_include()}.tfstate"
  dynamodb_table = "terraform_locks"
  profile        = "my-aws-profile"
}
# attributes local
inputs = {
}
# block remote_state
remote_state {
  backend = "s3"
  config = {
    bucket         = local.bucket
    region         = local.region
    key            = local.key
    dynamodb_table = local.dynamodb_table
    profile        = local.profile
    encrypt        = true
  }
}

Блок с местными жителями должен быть очевиден — вы можете легко сослаться на них позже в terragrunt.

Самая важная строка кода:

key = “terraform/${path_relative_to_include()}.tfstate”

Это гарантирует, что состояние будет сохранено по пути, который включает путь, в котором живет .hcl. При этом вы можете быстро убедиться, что каждое состояние среды будет управляться в другом файле состояния.

Теперь, чтобы убедиться, что это будет упоминаться в других файлах terragrunt, вам нужно добавить это:

# dev/terragrunt.hcl
include {
  path = find_in_parent_folders("common.hcl")
}
terraform {
  source = "${get_parent_terragrunt_dir()}//tf-files/envs/"
}
inputs {
  my_var = "var"
}

Используйте блок include, чтобы добавить файл common.hcl. Затем используйте terraform, чтобы указать каталог с кодом терраформирования, используя относительный путь, возвращаемый функцией get_parent_terragrunt_dir(). Чтобы запустить разработку terragrunt.hcl, перейдите в каталог и запустите terragrunt apply. Состояние будет сохранено в ведре как:

“terraform/dev.tfstate”

Каждая конфигурация среды упакована в один файл .hcl. Конечно, если вы хотите хранить состояния в разных сегментах, это также возможно. Просто измените имя корзины, чтобы использовать такую ​​функцию, как ключ состояния.

Команды Terragrunt

Большинство команд terragrunt являются оболочками для команд terraform:

terragrunt plan -> terraform plan
terragrunt apply -> terraform apply

Но некоторые из них продолжаются с дополнительными изменениями. terragrunt init автоматически создает корзину или DynamoDB (при использовании AWS).

Генерация кода терраформирования с помощью Terragrunt

Вы можете сгенерировать код терраформирования, чтобы пропустить ручную работу. Пример пользовательского примера: вы хотите создать идентичный провайдер для каждой среды с возможностью изменения профиля и региона AWS.

Просто добавьте вам дополнительный блок common.hcl :

generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite"
  contents = <<EOF
provider "aws" {
  version = ">= 2.57.0"
  profile = var.aws_profile
  region  = var.aws_region
}
EOF
}

Объединение нескольких файлов Terragrunt

Иногда для создания нескольких ресурсов с подробной настройкой может потребоваться использование словарей с большим количеством ключей или вложенных блоков. Например:

# ETL
glue_pythonshell_jobs = {
     match-trainingfile = {
      default_arguments = {
        "--extra-py-files"      = "s3://extra-py-files/pyfile.py"
        "--job-bookmark-option" = "job-bookmark-disable"
        "--enable-metrics"                   = "true"
      }
      role_arn        = "arn:aws:iam::<redacted>:role/<redacted>"
      script_location = "s3://scripts/match_trainingfile.py"
      name            = "pythonshell"
      timeout         = "2880"
    },
     order-records = {
      default_arguments = {
        "--job-bookmark-option" = "job-bookmark-disable"
        "--job-language"        = "python"
        "--TempDir"    = "s3://pipeline/workspace/temporary/"
        "--enable-metrics"                   = "true"
      }
      role_arn        = "arn:aws:iam::<redacted>:role/<redacted>"
      script_location = "s3://scripts/order_records.py"
      name            = "pythonshell"
      timeout         = "2880"
    },
...
# Kubernetes
...

Это словарь в атрибуте inputs конфигурационного файла terragrunt. Если вы когда-нибудь захотите управлять еще и Kubernetes, или VPC, или чем-то еще, представьте, насколько большим может стать terragrunt с таким количеством входных данных. К счастью, terragrunt поставляется с решением для сортировки ваших входных данных в файлы и объединения их в основной файл terragrunt.

Вы можете переместить все входные данные для заданий Glue и ETL, которые я показал, в файл с именем etl.hcl в том же каталоге и поместить их в атрибут inputs:

inputs = {
  ##
  ## Glue
  ##
 ....
}

А теперь обратитесь к этому файлу в нашем главном terragrunt.hcl :

locals {
  etl_vars = read_terragrunt_config("${get_terragrunt_dir()}/etl.hcl", {inputs = {}})
}
inputs = merge(
  local.etl_vars.inputs,
{
  project_name = "test"
  project_env  = "dev"
  aws_profile  = "test-dev"
  aws_region   = "us-east-1"
  vpc_cidr_block     = "10.0.0.0/16"
  vpc_subnet_newbits = 8 # vpc_cidr_block mask bits +      vpc_subnet_newbits = subnet size
  # Kubernetes
  ...
}
)

Это позволяет управлять кодом терраформирования, который требует подробных входных данных. Как видите, есть раздел # Kubernetes — его тоже можно переместить в отдельный файл, если он разрастется до больших размеров и после этого объединится вот так:

locals {
  etl_vars = read_terragrunt_config("${get_terragrunt_dir()}/etl.hcl", {inputs = {}})
  k8s_vars = read_terragrunt_config("${get_terragrunt_dir()}/k8s.hcl", {inputs = {}})
}
inputs = merge(
  local.etl_vars.inputs, 
  local.k8s_vars.inputs,
{
  project_name = "test"
  project_env  = "dev"
  aws_profile  = "test-dev"
  aws_region   = "us-east-1"
vpc_cidr_block     = "10.0.0.0/16"
  vpc_subnet_newbits = 8 # vpc_cidr_block mask bits +      vpc_subnet_newbits = subnet size
}
)

Все остается организованным и легко читаемым.

Последние мысли

Terraform — отличный инструмент, но я не могу представить себе работу с ним без Terragrunt. Эта статья является лишь кратким введением и не углубляется в более скрытые функции. И да, Terragrunt поддерживает работу с модулями.

Разумеется, у Terraform есть официальное решение для управления несколькими средами — Terraform Cloud. Но если вас не устраивают корпоративные решения и затраты — вам подойдет Terragrunt.

Попробуй сам. Может изменить правила игры для вас.