Terraform: Infrastructure as Code para Entornos Empresariales
Terraform de HashiCorp es la herramienta líder para gestionar infraestructura como código. Esta guía cubre desde conceptos básicos hasta implementaciones empresariales complejas.
Fundamentos de Terraform
Instalación y Configuración
# Instalación en Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# Verificar instalación
terraform version
# Configuración de autocompletado
terraform -install-autocomplete
Estructura de Proyecto
# Estructura recomendada para proyectos empresariales
proyecto-terraform/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── production/
├── modules/
│ ├── vpc/
│ ├── ec2/
│ ├── rds/
│ └── security-groups/
├── shared/
│ ├── backend.tf
│ └── providers.tf
└── scripts/
├── deploy.sh
└── destroy.sh
Configuración de Providers
AWS Provider Configuration
# providers.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.1"
}
}
backend "s3" {
bucket = "empresa-terraform-state"
key = "infrastructure/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
Owner = var.team_name
CostCenter = var.cost_center
}
}
}
Multi-Cloud Setup
# providers-multicloud.tf
provider "aws" {
region = "us-east-1"
alias = "primary"
}
provider "aws" {
region = "eu-west-1"
alias = "secondary"
}
provider "azurerm" {
features {}
subscription_id = var.azure_subscription_id
}
provider "google" {
project = var.gcp_project_id
region = var.gcp_region
}
Módulos Reutilizables
Módulo VPC
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
}
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnets[count.index]
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet-${count.index + 1}"
Type = "Public"
}
}
resource "aws_subnet" "private" {
count = length(var.private_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnets[count.index]
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.project_name}-private-subnet-${count.index + 1}"
Type = "Private"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
}
}
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? length(aws_subnet.public) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.project_name}-nat-gateway-${count.index + 1}"
}
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(aws_subnet.public) : 0
domain = "vpc"
tags = {
Name = "${var.project_name}-eip-${count.index + 1}"
}
}
Módulo EC2 con Auto Scaling
# modules/ec2/main.tf
resource "aws_launch_template" "app" {
name_prefix = "${var.project_name}-"
image_id = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.app.id]
user_data = base64encode(templatefile("${path.module}/user-data.sh", {
app_name = var.app_name
environment = var.environment
}))
iam_instance_profile {
name = aws_iam_instance_profile.app.name
}
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = var.root_volume_size
volume_type = "gp3"
encrypted = true
}
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.project_name}-instance"
}
}
}
resource "aws_autoscaling_group" "app" {
name = "${var.project_name}-asg"
vpc_zone_identifier = var.subnet_ids
target_group_arns = [aws_lb_target_group.app.arn]
health_check_type = "ELB"
health_check_grace_period = 300
min_size = var.min_size
max_size = var.max_size
desired_capacity = var.desired_capacity
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.project_name}-asg"
propagate_at_launch = false
}
}
resource "aws_autoscaling_policy" "scale_up" {
name = "${var.project_name}-scale-up"
scaling_adjustment = 2
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.app.name
}
resource "aws_autoscaling_policy" "scale_down" {
name = "${var.project_name}-scale-down"
scaling_adjustment = -1
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.app.name
}
Gestión de Estado Remoto
Backend S3 Configuration
# backend.tf
terraform {
backend "s3" {
bucket = "empresa-terraform-state"
key = "environments/${var.environment}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
# Workspace support
workspace_key_prefix = "workspaces"
}
}
# Configuración del bucket S3 para estado
resource "aws_s3_bucket" "terraform_state" {
bucket = "empresa-terraform-state"
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_dynamodb_table" "terraform_locks" {
name = "terraform-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Variables y Configuración
Variables de Entorno
# variables.tf
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnets" {
description = "List of public subnet CIDR blocks"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "private_subnets" {
description = "List of private subnet CIDR blocks"
type = list(string)
default = ["10.0.10.0/24", "10.0.20.0/24"]
}
variable "instance_types" {
description = "Instance types by environment"
type = map(string)
default = {
dev = "t3.micro"
staging = "t3.small"
production = "t3.medium"
}
}
Terraform.tfvars por Entorno
# environments/production/terraform.tfvars
environment = "production"
project_name = "empresa-app"
aws_region = "us-east-1"
team_name = "DevOps"
cost_center = "IT-001"
vpc_cidr = "10.100.0.0/16"
public_subnets = [
"10.100.1.0/24",
"10.100.2.0/24",
"10.100.3.0/24"
]
private_subnets = [
"10.100.10.0/24",
"10.100.20.0/24",
"10.100.30.0/24"
]
# Auto Scaling configuration
min_size = 2
max_size = 10
desired_capacity = 4
# Database configuration
db_instance_class = "db.r5.large"
db_allocated_storage = 100
db_multi_az = true
Workspaces y Entornos
Gestión de Workspaces
# Crear y gestionar workspaces
terraform workspace new development
terraform workspace new staging
terraform workspace new production
# Cambiar entre workspaces
terraform workspace select production
terraform workspace list
# Configuración condicional por workspace
locals {
environment_configs = {
development = {
instance_type = "t3.micro"
min_size = 1
max_size = 2
}
staging = {
instance_type = "t3.small"
min_size = 1
max_size = 3
}
production = {
instance_type = "t3.medium"
min_size = 2
max_size = 8
}
}
current_config = local.environment_configs[terraform.workspace]
}
CI/CD Pipeline
GitLab CI Pipeline
# .gitlab-ci.yml
stages:
- validate
- plan
- apply
- destroy
variables:
TF_ROOT: ${CI_PROJECT_DIR}/environments/${ENVIRONMENT}
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${ENVIRONMENT}
before_script:
- cd ${TF_ROOT}
- terraform --version
- terraform init
-backend-config="address=${TF_ADDRESS}"
-backend-config="lock_address=${TF_ADDRESS}/lock"
-backend-config="unlock_address=${TF_ADDRESS}/lock"
-backend-config="username=gitlab-ci-token"
-backend-config="password=${CI_JOB_TOKEN}"
-backend-config="lock_method=POST"
-backend-config="unlock_method=DELETE"
-backend-config="retry_wait_min=5"
validate:
stage: validate
script:
- terraform validate
- terraform fmt -check
only:
- merge_requests
- master
plan:dev:
stage: plan
variables:
ENVIRONMENT: dev
script:
- terraform plan -out=plan.cache
artifacts:
paths:
- ${TF_ROOT}/plan.cache
expire_in: 1 week
only:
- merge_requests
plan:production:
stage: plan
variables:
ENVIRONMENT: production
script:
- terraform plan -out=plan.cache
artifacts:
paths:
- ${TF_ROOT}/plan.cache
expire_in: 1 week
only:
- master
apply:dev:
stage: apply
variables:
ENVIRONMENT: dev
script:
- terraform apply plan.cache
dependencies:
- plan:dev
only:
- merge_requests
when: manual
apply:production:
stage: apply
variables:
ENVIRONMENT: production
script:
- terraform apply plan.cache
dependencies:
- plan:production
only:
- master
when: manual
destroy:
stage: destroy
variables:
ENVIRONMENT: dev
script:
- terraform destroy -auto-approve
only:
- schedules
when: manual
Seguridad y Compliance
Security Groups Module
# modules/security-groups/main.tf
resource "aws_security_group" "web" {
name_prefix = "${var.project_name}-web-"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-web-sg"
}
}
resource "aws_security_group" "database" {
name_prefix = "${var.project_name}-db-"
vpc_id = var.vpc_id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
tags = {
Name = "${var.project_name}-db-sg"
}
}
Compliance y Políticas
# compliance.tf
# Enforce encryption for S3 buckets
resource "aws_s3_bucket_public_access_block" "compliance" {
for_each = local.buckets
bucket = each.value
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# CloudTrail for audit logging
resource "aws_cloudtrail" "main" {
name = "${var.project_name}-cloudtrail"
s3_bucket_name = aws_s3_bucket.cloudtrail.bucket
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["${aws_s3_bucket.app_data.arn}/*"]
}
}
tags = {
Environment = var.environment
Purpose = "Compliance"
}
}
Monitoring y Observabilidad
CloudWatch Integration
# monitoring.tf
resource "aws_cloudwatch_dashboard" "main" {
dashboard_name = "${var.project_name}-dashboard"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
x = 0
y = 0
width = 12
height = 6
properties = {
metrics = [
["AWS/EC2", "CPUUtilization", "AutoScalingGroupName", aws_autoscaling_group.app.name],
[".", "NetworkIn", ".", "."],
[".", "NetworkOut", ".", "."]
]
view = "timeSeries"
stacked = false
region = var.aws_region
title = "EC2 Instance Metrics"
period = 300
}
}
]
})
}
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
alarm_name = "${var.project_name}-high-cpu"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitors ec2 cpu utilization"
alarm_actions = [aws_autoscaling_policy.scale_up.arn]
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.app.name
}
}
Testing y Validación
Terratest Example
// test/vpc_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVPCModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"project_name": "test",
"vpc_cidr": "10.0.0.0/16",
"public_subnets": []string{"10.0.1.0/24", "10.0.2.0/24"},
"private_subnets": []string{"10.0.10.0/24", "10.0.20.0/24"},
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
}
Mejores Prácticas
Scripts de Automatización
#!/bin/bash
# scripts/deploy.sh
set -e
ENVIRONMENT=${1:-dev}
ACTION=${2:-plan}
echo "Running Terraform ${ACTION} for ${ENVIRONMENT}"
cd "environments/${ENVIRONMENT}"
# Initialize Terraform
terraform init
# Validate configuration
terraform validate
# Format code
terraform fmt
# Run security scan
tfsec .
# Execute action
case ${ACTION} in
plan)
terraform plan -out=tfplan
;;
apply)
terraform apply tfplan
;;
destroy)
terraform destroy -auto-approve
;;
*)
echo "Unknown action: ${ACTION}"
exit 1
;;
esac
Conclusión
Una implementación empresarial de Terraform requiere:
- Estructura modular y reutilizable
- Gestión de estado remoto segura
- Pipelines CI/CD automatizados
- Separación clara de entornos
- Aplicación de políticas de seguridad
- Monitoreo y observabilidad integrados
- Testing automatizado
- Documentación completa
Esta configuración proporciona una base sólida para gestionar infraestructura como código a escala empresarial.