Terraform — инфраструктура как код
Terraform — это инструмент Infrastructure as Code (IaC): он описывает облачную инфраструктуру (виртуальные машины, сети, базы данных) в виде кода и приводит реальное состояние к описанному. Одна команда apply — и вся инфраструктура создана.
1. Что такое IaC и зачем это нужно
- Версионирование — инфраструктура хранится в Git, как код;
- Повторяемость — одинаково разворачивается в dev и prod;
- Аудит — видно, кто и что менял;
- Автоматизация — удаление, миграция и клонирование окружений.
2. Установка
# Linux (apt)
sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
terraform versionterraform.exe с официального сайта и добавь папку в PATH.3. Основы языка HCL
Конфигурации пишутся на HCL (HashiCorp Configuration Language):
# main.tf
provider "aws" {
region = "eu-west-1"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}Базовые конструкции
| Конструкция | Назначение |
|---|---|
| provider | облако/сервис: aws, azurerm, google, docker |
| resource | создаваемый объект |
| data | чтение существующего ресурса |
| variable | входной параметр |
| output | возвращаемое значение |
| module | переиспользуемый блок конфигурации |
4. Провайдеры
Провайдер — плагин, который умеет общаться с конкретным облаком. Обязательно указывается версия:
terraform {
required_version = ">= 1.0"
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}5. Жизненный цикл: init / plan / apply / destroy
terraform init # скачать провайдеров и модули
terraform plan # показать план изменений
terraform apply # применить (после подтверждения)
terraform destroy # удалить всю инфраструктуру
terraform fmt # отформатировать конфигурацию
terraform validate # проверить синтаксисterraform plan ДО ввода yes. Он показывает, что именно будет создано/изменено/удалено — и что из них разрушено безвозвратно.6. Переменные и output
Переменные определены в variables.tf:
variable "env" {
type = string
default = "dev"
description = "Окружение"
}
variable "instances" {
type = number
default = 2
}Значения задаются файлом terraform.tfvars или через -var:
env = "prod"
instances = 4terraform apply -var "env=prod" -var-file=prod.tfvarsВывод результатов наружу:
output "public_ip" {
value = aws_instance.web.public_ip
}7. State — файл состояния
Terraform хранит реальное состояние инфраструктуры в файле terraform.tfstate. Он связывает описанные ресурсы с созданными.
- никогда не редактируй state вручную;
- не храни его вместе с конфигурацией локально для команды;
- используй remote backend для общего хранения.
8. Remote backend
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
}
}Перед переключением старого state на новый backend выполняется миграция:
terraform init -migrate-state9. Модули
Модуль — переиспользуемый набор ресурсов. Свои модули лежат в папках, чужие — в реестре:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
cidr = "10.0.0.0/16"
}terraform init # подтянет модуль из реестра10. Workspaces
Workspace — независимое окружение с собственным state в рамках одной конфигурации:
terraform workspace new dev
terraform workspace new prod
terraform workspace select prod
terraform apply11. Практические советы
- форматируй код
terraform fmtперед каждым коммитом; - храни секреты в переменных окружения или в vault, не в
.tf; - используй план в CI — применяй только после ревью diff;
- разделяй конфигурации по средам:
dev/,prod/; - используй
terraform plan -out=plan.outи применяй именно этот файл.
12. Terraform в CI/CD
Инфраструктуру меняют не руками, а через пайплайн: plan в pull request, apply после ревью. Это защищает от «два админа, одна кнопка» и оставляет историю изменений.
# .github/workflows/terraform.yml (фрагмент)
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Plan
run: |
terraform init
terraform plan -out=plan.out
- name: Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve plan.outСекреты провайдера передаются переменными окружения из секретов CI, а сам план сохраняется как артефакт для аудита.
В пайплайне важно не применять план вслепую: разрешайте apply только для ветки main после ревью, а для временных веток оставляйте один plan с выгрузкой артефакта. Так каждое изменение инфраструктуры становится записью в истории, а откат — понятным шагом назад.
13. Решение конфликтов и проблем
Проблемы Terraform делятся на две группы: блокировка state и рассинхрон с реальной инфраструктурой (drift). Разберём типичные случаи.
- Lock: параллельный запуск даёт «lock error» — в remote backend достаточно подождать или удалить устаревший лимит.
- Drift: ресурс изменили руками —
terraform planпокажет разницу, аterraform applyвернёт конфигурацию. - Важные ресурсы:
prevent_destroy = trueзапрещает удаление базы и дисков случайным destroy. - Импорт: ресурс, созданный мимо Terraform, подключают командой
terraform import.
terraform import aws_s3_bucket.logs my-log-bucket
terraform state list # что под контролем
terraform state rm my-service # вывести ресурс из-под управленияЧастый сценарий — ручная правка ресурса в облаке: следующий terraform plan покажет расхождение. Решений три: принять фактическое состояние, вернуть конфигурацию обратным apply или перенести правку в код. Любой выбор фиксируется в истории проекта.
.tfstate запрещена: потеряете связь конфигурации с ресурсами. Любые операции состояния — только через terraform state ....14. Секреты и интеграция с Vault
Пароли и ключи в переменных окружения — стартовый, но слабый подход. HashiCorp Vault хранит секреты централизованно и выдаёт их с динамическим TTL.
# Провайдер vault в конфигурации
provider "vault" {
address = "https://vault.example.com"
}
data "vault_kv_secret_v2" "db" {
mount = "secret"
name = "myapp/db"
}
resource "aws_instance" "web" {
user_data = <<-EOF
export DB_PASSWORD="${data.vault_kv_secret_v2.db.data["password"]}"
EOF
}Сам Vault для Terraform использует токен CI с ограниченной политикой. Подробнее — в курсе Vault; управление секретов также показано в Ansible.
plan.out или лог пайплайна: экранируй вывод и не выводи переменные, содержащие пароли.15. Качество кода: tflint, tfsec, terraform-docs
Инфраструктура-как-код проверяется как обычный код: линтеры и статический анализ ловят ошибки до того, как они попадут в план.
tflint # неверные типы, лишние аргументы провайдера
tfsec # уязвимости: открытые порты, публичные бакеты
terraform-docs # README из переменных и выходов- tflint ловит неочевидное: deprecated-аргументы, неверные типы, пропущенные required-поля;
- tfsec (или Checkov) ищет проблемы безопасности в коде ещё до apply;
- terraform-docs генерирует README: документация не расходится с кодом;
- всё это — в CI на каждый PR вместе с
terraform fmt -checkи plan.
terraform fmt -recursive и tflint в pre-commit: мелочи, отвлекающие ревьюера, уйдут автоматически.16. Чтение окружения: data, remote state и random
Не вся инфраструктура управляется Terraform: что-то создано вручную или другим модулем. Для чтения существуют data-источники — они не создают ресурсы, а только читают.
data "terraform_remote_state" "base" {
backend = "s3"
config = { bucket = "tf-state", key = "base/terraform.tfstate" }
}
resource "random_password" "db" { length = 20 }
resource "aws_db_instance" "app" {
password = random_password.db.result
}data-источники читают готовые ресурсы и параметры окружения;terraform_remote_stateпередаёт выходы другого стека без дублирования;randomгенерирует пароли и идентификаторы прямо в коде.
17. Политики как код: OPA и Sentinel
«Все знают, что прод-бакеты должны быть закрытыми» — недостаточно. Политики как код превращают правило в проверяемый машинный тест на этапе plan.
# rego: план отклоняется при публичном бакете
deny[msg] {
bucket := input.some_bucket
bucket.acl == "public-read"
msg := "S3 bucket must not be public-read"
}- OPA (rego) встраивается в пайплайн и проверяет сгенерированный план;
- Sentinel встроен в Terraform Cloud с уровнями hard-mandatory и soft-warning;
- политики живут в репозитории и проходят код-ревью, как обычный код.
Не покрывайте всё «жёстко»: слишком строгие правила раздражают команду, и их начинают игнорировать. Начните с трёх правил, которые реально горели у вас в проде, и добавляйте по мере инцидентов.
policy/ сразу: даже без OPA это «конституция» проекта — что можно и что нельзя.18. Откаты и безопасные миграции
Инфраструктура иногда ломается после apply. Хорошая новость: история состояний позволяет откатиться — откат это тоже «план вперёд», а не магия.
- до изменений снимите точку:
terraform state pull; - при регрессии верните прежний рабочий код и примените — ресурсы вернутся в требуемое состояние;
- переименования — через
terraform state mvи блокиmoved, а не удаление с пересозданием; - для невосполнимых ресурсов —
prevent_destroy = true.
terraform state pull > backup-$(date +%F).json
terraform state mv aws_instance.web aws_instance.web_new
terraform state rm aws_instance.legacy19. Публикация и версионирование модулей
Модуль, написанный однажды и качественный, команда использует в десятках проектов. Чтобы делиться им честно, ему нужны версии и механика релиза — а не копирование папок по репозиториям.
# вызов модуля по git-тегу
module "vpc" {
source = "git::https://gitlab.com/team/terraform-module-vpc.git?ref=v1.2.0"
cidr = "10.0.0.0/16"
}- версионирование — semver: мажор/минор/патч, ломающие изменения — мажор;
- CHANGELOG и описание переменных/выходов — «документация», читаемая коллегами;
- тестирование модуля (test/golden, tflint) перед публикацией тега.
Registry Terraform или частный приватный реестр (если команда большая) — кэш модулей с безопасностью и верс. Для старта достаточно git-тегов.
20. Provisioners: локальные и remote
Provisioners выполняют команды при создании/уничтожении ресурса: установить пакет, прогнать скрипт, добавить в Ansible. Это «последний шанс» декларативности — почти всегда лучше вынести подготовку в образ, но provisioners упрощают жизнь в мелких случаях.
resource "aws_instance" "web" {
provisioner "remote-exec" {
inline = [
"apt-get update -y",
"systemctl start nginx",
]
}
}| Тип | Когда |
|---|---|
| local-exec | запуск локально после/до ресурса (генерация конфига) |
| remote-exec | команда в самом ресурсе через SSH/WinRM |
| file | копирование файла на удалённую машину |
Опасность: provisioners выполняются только при создании (или с -force), полная идемпотентность не гарантирована. Отсюда правило — чем меньше, тем лучше.
21. count и for_each: повторяющиеся ресурсы
Десять одинаковых ноутбуков или список пользователей в одну группу — копировать блоки руками нельзя. Два способа «массовости»: count (цифровая индексация) и for_each (обход коллекции по ключам/значениям).
variable "instances" {
type = map(string)
default = { web = "t3.micro", api = "t3.small" }
}
resource "aws_instance" "app" {
# for_each даёт читаемые ключи и устойчивый state
for_each = var.instances
instance_type = each.value
tags = { Name = each.key }
}- count — про количество и индексы (
count.index); - for_each — про «двухфакторную» коллекцию: ключ → значение;
- for_each устойчив к порядку: удаление из середины не «переливает» индексы.
Выбор: если элементы — самостоятельные сущности с именами — for_each; если «ряд» без идентичности — count. Оба использовать на одном ресурсе запрещено, но можно сгруппировать.
toset() для обхода списков: for_each требует коллекцию с уникальными ключами.module.x[each.key] — заучите синтаксис доступа заранее.22. OpenTofu и будущее экосистемы
HashiCorp изменила лицензию Terraform, и сообщество ответило форком — OpenTofu: свободный аналог с тем же синтаксисом и совместимостью state. Это важная веха экосистемы.
# Совместимая команда: замените бинарь terraform
# на tofu — синтаксис и state остаются
tofu init
tofu plan
tofu apply
# Переход между тулами одного проекта не требует
# пересоздания инфраструктуры- синтаксис HCL, модули, providers — совместимы на 99%;
- OpenTofu развивается открыто, добавляя собственные функции, а HCP Terraform (Cloud) продолжает развитие как SaaS-обвязка.
Стратегия на будущее: держите инфраструктуру в «не-привязанном» к вендору виде — модули и стандартные команды. Тогда смена тула не меняет планы работ.
Что дальше
Инфраструктура поднята — теперь настрой её с помощью Ansible, упакуй приложения в Docker и оркестрируй через Kubernetes. Каталог курсов — на главной странице.