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.