Введение: зачем автоматизировать сеть с помощью кода
Ручное управление сетью создает три проблемы. Первая - ошибки. Опечатка в маске подсети или неправильный тег в консоли AWS ломает связность между серверами. Вторая - время. Развернуть VPC с десятком подсетей, таблицами маршрутизации и NAT-шлюзами через веб-интерфейс можно за час, а повторить это для dev, staging и production окружений - за три. Третья - невоспроизводимость. Конфигурация, собранная кликами, не задокументирована и не версионирована. Через полгода никто не помнит, почему в этой подсети другой шлюз.
Infrastructure as Code решает эти проблемы. Вы описываете сеть декларативно - в файлах конфигурации. Terraform создает ресурсы в облаке по этому описанию. Ansible доводит их до нужного состояния: настраивает маршруты, файрволы, политики на серверах и оборудовании. Результат - идентичные окружения, воспроизводимые за минуты, с историей изменений в Git.
В этой статье вы получите готовые примеры кода для AWS и GCP. Разберем создание VPC, подсетей, NAT-шлюзов и балансировщиков нагрузки на Terraform. Затем подключим Ansible для тонкой настройки маршрутизации на Linux и сетевом оборудовании. В финале - проверенные практики модульной структуры, версионирования и безопасного отката изменений. Если вы уже работали с Terraform и Ansible по отдельности, материал по их связке с Dynamic Inventory и remote state дополнит эту статью конкретными скриптами.
Декларативное описание сети в Terraform: от VPC до балансировщика
Terraform оперирует провайдерами - плагинами для конкретных облаков. Для AWS это hashicorp/aws, для GCP - hashicorp/google. Вы описываете желаемое состояние ресурсов в файлах .tf, запускаете terraform plan для предпросмотра изменений и terraform apply для применения. Никаких мастеров настройки, только код.
Базовая структура проекта Terraform
Рабочий проект начинается с четкой организации файлов. Типовая структура для сетевого слоя:
network/
├── main.tf # вызов модулей
├── variables.tf # входные параметры
├── outputs.tf # экспортируемые значения
├── terraform.tfvars # значения переменных для окружения
└── modules/
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── loadbalancer/
├── main.tf
├── variables.tf
└── outputs.tfТакое разделение позволяет переиспользовать модуль VPC в разных проектах, меняя только входные переменные. Переменные определяют CIDR-блоки, имена, теги. Outputs возвращают ID созданных ресурсов - их получат другие модули, например compute, через удаленное состояние.
Создание VPC и подсетей в AWS
Начнем с базового блока - изолированной сети. Код ниже создает VPC с двумя публичными и двумя приватными подсетями в разных зонах доступности:
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-${var.availability_zones[count.index]}"
}
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.environment}-private-${var.availability_zones[count.index]}"
}
}Параметр map_public_ip_on_launch = true критичен для публичных подсетей - без него инстансы не получат публичный IP автоматически. Приватные подсети этого флага не имеют: доступ в интернет они получат через NAT-шлюз на следующем шаге.
Настройка маршрутизации и NAT-шлюза
Публичные подсети должны иметь выход в интернет напрямую, приватные - через NAT. Для этого создаем Internet Gateway, NAT Gateway и таблицы маршрутизации:
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
resource "aws_eip" "nat" {
domain = "vpc"
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = {
Name = "${var.environment}-nat"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.environment}-public-rt"
}
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = "${var.environment}-private-rt"
}
}
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = length(var.private_subnet_cidrs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}NAT Gateway размещается в публичной подсети и требует Elastic IP. Маршрут по умолчанию для приватных подсетей указывает на него, для публичных - на Internet Gateway. Ассоциации привязывают таблицы к подсетям. После terraform apply инстансы в приватных подсетях смогут скачивать пакеты, но останутся недоступны извне.
Развертывание балансировщика нагрузки
Application Load Balancer распределяет входящий трафик между инстансами в нескольких зонах. Минимальная конфигурация:
resource "aws_security_group" "alb" {
name = "${var.environment}-alb-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_lb" "main" {
name = "${var.environment}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
}
resource "aws_lb_target_group" "app" {
name = "${var.environment}-app-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/health"
interval = 30
healthy_threshold = 3
unhealthy_threshold = 3
}
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}Балансировщик размещается в публичных подсетях, принимает трафик на 443 порт и направляет его в target group с инстансами приложения. Health check проверяет endpoint /health каждые 30 секунд. Три успешных проверки - инстанс считается здоровым, три неуспешных - выводится из балансировки.
Аналогичная топология в GCP
Принципы те же, ресурсы называются иначе. Вместо VPC - google_compute_network, вместо Internet Gateway - автоматическая маршрутизация через google_compute_route, NAT реализуется через Cloud NAT, а балансировщик - через google_compute_url_map и google_compute_target_http_proxy. Пример создания сети с подсетями:
resource "google_compute_network" "main" {
name = "${var.environment}-vpc"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "public" {
count = length(var.public_subnet_cidrs)
name = "${var.environment}-public-${count.index}"
ip_cidr_range = var.public_subnet_cidrs[count.index]
region = var.region
network = google_compute_network.main.id
}
resource "google_compute_router" "main" {
name = "${var.environment}-router"
region = var.region
network = google_compute_network.main.id
}
resource "google_compute_router_nat" "main" {
name = "${var.environment}-nat"
router = google_compute_router.main.name
region = var.region
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}Ключевое отличие от AWS: GCP использует Cloud Router как промежуточный слой для NAT. Подсети глобальны в рамках региона, а не привязаны к зонам доступности жестко. Балансировщик HTTP(S) в GCP - это глобальный ресурс, не требующий указания подсетей. Выбор провайдера влияет на синтаксис, но не на архитектурный подход. Если сравниваете инструменты IaC для разных задач, обратитесь к детальному сравнению Terraform, Ansible и Pulumi.
Гибкая конфигурация с Ansible: политики маршрутизации и не только
Terraform создает ресурсы. Ansible приводит их конфигурацию к нужному состоянию. После того как облачные инстансы запущены, на них нужно настроить специфичные маршруты, файрволы, сетевые интерфейсы. Ansible делает это идемпотентно - повторный запуск playbook не сломает работающую конфигурацию.
Динамический инвентарь из Terraform state
Ручная передача IP-адресов из Terraform в Ansible - источник ошибок. Правильный путь - динамический инвентарь, который читает state-файл Terraform и строит список хостов автоматически. Плагин terraform.py для Ansible делает именно это. Альтернатива - генерировать статический inventory через шаблон Terraform:
resource "local_file" "ansible_inventory" {
content = templatefile("${path.module}/templates/inventory.tpl", {
web_servers = aws_instance.web[*].public_ip
db_servers = aws_instance.db[*].private_ip
})
filename = "../ansible/inventory/hosts.ini"
}Этот подход проще для небольших проектов. Для крупных лучше использовать плагин - он всегда отражает актуальное состояние инфраструктуры. Подробная схема такой интеграции описана в руководстве по связке Terraform и Ansible с готовым скриптом Dynamic Inventory.
Настройка маршрутизации на Linux-серверах
Облачная маршрутизация работает на уровне VPC. Но бывают сценарии, где нужны статические маршруты на самом хосте: несколько сетевых интерфейсов, VPN-туннели, source-based routing для разделения трафика. Ansible-роль для такой задачи:
- name: Add static route via eth1
ansible.builtin.lineinfile:
path: /etc/network/interfaces.d/eth1-routes
line: "up ip route add 10.100.0.0/16 via 192.168.10.1 dev eth1"
create: yes
notify: restart networking
- name: Configure source-based routing table
ansible.builtin.lineinfile:
path: /etc/iproute2/rt_tables
line: "200 custom_table"
notify: apply routing rules
- name: Add routing rule for source IP
ansible.builtin.command:
cmd: "ip rule add from 10.0.1.0/24 table custom_table"
when: ansible_check_mode == falseПервый блок добавляет статический маршрут через конкретный интерфейс. Второй создает отдельную таблицу маршрутизации для source-based routing - это нужно, когда трафик от определенных подсетей должен уходить через другой шлюз. Третий блок добавляет правило, направляющее пакеты от сети 10.0.1.0/24 в эту таблицу.
Управление сетевым оборудованием
Ansible работает с коммутаторами и маршрутизаторами через специализированные модули. Для Cisco IOS:
- name: Configure VLAN on Cisco switch
cisco.ios.ios_vlans:
config:
- vlan_id: 100
name: web_servers
state: active
- name: Configure OSPF on Cisco router
cisco.ios.ios_ospfv2:
config:
processes:
- process_id: 1
network:
- address: 10.0.0.0
wildcard_bits: 0.0.0.255
area: 0Для Juniper используется модуль junipernetworks.junos.junos_vlans, для Arista - arista.eos.eos_vlans. Принцип везде одинаков: описываете желаемое состояние, модуль приводит оборудование к нему. Ansible сам определяет, какие команды нужно отправить, и проверяет результат. Это критично для крупных сетей, где ручная правка конфигурации на десятках устройств гарантированно приводит к расхождениям.
Проверенные практики: модульность, версионирование и безопасные изменения
IaC без инженерных практик превращается в набор скриптов, которые страшно запускать. Четыре правила меняют ситуацию.
Модульная структура Terraform-проекта
Модуль - это изолированный набор ресурсов с четким интерфейсом: переменные на вход, outputs на выход. Модуль network создает VPC и возвращает ID подсетей. Модуль compute получает эти ID и размещает в них инстансы. Модуль loadbalancer принимает список инстансов и вешает на них балансировщик. Связываются они через удаленное состояние:
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "terraform-state"
key = "network/terraform.tfstate"
region = "us-east-1"
}
}
module "compute" {
source = "./modules/compute"
public_subnet_ids = data.terraform_remote_state.network.outputs.public_subnet_ids
}Такая архитектура позволяет менять сетевой слой, не трогая вычислительный, и наоборот. Каждый модуль можно тестировать отдельно. Подход детально разобран в практическом руководстве по IaC с примерами для AWS и Kubernetes.
Версионирование конфигураций в Git
Инфраструктурный код живет в репозитории наравне с прикладным. Стратегия ветвления - trunk-based: основная ветка main, короткоживущие feature-ветки для изменений. Каждый pull request запускает terraform plan в CI/CD и показывает diff ревьюеру. Тегирование релизов привязывает версию инфраструктуры к версии приложения:
git tag -a v1.3.0 -m "Добавлен NAT-шлюз для staging"
git push origin v1.3.0Это дает полную историю: кто, когда и зачем изменил конфигурацию сети. Откат - это просто checkout предыдущего тега и terraform apply.
Планирование изменений и минимизация рисков
terraform plan - главный инструмент безопасности. Он показывает, какие ресурсы будут созданы, изменены или уничтожены, до применения. Вывод нужно читать внимательно: символ -/+ означает уничтожение и пересоздание ресурса - это прервет трафик. Флаг -target позволяет применить изменения только к конкретному ресурсу:
terraform plan -target=aws_nat_gateway.mainДля критичных окружений подключаются политики безопасности. HashiCorp Sentinel или Open Policy Agent проверяют план на соответствие правилам: нельзя создать публичный S3-бакет, нельзя открыть SSH на 0.0.0.0/0, нельзя превысить лимит ресурсов. Нарушение блокирует apply.
Стратегии быстрого отката
State-файл Terraform - это источник истины о текущем состоянии инфраструктуры. Его потеря означает ручное восстановление или повторное создание ресурсов. State хранят в S3 или GCS с включенным версионированием и блокировками через DynamoDB:
terraform {
backend "s3" {
bucket = "terraform-state"
key = "network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}Блокировка через DynamoDB предотвращает одновременный запуск apply двумя инженерами - второй получит ошибку и не повредит состояние. Для отката достаточно восстановить предыдущую версию state-файла из S3 и запустить terraform apply с кодом нужной версии из Git. Время восстановления - минуты, а не часы ручной правки консоли.
Если вы разворачиваете инфраструктуру под мероприятия, хакатоны или демо-стенды, примените подход из руководства по автоматизации развертывания стендов - готовые шаблоны сокращают подготовку с недели до нескольких часов.
Заключение: ваш путь к полностью автоматизированной сети
Автоматизация сети через IaC - это три конкретных шага. Первый: опишите топологию в Terraform - VPC, подсети, шлюзы, балансировщик. Код из этой статьи дает готовую основу для AWS и GCP. Второй: подключите Ansible для настройки маршрутов, файрволов и оборудования. Начните с динамического инвентаря и ролей для статических маршрутов. Третий: внедрите инженерные практики - модули, Git, terraform plan перед каждым изменением и версионируемый state в S3.
Проверьте подход на тестовом проекте. Создайте репозиторий, скопируйте примеры, запустите terraform plan. Увидите список ресурсов, которые появятся в облаке - и ни одного клика в консоли. Для продакшена добавьте CI/CD пайплайн, который автоматически применяет изменения после review. Полный стек с Auto Scaling и Nginx описан в руководстве по отказоустойчивому веб-приложению.
Для практической отработки навыков потребуется облачная инфраструктура. Timeweb Cloud предоставляет VDS, базы данных и Kubernetes с поминутной тарификацией - удобно для тестовых окружений. Если в процессе настройки понадобится консультация по сложным конфигурациям, AiTunnel дает доступ к GPT и Claude через единый API без VPN - можно быстро проверить синтаксис или сгенерировать шаблон модуля.