Terraform для управления облачной инфраструктурой
Ansible прекрасно справляется с настройкой уже существующего сервера, но кто-то должен этот сервер сначала создать: заказать виртуальную машину, выделить сеть, настроить security group, привязать диск. Делать это руками через веб-консоль облачного провайдера или гипервизора работает, пока серверов немного — а дальше начинается та же проблема, что и с ручной настройкой: расхождения между «тем, что должно быть» и «тем, что реально создано». Terraform закрывает именно этот этап — управление инфраструктурой как кодом (Infrastructure as Code), только не настройками внутри сервера, а самим фактом его существования. В этой статье разберём базовые концепции Terraform и опишем инфраструктуру для лабораторного стенда в сети 192.168.10.0/24.
В отличие от Ansible, который выполняет последовательность шагов, Terraform работает декларативно: ты описываешь желаемое конечное состояние («должно быть три сервера с такими-то параметрами»), а Terraform сам вычисляет, что нужно создать, изменить или удалить, чтобы прийти к этому состоянию. Он умеет работать практически с любым облаком — AWS, Azure, Google Cloud, а также с локальной виртуализацией вроде Proxmox или VMware через соответствующие провайдеры.
Шаг 1. Установка Terraform
wget -O- https://apt.releases.hashicorp.com/gpg | 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" | tee /etc/apt/sources.list.d/hashicorp.list
apt update
apt install -y terraform
terraform -version
Terraform, как и Ansible, — это утилита командной строки, а не сервис. Устанавливать её можно на любую рабочую машину или на выделенный сервер автоматизации, например тот же srv-ansible01, если хочется держать весь инструментарий IaC в одном месте.
Шаг 2. Структура проекта
Terraform-проект — это каталог с файлами .tf. Стандартная практика — разносить конфигурацию по смыслу:
infra/
providers.tf
variables.tf
main.tf
outputs.tf
terraform.tfvars
В providers.tf указывается, с каким облаком или гипервизором работаем. Возьмём для примера провайдер Proxmox — частый выбор для локальной лаборатории с доменом itpages.lab.local:
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.61.1"
}
}
}
provider "proxmox" {
endpoint = "https://192.168.10.5:8006/"
api_token = var.proxmox_api_token
insecure = true
}
Шаг 3. Переменные
Секреты и изменяемые параметры выносятся в переменные, чтобы не хардкодить их в основной конфигурации:
# variables.tf
variable "proxmox_api_token" {
type = string
sensitive = true
}
variable "vm_count" {
type = number
default = 2
}
Сами значения задаются в terraform.tfvars (этот файл в .gitignore, если содержит секреты) или передаются через переменные окружения вида TF_VAR_proxmox_api_token.
Шаг 4. Описываем сервер
Теперь основная часть — main.tf, где описываются ресурсы, которые Terraform должен создать. Например, виртуальная машина для будущего srv-web02:
resource "proxmox_virtual_environment_vm" "web" {
name = "srv-web02"
node_name = "pve-node01"
cpu {
cores = 2
}
memory {
dedicated = 4096
}
disk {
datastore_id = "local-lvm"
size = 32
}
network_device {
bridge = "vmbr0"
}
initialization {
ip_config {
ipv4 {
address = "192.168.10.23/24"
gateway = "192.168.10.1"
}
}
}
}
Это декларативное описание: Terraform сравнит его с текущим состоянием инфраструктуры и поймёт, что нужно создать новую машину с указанными параметрами. Если позже увеличить memory.dedicated до 8192, Terraform на следующем прогоне сам определит, что нужно изменить именно память, не пересоздавая всю машину с нуля — если, конечно, тип ресурса это позволяет без пересоздания.
Шаг 5. Инициализация и применение
terraform init
Эта команда скачивает нужный провайдер (в нашем случае — плагин для Proxmox) и настраивает рабочий каталог. Выполняется один раз при старте проекта и повторно — при добавлении новых провайдеров.
terraform plan
План — это, пожалуй, самая важная команда во всём Terraform. Она показывает, что именно изменится, ничего при этом не применяя: какие ресурсы будут созданы (зелёным, со знаком +), какие изменены (жёлтым, ~), а какие удалены (красным, -). Прежде чем применять что-либо к реальной инфраструктуре, план стоит прочитать внимательно — особенно строки с удалением, которые иногда возникают неожиданно из-за изменения параметра, требующего пересоздания ресурса.
terraform apply
После подтверждения (ввод yes) Terraform создаст описанные ресурсы и сохранит их состояние в файл terraform.tfstate. Этот файл — по сути карта соответствия между кодом и реальной инфраструктурой, и относиться к нему нужно бережно.
Шаг 6. Состояние (state) и его хранение
Файл terraform.tfstate хранит актуальное представление о том, что Terraform создал и с какими параметрами. Именно по нему на следующем прогоне сравнивается желаемое состояние (код) с фактическим. Если этот файл потерять, Terraform перестанет понимать, какие ресурсы уже существуют, и может попытаться создать дубликаты. Поэтому для командной работы state никогда не хранится локально на ноутбуке — его выносят в удалённый backend, например:
terraform {
backend "s3" {
bucket = "itpages-terraform-state"
key = "lab/terraform.tfstate"
region = "eu-central-1"
}
}
Удалённый backend с блокировкой (locking) решает ещё одну проблему — если два инженера одновременно запустят terraform apply, второй получит ошибку блокировки вместо того, чтобы применить изменения поверх незавершённого прогона первого.
Шаг 7. Модули: переиспользуемые блоки
Когда нужно создать несколько однотипных серверов — скажем, три веб-сервера с одинаковой конфигурацией, но разными именами и IP — вместо копирования блока resource используется count или for_each, а для более сложной переиспользуемой логики — модули:
module "web_servers" {
source = "./modules/vm"
for_each = { "srv-web01" = "192.168.10.21", "srv-web02" = "192.168.10.23" }
name = each.key
ip = each.value
}
Модуль ./modules/vm при этом инкапсулирует все детали создания машины (CPU, память, диск, сеть), а снаружи остаётся только простой интерфейс из имени и IP-адреса. Это тот же принцип, что и роли в Ansible — не повторять одну и ту же логику в десяти местах, а описать её один раз и переиспользовать.
Шаг 8. Terraform и Ansible вместе
На практике Terraform и Ansible почти всегда работают в связке: Terraform создаёт сервер и выводит его IP-адрес через output, а Ansible следом настраивает то, что на этом сервере должно быть установлено.
# outputs.tf
output "web_server_ip" {
value = proxmox_virtual_environment_vm.web.initialization[0].ip_config[0].ipv4[0].address
}
Дальше этот вывод можно подставить прямо в инвентарь Ansible — вручную, через скрипт или с помощью динамического inventory-плагина, который читает terraform.tfstate напрямую и сам формирует список хостов для следующего шага автоматизации. Получается сквозной цикл: Terraform поднимает сервер srv-web02 с нужным IP в сети 192.168.10.0/24, а Ansible следом ставит на него nginx, node_exporter и Zabbix-агент — без единого ручного клика на всём пути.
Workspaces: одна конфигурация, несколько окружений
Часто одна и та же конфигурация нужна в нескольких вариантах — например, лабораторный стенд itpages.lab.local и отдельное тестовое окружение с теми же ресурсами, но другими IP-адресами и размерами машин. Дублировать код ради этого не нужно — у Terraform есть workspaces:
terraform workspace new staging
terraform workspace new production
terraform workspace select staging
Каждый workspace хранит собственный state, поэтому изменения в staging никак не затрагивают production, хотя код остаётся общим. Разница между окружениями обычно выносится в переменные — количество машин, диапазон IP, размер ресурсов — и подставляется в зависимости от того, в каком workspace идёт применение: terraform.workspace можно использовать прямо в выражениях внутри .tf файлов.
Импорт существующей инфраструктуры
Не всегда Terraform внедряется с чистого листа — часто в компании уже есть серверы, созданные вручную, которые хочется взять под управление кодом, не пересоздавая их. Для этого есть команда import, которая привязывает уже существующий ресурс к описанию в конфигурации:
terraform import proxmox_virtual_environment_vm.web "pve-node01/100"
После импорта состояние ресурса появляется в state, но конфигурацию в main.tf всё равно придётся дописать вручную так, чтобы она точно соответствовала реальным параметрам — иначе следующий plan покажет расхождения и предложит «исправить» сервер, откатив параметры, которые импорт не подтянул автоматически. На практике это самый муторный этап внедрения Terraform в уже существующую инфраструктуру, но именно он окупается в дальнейшем — после него вся инфраструктура становится видна и воспроизводима из кода.
Destroy и работа с ресурсами, которые нельзя терять
Команда terraform destroy удаляет все ресурсы, описанные в конфигурации, — это удобно для одноразовых тестовых окружений, но крайне опасно, если случайно выполнить её не в том каталоге или не в том workspace. Для критичных ресурсов, которые никогда не должны быть удалены автоматически (например, диск с данными базы), в Terraform есть защита:
resource "proxmox_virtual_environment_vm" "db" {
# ...
lifecycle {
prevent_destroy = true
}
}
При попытке удалить такой ресурс Terraform откажется выполнять план, пока флаг явно не будет снят из кода — это простая, но действенная страховка от случайной команды не в том терминале.
Частые ошибки и как их избежать
- Ручные правки инфраструктуры в обход Terraform. Если кто-то зашёл в консоль облака и поменял параметры сервера напрямую, состояние в terraform.tfstate разойдётся с реальностью. При следующем apply Terraform либо попытается откатить ручные изменения, либо, что хуже, пересоздать ресурс. Любые изменения инфраструктуры, управляемой Terraform, должны идти только через код.
- Локальный state без блокировки в команде. Если несколько человек работают с одной инфраструктурой, а state лежит на чьём-то локальном диске, рано или поздно кто-то применит план поверх устаревшего состояния и получит конфликт или дубликаты ресурсов. Удалённый backend с locking — не опция, а обязательное условие для командной работы.
- terraform apply без предварительного plan. Даже опытные инженеры иногда запускают apply сразу, полагаясь на память о том, что именно должно измениться. В реальности конфигурация могла разойтись с ожиданиями — например, из-за чужого коммита. Привычка всегда сначала смотреть на plan и только потом подтверждать apply экономит от неприятных сюрпризов, особенно от строк с пометкой «будет пересоздано».
Что дальше
Автоматизация и мониторинг обычно идут рука об руку: