Полная автоматизация жизненного цикла инфраструктуры — от создания облачных ресурсов до настройки ПО на них — стала стандартом для эффективных DevOps-команд. Связка Terraform для декларативного развертывания и Ansible для идемпотентной конфигурации образует мощный, проверенный на практике дуэт. Это руководство предоставляет готовые модули Terraform для AWS и Yandex Cloud, роли Ansible для Nginx и PostgreSQL, а также детальные инструкции по их безопасной интеграции в командный CI/CD workflow, позволяя вам заменить ручные операции на надежный, воспроизводимый код.
От Infrastructure as Code к работающему сервису: философия связки Terraform + Ansible
Ключевой принцип современной инфраструктуры — идемпотентность: повторное применение конфигурации должно приводить к одинаковому, предсказуемому результату. Terraform и Ansible реализуют этот принцип на разных этапах жизненного цикла, образуя четкое разделение ответственности. Terraform выступает как архитектор и строитель, декларативно описывая и создавая «железо» и базовые облачные сервисы. Ansible же выполняет роль отделочника и наладчика, обеспечивая нужное состояние программного обеспечения внутри уже созданных ресурсов. Такой подход устраняет хаос ручных изменений, обеспечивает полную воспроизводимость сред (от dev до prod) и создает прозрачный аудит всех изменений через систему контроля версий.
Почему не только Terraform или не только Ansible? Границы ответственности
Использование одного инструмента для всех задач ведет к усложнению и нарушению best practices. Terraform идеально подходит для создания и управления внешними ресурсами облачных провайдеров: виртуальными сетями (VPC), подсетями, группами безопасности (Security Groups), виртуальными машинами (EC2, Compute Cloud), балансировщиками нагрузки и базами данных как сервис (RDS). Его декларативная модель гарантирует, что реальное состояние инфраструктуры всегда соответствует коду. Однако для тонкой настройки операционной системы (установка пакетов, конфигурационные файлы, управление системными сервисами) встроенные provisioners (local-exec, remote-exec) считаются крайней мерой (last resort), так как они усложняют отладку и нарушают чистоту модели.
Ansible, будучи императивным инструментом управления конфигурацией, создан именно для задач внутри ОС. Он предоставляет сотни модулей для атомарных операций (apt, yum, template, service), которые гарантируют идемпотентность. Хотя у Ansible есть модули для облачных провайдеров (ec2_instance, vpc), их использование для масштабного provisioning менее удобно и декларативно по сравнению с Terraform. Таким образом, граница проходит четко: Terraform управляет «внешней» инфраструктурой, Ansible — «внутренней» конфигурацией.
Целевой workflow: от `terraform apply` до `ansible-playbook run`
Идеальный автоматизированный пайплайн выглядит как последовательная цепочка:
- Код Terraform: Описание необходимых облачных ресурсов в модулях (main.tf).
- План и применение: Запуск
terraform planдля проверки иterraform applyдля создания ресурсов. - Извлечение выходных данных: Terraform экспортирует ключевые атрибуты созданных ресурсов (IP-адреса, имена хостов, ID) через
outputs. - Динамический инвентарь Ansible: Специальный скрипт (dynamic inventory) автоматически преобразует outputs Terraform (например, из JSON-файла) в список хостов для Ansible.
- Запуск плейбуков: Выполнение
ansible-playbookприменяет роли для установки и настройки ПО (Nginx, PostgreSQL) на созданных серверах. - Готовый сервис: В результате получается полностью настроенная и работоспособная среда.
Автоматическая передача данных (например, IP-адресов) между этапами исключает ошибки ручного копирования и делает процесс воспроизводимым.
Практикум: развертывание инфраструктуры с помощью модулей Terraform
Рассмотрим создание базовых модулей для двух облачных провайдеров. Структура каждого модуля следует best practices: main.tf (основные ресурсы), variables.tf (входные переменные), outputs.tf (выходные данные). Все примеры проверены с актуальными версиями провайдеров: AWS Provider (~> 5.0) и Yandex Cloud Provider (~> 0.100).
Модуль для AWS: создаем безопасную основу для веб-сервера
Модуль создает минимальную, но безопасную инфраструктуру в AWS для размещения веб-сервера: VPC с включенным DNS, публичную подсеть, security group и EC2-инстанс.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "web-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
availability_zone = "${var.aws_region}a"
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow HTTP and SSH"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.ssh_access_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = var.key_pair_name
tags = {
Name = "web-server"
Role = "web"
}
}
Важно: Укажите свои значения для переменных aws_access_key, aws_secret_key (через переменные окружения или backend) и key_pair_name.
Модуль для Yandex Cloud: аналогичная логика в другом облаке
Адаптация подхода для Yandex Cloud демонстрирует принципы cloud-agnostic дизайна. Основные отличия — в названиях ресурсов и способе аутентификации (через service account key).
# main.tf
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
version = "~> 0.100"
}
}
}
provider "yandex" {
service_account_key_file = var.service_account_key_file
cloud_id = var.cloud_id
folder_id = var.folder_id
zone = var.zone
}
resource "yandex_vpc_network" "network" {
name = "web-network"
}
resource "yandex_vpc_subnet" "subnet" {
name = "web-subnet"
zone = var.zone
network_id = yandex_vpc_network.network.id
v4_cidr_blocks = ["192.168.10.0/24"]
}
resource "yandex_compute_instance" "web" {
name = "web-server"
platform_id = "standard-v3"
zone = var.zone
resources {
cores = 2
memory = 2
}
boot_disk {
initialize_params {
image_id = "fd8vmcue7aajpmeo39kk" # Ubuntu 22.04 LTS
}
}
network_interface {
subnet_id = yandex_vpc_subnet.subnet.id
nat = true # Важно для публичного IP
}
metadata = {
ssh-keys = "ubuntu:${file(var.public_key_path)}"
}
labels = {
role = "web"
}
}
Ключевое отличие — в Yandex Cloud публичный IP (NAT) назначается на уровне сетевого интерфейса (network_interface { nat = true }), а не инстанса.
Ключевые outputs: готовим данные для Ansible
Outputs в Terraform — это явно объявленный интерфейс модуля. Для интеграции с Ansible необходимо экспортировать атрибуты созданных ресурсов.
# outputs.tf (для AWS)
output "web_instance_public_ip" {
description = "Public IP of the web server"
value = aws_instance.web.public_ip
}
output "web_instance_id" {
description = "ID of the web server"
value = aws_instance.web.id
}
output "security_group_id" {
description = "ID of the security group"
value = aws_security_group.web.id
}
После применения конфигурации сохраните outputs в формате JSON, который сможет прочитать Ansible Dynamic Inventory:
terraform output -json > terraform_output.json
Этот файл станет источником данных для автоматического построения инвентаря.
Настройка созданных серверов: от ролей Ansible до готовых сервисов
После создания инфраструктуры переходим к ее конфигурации. Ansible работает с инвентарем хостов. Вместо статического файла с IP-адресами используем Dynamic Inventory, который автоматически получает данные из outputs Terraform.
Dynamic Inventory: автоматически находим хосты из Terraform
Простой Python-скрипт может преобразовать JSON от Terraform в инвентарь Ansible. Создайте файл inventory_terraform.py:
#!/usr/bin/env python3
import json
import sys
with open('terraform_output.json') as f:
tf_output = json.load(f)
inventory = {
'web': {
'hosts': [tf_output['web_instance_public_ip']['value']],
'vars': {
'ansible_user': 'ubuntu',
'ansible_ssh_private_key_file': '~/.ssh/id_rsa'
}
}
}
if sys.argv[1] == '--list':
print(json.dumps(inventory))
elif sys.argv[1] == '--host':
print(json.dumps({}))
Сделайте скрипт исполняемым (chmod +x inventory_terraform.py) и настройте ansible.cfg или указывайте его напрямую: ansible-playbook -i inventory_terraform.py site.yml.
Для более сложных сценариев, включая теги и группы, можно использовать официальный, но устаревший скрипт terraform.py или написать свой парсер. Этот подход полностью устраняет необходимость ручного обновления инвентаря при каждом пересоздании инфраструктуры.
Роль `nginx`: установка, конфигурация и деплой статики
Роль Ansible — это переиспользуемый компонент для решения конкретной задачи. Структура роли nginx:
roles/nginx/
├── tasks/
│ └── main.yml
├── handlers/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
└── defaults/
└── main.yml
tasks/main.yml:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Deploy Nginx configuration template
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: restart nginx
- name: Deploy index.html
copy:
src: files/index.html
dest: /var/www/html/index.html
mode: '0644'
handlers/main.yml:
- name: restart nginx
service:
name: nginx
state: restarted
defaults/main.yml (переменные по умолчанию):
nginx_port: 80
server_name: "localhost"
Эта роль гарантирует, что Nginx будет установлен, сконфигурирован и запущен, а при изменении шаблона конфигурации — автоматически перезагружен.
Роль `postgresql`: от установки до настройки репликации
Роль для PostgreSQL демонстрирует управление более сложным stateful-сервисом. Она может быть адаптирована под кластерную конфигурацию.
# tasks/main.yml (фрагмент)
- name: Install PostgreSQL
apt:
name: "postgresql-{{ pg_version }}"
state: present
- name: Configure pg_hba.conf
template:
src: pg_hba.conf.j2
dest: /etc/postgresql/{{ pg_version }}/main/pg_hba.conf
notify: restart postgresql
- name: Configure postgresql.conf
template:
src: postgresql.conf.j2
dest: /etc/postgresql/{{ pg_version }}/main/postgresql.conf
notify: restart postgresql
- name: Create database
postgresql_db:
name: "{{ db_name }}"
- name: Create user
postgresql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
db: "{{ db_name }}"
priv: "ALL"
Для организации репликации можно использовать inventory-переменные для определения роли сервера:
# В инвентаре для хоста-мастера
db_role: master
# Для хоста-реплики
db_role: replica
Задачи в роли могут содержать условные операторы (when: db_role == 'master') для выполнения специфичных действий. Важно: Данная роль — базовый пример. Для production необходимо детально настроить параметры производительности (shared_buffers, work_mem), безопасность (SSL) и мониторинг.
Сводный плейбук: запускаем всю конфигурацию
Плейбук верхнего уровня site.yml определяет, какие роли и к каким хостам применять.
---
- name: Configure web servers
hosts: web
become: yes
roles:
- nginx
- name: Configure database servers
hosts: db
become: yes
roles:
- postgresql
Запуск всей конфигурации после создания инфраструктуры:
ansible-playbook -i inventory_terraform.py site.yml
Если ваш dynamic inventory корректно поместил хосты в группы web и db на основе тегов Terraform (например, tag_Role_Web), плейбук автоматически применит нужные роли к нужным серверам.
Безопасность и командная работа: управление State, блокировки и CI/CD
Использование Terraform и Ansible в одиночку на локальной машине — лишь первый шаг. Для работы в команде и в продакшн-средах критически важны безопасное хранение state, блокировка от параллельного применения и интеграция в CI/CD.
Remote Backend: храним state файл безопасно и доступно
Локальный файл terraform.tfstate — это точка отказа. Его потеря делает управление инфраструктурой невозможным. Remote backend решает эту проблему.
Для AWS (S3 + DynamoDB для блокировок):
terraform {
backend "s3" {
bucket = "your-unique-terraform-state-bucket"
key = "global/s3/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-locks"
}
}
Предварительно создайте S3 bucket с включенным versioning и DynamoDB таблицу с первичным ключом LockID (тип String).
Для Yandex Cloud можно использовать Object Storage (совместимый с S3 API):
terraform {
backend "s3" {
endpoint = "storage.yandexcloud.net"
bucket = "your-terraform-state-bucket"
key = "terraform.tfstate"
region = "ru-central1"
access_key = "..."
secret_key = "..."
skip_region_validation = true
skip_credentials_validation = true
}
}
После настройки backend выполните terraform init -reconfigure для миграции state.
Стратегия работы в Git: изолируем среды и контролируем изменения
Рекомендуемая структура репозитория разделяет код и конфигурацию сред:
terraform-repo/
├── modules/ # Переиспользуемые модули (vpc, compute)
│ ├── compute
│ └── vpc
├── environments/
│ ├── dev/ # Конфигурация dev-среды
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── prod/ # Конфигурация prod-среды
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
└── ansible/ # Роли и плейбуки Ansible
В environments/dev/main.tf вызываются модули из ../modules с параметрами для разработки (меньшие инстансы). В prod/ — те же модули, но с другими переменными (terraform.tfvars). Это обеспечивает согласованность и контроль. Все изменения в инфраструктуре проходят через pull/merge request с обязательным ревью кода и запуском terraform plan.
Интеграция в CI/CD: автоматизируем plan и apply
Базовый пайплайн в GitLab CI может выглядеть так:
# .gitlab-ci.yml
stages:
- validate
- plan
- apply
variables:
TF_DIR: "environments/$ENVIRONMENT"
before_script:
- cd $TF_DIR
- terraform init
terraform_validate:
stage: validate
script:
- terraform validate
terraform_plan:
stage: plan
script:
- terraform plan -out=plan.tfplan
artifacts:
paths:
- $TF_DIR/plan.tfplan
only:
- merge_requests
terraform_apply:
stage: apply
script:
- terraform apply -auto-approve plan.tfplan
when: manual # Критично для продакшн! Только ручной запуск.
only:
- main
Учетные данные провайдера (AWS keys, Yandex SA key) должны храниться в защищенных переменных CI/CD, а не в коде. Автоматический apply допустим только для некритичных сред (dev). Для production обязательна manual job после ревью плана.
Для комплексного подхода к управлению инфраструктурой и приложениями, изучите сравнение методологий в нашем материале: GitOps и Infrastructure as Code (IaC): Практическое сравнение подходов для DevOps в 2026.
Итоги и дальнейшие шаги: от базового пайплайна к продвинутой автоматизации
Выстроенный workflow «Terraform создает -> Ansible настраивает» обеспечивает полную автоматизацию, воспроизводимость и безопасность инфраструктуры. Вы получили готовые строительные блоки для AWS и Yandex Cloud, механизм их интеграции через Dynamic Inventory и best practices для командной работы.
Для дальнейшего развития рекомендуется:
- Тестирование: Внедрить Terratest на Go для модулей Terraform (проверка создания/удаления ресурсов) и Molecule для ролей Ansible (проверка конвергенции и идемпотентности).
- Углубление в Ansible: Использовать Ansible Vault для шифрования секретов, освоить динамические инвентари для облачных провайдеров (aws_ec2, yandex_compute).
- Переход к оркестрации: В сценариях с Kubernetes Terraform остается для создания самого кластера (EKS, Managed Kubernetes), но настройка workloads часто переходит к Helm и GitOps-инструментам (Flux, ArgoCD). Ansible может использоваться для bootstrap-задач.
- Мониторинг и управление: Добавить в пайплайн автоматическое создание алертов и дашбордов (например, используя Terraform модули для AWS CloudWatch или Yandex Monitoring).
Начинайте с малого: автоматизируйте одну среду и один сервис, отладьте пайплайн, а затем итеративно расширяйте охват. Официальная документация остается ключевым источником: Terraform Docs, Ansible Docs. Для отработки навыков работы с контейнерами, которые часто становятся целевой средой для развертывания, обратитесь к руководству: Docker Compose для сборки и управления многоконтейнерными приложениями.
Помните, что цель — не просто написать код, а создать надежный, самоописываемый и легко поддерживаемый процесс, который экономит время вашей команды и минимизирует операционные риски.