← Назад к вопросам
Написание Terraform модуля для AWS EC2 с EBS
2.0 Middle🔥 201 комментариев
#Облачные технологии
Условие
Напишите Terraform модуль для создания инфраструктуры в AWS:
Требования
- EC2 инстанс типа t3.micro
- EBS том размером 20GB, подключенный к инстансу
- Security Group с открытыми портами 22 (SSH) и 80 (HTTP)
- Elastic IP, привязанный к инстансу
- Модуль должен принимать переменные: region, instance_type, volume_size
- Outputs: public_ip, instance_id
Дополнительные вопросы
- Как организовать state файл для командной работы?
- Что такое terraform plan и terraform apply?
- Как откатить изменения в Terraform?
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
1. Структура Terraform модуля
Файловая структура
ec2-module/
├── main.tf # Основные ресурсы
├── variables.tf # Объявление переменных
├── outputs.tf # Выходные значения
├── terraform.tf # Конфигурация провайдера и backend
└── terraform.tfvars # Значения переменных (gitignore!)
2. main.tf - Основные ресурсы
# Security Group
resource "aws_security_group" "web_sg" {
name = "web-security-group-${var.environment}"
description = "Security group for web server"
vpc_id = var.vpc_id
tags = {
Name = "web-sg"
}
}
# Ingress rules
resource "aws_vpc_security_group_ingress_rule" "ssh" {
security_group_id = aws_security_group.web_sg.id
description = "SSH access"
from_port = 22
to_port = 22
ip_protocol = "tcp"
cidr_ipv4 = var.ssh_cidr # например "0.0.0.0/0" или "10.0.0.0/8"
tags = {
Name = "ssh-rule"
}
}
resource "aws_vpc_security_group_ingress_rule" "http" {
security_group_id = aws_security_group.web_sg.id
description = "HTTP access"
from_port = 80
to_port = 80
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
tags = {
Name = "http-rule"
}
}
resource "aws_vpc_security_group_ingress_rule" "https" {
security_group_id = aws_security_group.web_sg.id
description = "HTTPS access"
from_port = 443
to_port = 443
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
tags = {
Name = "https-rule"
}
}
# Egress rule (разрешить весь исходящий трафик)
resource "aws_vpc_security_group_egress_rule" "all" {
security_group_id = aws_security_group.web_sg.id
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
ip_protocol = "-1" # -1 означает все протоколы
cidr_ipv4 = "0.0.0.0/0"
tags = {
Name = "allow-all-egress"
}
}
# EC2 Instance
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.web_sg.id]
key_name = var.key_name
root_block_device {
volume_type = "gp3"
volume_size = var.root_volume_size
delete_on_termination = true
encrypted = true
tags = {
Name = "root-volume"
}
}
user_data = base64encode(var.user_data_script)
monitoring = true
tags = {
Name = "web-server-${var.environment}"
Environment = var.environment
}
depends_on = [aws_security_group.web_sg]
}
# EBS Volume
resource "aws_ebs_volume" "data_volume" {
availability_zone = aws_instance.web.availability_zone
size = var.data_volume_size
type = "gp3"
iops = 3000
throughput = 125
encrypted = true
kms_key_id = var.kms_key_id
tags = {
Name = "data-volume"
}
}
# Attach EBS Volume to Instance
resource "aws_volume_attachment" "data_attach" {
device_name = "/dev/sdf"
volume_id = aws_ebs_volume.data_volume.id
instance_id = aws_instance.web.id
}
# Elastic IP
resource "aws_eip" "web_eip" {
instance = aws_instance.web.id
domain = "vpc"
depends_on = [aws_instance.web]
tags = {
Name = "web-eip"
}
}
# Data source для получения последнего AMI Amazon Linux 2
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}
}
3. variables.tf - Входные переменные
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
validation {
condition = can(regex("^[a-z]{2}-[a-z]+-\\d{1}$", var.region))
error_message = "Region must be valid AWS region format."
}
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be one of: dev, staging, prod."
}
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
validation {
condition = can(regex("^t[23]\\.micro$", var.instance_type))
error_message = "Instance type must be t3.micro or t2.micro."
}
}
variable "root_volume_size" {
description = "Root volume size in GB"
type = number
default = 20
validation {
condition = var.root_volume_size >= 20 && var.root_volume_size <= 100
error_message = "Root volume size must be between 20 and 100 GB."
}
}
variable "data_volume_size" {
description = "Data volume size in GB"
type = number
default = 20
validation {
condition = var.data_volume_size >= 10 && var.data_volume_size <= 1000
error_message = "Data volume size must be between 10 and 1000 GB."
}
}
variable "vpc_id" {
description = "VPC ID where to create resources"
type = string
nullable = false
}
variable "subnet_id" {
description = "Subnet ID for EC2 instance"
type = string
nullable = false
}
variable "key_name" {
description = "EC2 Key Pair name for SSH access"
type = string
nullable = false
}
variable "ssh_cidr" {
description = "CIDR block for SSH access"
type = string
default = "0.0.0.0/0"
validation {
condition = can(cidrhost(var.ssh_cidr, 0))
error_message = "SSH CIDR must be valid CIDR notation."
}
}
variable "user_data_script" {
description = "User data script for EC2 initialization"
type = string
default = ""
}
variable "kms_key_id" {
description = "KMS key ID for EBS encryption"
type = string
default = null
}
4. outputs.tf - Выходные значения
output "instance_id" {
description = "EC2 instance ID"
value = aws_instance.web.id
}
output "instance_arn" {
description = "EC2 instance ARN"
value = aws_instance.web.arn
}
output "public_ip" {
description = "Elastic IP address"
value = aws_eip.web_eip.public_ip
}
output "private_ip" {
description = "Private IP address"
value = aws_instance.web.private_ip
}
output "security_group_id" {
description = "Security Group ID"
value = aws_security_group.web_sg.id
}
output "data_volume_id" {
description = "Data volume ID"
value = aws_ebs_volume.data_volume.id
}
output "data_volume_attachment_id" {
description = "Volume attachment ID"
value = aws_volume_attachment.data_attach.id
}
output "connection_string" {
description = "SSH connection string"
value = "ssh -i /path/to/key.pem ec2-user@${aws_eip.web_eip.public_ip}"
}
5. terraform.tf - Конфигурация провайдера и Backend
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# Backend для хранения state в S3 (для командной работы)
backend "s3" {
bucket = "my-terraform-state"
key = "prod/ec2/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Terraform = "true"
Project = "web-server"
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
6. terraform.tfvars - Значения переменных
# Создайте файл terraform.tfvars (НЕ коммитьте в git!)
region = "us-east-1"
environment = "prod"
instance_type = "t3.micro"
root_volume_size = 20
data_volume_size = 20
vpc_id = "vpc-xxxxxxxxx"
subnet_id = "subnet-xxxxxxxxx"
key_name = "my-keypair"
ssh_cidr = "0.0.0.0/0"
7. .gitignore для Terraform
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data
*.tfvars
*.tfvars.json
# Ignore override files
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Ignore plan files
*.tfplan
# IDE
.vscode/
.idea/
*.swp
8. State файл для командной работы
Проблема: локальный state
# ❌ ПЛОХО - state на локальной машине
# Если несколько разработчиков работают на одной инфраструктуре:
ls -la terraform.tfstate
# terraform.tfstate находится только на вашей машине!
# Коллега не видит изменения
# Конфликты при одновременной работе
Решение: Remote State в S3
# terraform.tf
terraform {
backend "s3" {
bucket = "my-terraform-state" # Название S3 bucket
key = "prod/ec2/terraform.tfstate" # Путь к state файлу
region = "us-east-1"
encrypt = true # Шифрование в S3
dynamodb_table = "terraform-locks" # Таблица для блокировок
}
}
Создание S3 bucket и DynamoDB таблицы
# Это нужно создать один раз, отдельно
aws s3api create-bucket \
--bucket my-terraform-state \
--region us-east-1
# Включить versioning
aws s3api put-bucket-versioning \
--bucket my-terraform-state \
--versioning-configuration Status=Enabled
# Включить шифрование
aws s3api put-bucket-encryption \
--bucket my-terraform-state \
--server-side-encryption-configuration '{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
}'
# Заблокировать публичный доступ
aws s3api put-public-access-block \
--bucket my-terraform-state \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# Создать DynamoDB таблицу для блокировок
aws dynamodb create-table \
--table-name terraform-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
Инициализация с remote state
# Инициализация (загрузит state из S3)
terraform init
# После инициализации state будет локально и в S3
terraform state list # Просмотр всех ресурсов
9. Terraform plan и terraform apply
terraform plan - Просмотр изменений
# Показывает, что Terraform собирается делать (без реальных изменений)
terraform plan
# Вывод
# + aws_instance.web # Создаст новый инстанс
# + aws_ebs_volume.data_volume # Создаст новый том
# + aws_eip.web_eip # Создаст Elastic IP
# + aws_security_group.web_sg # Создаст Security Group
# Сохранение плана в файл
terraform plan -out=tfplan
terraform apply - Применение изменений
# Применяет изменения (интерактивно просит подтверждение)
terraform apply
# Применяет без подтверждения (использовать осторожно!)
terraform apply -auto-approve
# Применяет сохраненный план (безопаснее)
terraform apply tfplan
Процесс plan + apply
# Шаг 1: Проверяем что изменится
terraform plan -out=tfplan
# Шаг 2: Смотрим результат
cat tfplan # (нечитаемо, это бинарный формат)
# Шаг 3: Применяем если все хорошо
terraform apply tfplan
# Шаг 4: Проверяем результаты
terraform state show aws_instance.web
terraform output public_ip
10. Откат изменений
Вариант 1: terraform destroy
# Удаляет ВСЕ ресурсы, описанные в config
terraform destroy
# Предварительный просмотр
terraform plan -destroy
# Удаление без подтверждения
terraform destroy -auto-approve
Вариант 2: Откат state к предыдущей версии
# Просмотр истории state
aws s3api list-object-versions --bucket my-terraform-state --prefix "prod/ec2/"
# Восстановление версии из S3
aws s3api get-object \
--bucket my-terraform-state \
--key "prod/ec2/terraform.tfstate" \
--version-id "VersionId12345" \
terraform.tfstate.backup
# Замена текущего state
cp terraform.tfstate.backup terraform.tfstate
terraform apply
Вариант 3: Откат отдельного ресурса
# Удаление конкретного ресурса из state (он остается в AWS, но Terraform его не видит)
terraform state rm aws_ebs_volume.data_volume
# Пересоздание ресурса
terraform apply
Вариант 4: terraform taint (пересоздание ресурса)
# Помечает ресурс как "грязный" - будет пересоздан при apply
terraform taint aws_instance.web
# При следующем apply инстанс будет уничтожен и создан заново
terraform apply
# Отмена пометки
terraform untaint aws_instance.web
11. Лучшие практики
Структура для production
terraform/
├── environments/
│ ├── dev/
│ │ ├── terraform.tfvars
│ │ └── main.tf (вызов модуля)
│ ├── staging/
│ │ └── terraform.tfvars
│ └── prod/
│ └── terraform.tfvars
├── modules/
│ └── ec2/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── terraform.tf
└── shared/
├── backend.tf
└── versions.tf
Команды для CI/CD
#!/bin/bash
set -e
# Инициализация
terraform init
# Валидация
terraform validate
# Форматирование
terraform fmt -check
# План
terraform plan -out=tfplan
# Применение (только после approval)
terraform apply tfplan