Home » Как структурировать проект Terraform — лучшие практики
Как структурировать проект Terraform — лучшие практики

Как структурировать проект 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 sources
data "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 — это тоже код, и к нему применимы все принципы хорошей архитектуры.

Полезные ссылки:


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked