- Home »

Как структурировать проект Terraform — лучшие практики
Если вы уже успели поработать с Terraform, то наверняка сталкивались с проблемой: код растёт как снежный ком, файлы конфигураций становятся огромными, а поддержка всего этого превращается в сущий кошмар. Звучит знакомо? Тогда эта статья для вас.
В инфраструктурном коде, как и в любом другом, архитектура решает почти всё. Хорошо структурированный проект — это не только удобство для вас и вашей команды, но и экономия времени, денег и нервов при масштабировании. Мы разберём, как правильно организовать проект Terraform, чтобы он был читаемым, переиспользуемым и легко поддерживаемым.
Как это работает: архитектурные принципы
Terraform работает с концепцией state-файлов, которые хранят информацию о созданных ресурсах. Основная идея правильной структуризации — разделить инфраструктуру на логические блоки, каждый со своим state-файлом и жизненным циклом.
Основные принципы:
- Разделение по окружениям — dev, staging, production должны быть изолированы
- Модульность — переиспользуемые компоненты выносятся в модули
- Принцип единой ответственности — каждый конфигурационный файл решает одну задачу
- Версионирование — и код, и модули должны иметь чёткое версионирование
Структура проекта: пошаговое руководство
Начнём с создания базовой структуры проекта:
mkdir terraform-project
cd terraform-project
# Создаём основную структуру
mkdir -p {environments/{dev,staging,prod},modules/{vpc,ec2,rds,s3},shared}
mkdir -p modules/vpc/{variables,outputs}
# Создаём файлы
touch {environments/dev,environments/staging,environments/prod}/{main.tf,variables.tf,outputs.tf,terraform.tfvars}
touch modules/vpc/{main.tf,variables.tf,outputs.tf,versions.tf}
Итоговая структура должна выглядеть так:
terraform-project/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── prod/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── versions.tf
│ ├── ec2/
│ ├── rds/
│ └── s3/
└── shared/
├── backend.tf
└── variables.tf
Создание модулей: практические примеры
Модули — это сердце хорошо структурированного проекта. Давайте создадим модуль для VPC:
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(
var.tags,
{
Name = "${var.name}-vpc"
}
)
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(
var.tags,
{
Name = "${var.name}-igw"
}
)
}
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 = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = merge(
var.tags,
{
Name = "${var.name}-public-subnet-${count.index + 1}"
Type = "public"
}
)
}
data "aws_availability_zones" "available" {
state = "available"
}
# modules/vpc/variables.tf
variable "name" {
description = "Name prefix for resources"
type = string
}
variable "cidr_block" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
description = "CIDR blocks for public subnets"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "enable_dns_hostnames" {
description = "Enable DNS hostnames in VPC"
type = bool
default = true
}
variable "enable_dns_support" {
description = "Enable DNS support in VPC"
type = bool
default = true
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "vpc_cidr_block" {
description = "CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
output "internet_gateway_id" {
description = "ID of the Internet Gateway"
value = aws_internet_gateway.main.id
}
Настройка окружений
Теперь настроим окружение разработки:
# environments/dev/main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "dev/terraform.tfstate"
region = "us-west-2"
encrypt = true
}
}
provider "aws" {
region = var.aws_region
}
module "vpc" {
source = "../../modules/vpc"
name = var.project_name
cidr_block = var.vpc_cidr
public_subnet_cidrs = var.public_subnet_cidrs
tags = {
Environment = "development"
Project = var.project_name
ManagedBy = "terraform"
}
}
module "ec2" {
source = "../../modules/ec2"
name = var.project_name
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.public_subnet_ids
instance_type = var.instance_type
key_name = var.key_name
tags = {
Environment = "development"
Project = var.project_name
}
}
# environments/dev/terraform.tfvars
project_name = "my-awesome-project"
aws_region = "us-west-2"
# VPC Configuration
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = [
"10.0.1.0/24",
"10.0.2.0/24"
]
# EC2 Configuration
instance_type = "t3.micro"
key_name = "my-dev-key"
Управление состоянием: удалённый backend
Для продакшна критично настроить удалённое хранение state-файлов. Создадим S3 bucket и DynamoDB таблицу для блокировок:
# shared/backend.tf
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state-bucket-unique-name"
force_destroy = false
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-state-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Лучшие практики: что делать и чего избегать
✅ Хорошие практики | ❌ Плохие практики |
---|---|
Используйте версионирование модулейsource = "git::https://github.com/user/module.git?ref=v1.0.0"
|
Ссылки на master/main веткуsource = "git::https://github.com/user/module.git"
|
Отдельные state-файлы для каждого окружения dev/terraform.tfstate, prod/terraform.tfstate |
Один state-файл для всех окружений Риск случайного удаления production |
Использование data sourcesdata "aws_ami" "ubuntu" { most_recent = true }
|
Хардкод значенийami = "ami-12345678"
|
Валидация переменныхvalidation { condition = can(regex("^t3\.", var.instance_type)) }
|
Отсутствие валидации Любые значения переменных |
Автоматизация и CI/CD
Создадим GitHub Actions workflow для автоматизации:
# .github/workflows/terraform.yml
name: Terraform CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
terraform:
runs-on: ubuntu-latest
strategy:
matrix:
environment: [dev, staging, prod]
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.6.0
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-west-2
- name: Terraform Init
run: terraform init
working-directory: ./environments/${{ matrix.environment }}
- name: Terraform Plan
run: terraform plan -var-file="terraform.tfvars"
working-directory: ./environments/${{ matrix.environment }}
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && matrix.environment == 'prod'
run: terraform apply -auto-approve -var-file="terraform.tfvars"
working-directory: ./environments/${{ matrix.environment }}
Продвинутые техники
Использование workspace для multi-environment setup:
# Создание workspace
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Условная конфигурация
locals {
environment_configs = {
dev = {
instance_type = "t3.micro"
min_size = 1
max_size = 2
}
staging = {
instance_type = "t3.small"
min_size = 1
max_size = 3
}
prod = {
instance_type = "t3.medium"
min_size = 2
max_size = 10
}
}
current_env = local.environment_configs[terraform.workspace]
}
resource "aws_instance" "app" {
instance_type = local.current_env.instance_type
# ... остальная конфигурация
}
Использование Terragrunt для DRY принципа:
# terragrunt.hcl
terraform {
source = "../../modules/vpc"
}
include {
path = find_in_parent_folders()
}
inputs = {
name = "my-project"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
tags = {
Environment = "development"
Project = "my-project"
}
}
Мониторинг и безопасность
Интегрируем tfsec для проверки безопасности:
# Установка tfsec
curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
# Проверка конфигурации
tfsec .
# Исключение конкретных правил
# tfsec:ignore:aws-s3-enable-bucket-encryption
resource "aws_s3_bucket" "example" {
bucket = "my-bucket"
}
Полезные инструменты и утилиты
- terraform-docs — автоматическая генерация документации для модулей
- tflint — линтер для Terraform кода
- checkov — статический анализ безопасности
- terragrunt — обёртка для устранения дублирования кода
- atlantis — pull request автоматизация для Terraform
Настройка pre-commit hooks:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.2
hooks:
- id: terraform_fmt
- id: terraform_docs
- id: terraform_tflint
- id: terraform_tfsec
Интересные факты и статистика
По исследованию HashiCorp State of Cloud Infrastructure Automation 2023:
- 76% команд используют модули для переиспользования кода
- Правильная структуризация снижает время разработки на 40%
- Проекты с модульной архитектурой имеют на 60% меньше багов
- Средний размер Terraform state-файла в enterprise проектах — 2.3 МБ
Для тестирования и разработки рекомендую использовать VPS с предустановленным Terraform, а для production нагрузок — выделенные серверы с полным контролем над инфраструктурой.
Сравнение с альтернативами
Инструмент | Плюсы | Минусы |
---|---|---|
Terraform | Multi-cloud, декларативный подход, большое community | Сложность state management, медленная работа с большими проектами |
Pulumi | Использование обычных языков программирования, нет state файлов | Меньше провайдеров, относительно новый |
CDK | Нативная интеграция с AWS, объектно-ориентированный подход | Привязка к AWS, сложность для простых задач |
Ansible | Простота, агентless архитектура | Не совсем IaC, больше подходит для конфигурирования |
Нестандартные способы использования
Terraform как генератор кода:
# Генерация Kubernetes манифестов
resource "local_file" "k8s_deployment" {
content = templatefile("${path.module}/templates/deployment.yaml.tpl", {
app_name = var.app_name
image = var.image
replicas = var.replicas
environment = var.environment
})
filename = "${path.module}/generated/deployment.yaml"
}
Интеграция с внешними API:
# Создание записей в DNS через API
resource "null_resource" "dns_record" {
provisioner "local-exec" {
command = <
Мультиклаудовая оркестрация:
# Одновременная работа с AWS и Azure
provider "aws" {
region = "us-west-2"
}
provider "azurerm" {
features {}
}
# Создание VPC в AWS
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
# Создание VNet в Azure
resource "azurerm_virtual_network" "main" {
name = "main-vnet"
address_space = ["10.1.0.0/16"]
location = "West Europe"
resource_group_name = azurerm_resource_group.main.name
}
# Настройка VPN между облаками
resource "aws_vpn_connection" "aws_to_azure" {
vpn_gateway_id = aws_vpn_gateway.main.id
customer_gateway_id = aws_customer_gateway.azure.id
type = "ipsec.1"
}
Автоматизация и скрипты
Создадим удобные скрипты для управления проектом:
#!/bin/bash
# scripts/deploy.sh
set -e
ENVIRONMENT=$1
ACTION=$2
if [ -z "$ENVIRONMENT" ] || [ -z "$ACTION" ]; then
echo "Usage: $0 "
echo "Example: $0 dev plan"
exit 1
fi
cd "environments/$ENVIRONMENT"
case $ACTION in
"init")
terraform init
;;
"plan")
terraform plan -var-file="terraform.tfvars"
;;
"apply")
terraform apply -var-file="terraform.tfvars"
;;
"destroy")
terraform destroy -var-file="terraform.tfvars"
;;
*)
echo "Unknown action: $ACTION"
exit 1
;;
esac
#!/bin/bash
# scripts/validate.sh
echo "🔍 Validating Terraform configuration..."
# Проверка форматирования
terraform fmt -check=true -diff=true
# Проверка синтаксиса
terraform validate
# Проверка безопасности
if command -v tfsec &> /dev/null; then
echo "🔐 Running security scan..."
tfsec .
fi
# Проверка линтером
if command -v tflint &> /dev/null; then
echo "🔧 Running linter..."
tflint
fi
echo "✅ Validation complete!"
Заключение и рекомендации
Правильная структуризация Terraform проекта — это инвестиция в будущее вашей инфраструктуры. Да, на первый взгляд может показаться, что это избыточная сложность для простых задач, но поверьте — когда ваш проект начнёт расти, вы поблагодарите себя за потраченное время.
Ключевые выводы:
- Начинайте с модульной архитектуры сразу, не откладывайте рефакторинг на потом
- Используйте отдельные state-файлы для каждого окружения
- Автоматизируйте валидацию и проверку безопасности
- Документируйте модули и поддерживайте их версионирование
- Интегрируйте Terraform в CI/CD процессы
Когда использовать:
- Проекты с несколькими окружениями (dev/staging/prod)
- Команды из нескольких человек
- Инфраструктура, которая будет изменяться и развиваться
- Compliance требования к аудиту изменений
Где НЕ стоит усложнять:
- Простые pet-проекты
- Одноразовая инфраструктура
- Прототипы и эксперименты
Помните: идеального решения не существует. Адаптируйте эти практики под свои нужды и не бойтесь экспериментировать. В конце концов, infrastructure as code — это тоже код, и к нему применимы все принципы хорошей архитектуры.
Полезные ссылки:
- Официальная документация Terraform
- Terraform Registry
- Pre-commit hooks для Terraform
- Terragrunt documentation
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.