Infraestructura como Código (IaC) 🏗️
La infraestructura como código revoluciona la forma en que gestionamos y desplegamos recursos de TI, tratando la infraestructura con las mismas prácticas que el código de aplicación.
🎯 ¿Qué es Infrastructure as Code?
Definición
Infrastructure as Code (IaC) es la práctica de gestionar y aprovisionar infraestructura a través de código en lugar de procesos manuales o interfaces gráficas.
Antes vs Después de IaC
Método tradicional
1. Documentar infraestructura en Excel/Word
2. Crear servidores manualmente via GUI
3. Configurar servicios conectándose via SSH
4. "Esperanza y oraciones" para replicar
5. Inconsistencias entre entornos
Con IaC
1. Definir infraestructura en código
2. Versionarlo en Git
3. Aplicar automáticamente
4. Replicar idénticamente
5. Consistencia garantizada
🔄 Beneficios de IaC
1. Consistencia y repetibilidad
# El mismo código = infraestructura idéntica
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1d0"
instance_type = "t2.micro"
tags = {
Name = "web-server-${count.index + 1}"
Environment = var.environment
}
}
2. Versionado y trazabilidad
# Historial completo de cambios
git log --oneline infrastructure/
# a1b2c3d Add load balancer configuration
# d4e5f6g Update instance type to t3.medium
# g7h8i9j Initial infrastructure setup
3. Colaboración y revisión
# Pull request para cambios de infraestructura
- name: Add RDS database
changes:
- database.tf: +45 -0
- variables.tf: +12 -0
reviewers: [devops-team, security-team]
4. Automatización y CI/CD
# Pipeline para infraestructura
stages:
- validate
- plan
- apply (manual approval)
- test
🛠️ Herramientas principales
Terraform (HashiCorp)
Uso: Provisioning multi-cloud Fortalezas: Multi-cloud, gran ecosistema, state management Lenguaje: HCL (HashiCorp Configuration Language)
# Ejemplo básico de Terraform
provider "aws" {
region = "us-west-2"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-${count.index + 1}"
}
}
AWS CloudFormation
Uso: Infraestructura AWS nativa Fortalezas: Integración perfecta con AWS, rollback automático Lenguaje: JSON/YAML
# CloudFormation template
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Basic VPC with public subnets'
Parameters:
EnvironmentName:
Description: Environment name prefix
Type: String
Default: Development
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-VPC
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-Public-Subnet-1
Pulumi
Uso: IaC con lenguajes de programación familiar Fortalezas: TypeScript/Python/Go/C#, programabilidad completa Lenguaje: Múltiples lenguajes de programación
// Pulumi con TypeScript
import * as aws from "@pulumi/aws";
// Crear VPC
const vpc = new aws.ec2.Vpc("main-vpc", {
cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
enableDnsSupport: true,
tags: {
Name: "main-vpc"
}
});
// Crear subnets públicas
const publicSubnets = [];
for (let i = 0; i < 2; i++) {
const subnet = new aws.ec2.Subnet(`public-subnet-${i + 1}`, {
vpcId: vpc.id,
cidrBlock: `10.0.${i + 1}.0/24`,
mapPublicIpOnLaunch: true,
tags: {
Name: `public-subnet-${i + 1}`
}
});
publicSubnets.push(subnet);
}
🏗️ Arquitectura típica con IaC
Estructura de proyecto
infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── prod/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── ec2/
│ └── rds/
├── shared/
│ ├── data.tf
│ └── providers.tf
└── scripts/
├── deploy.sh
└── destroy.sh
Ejemplo de módulo reutilizable
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(
var.common_tags,
{
Name = "${var.name_prefix}-vpc"
}
)
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(
var.common_tags,
{
Name = "${var.name_prefix}-igw"
}
)
}
# modules/vpc/variables.tf
variable "cidr_block" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "name_prefix" {
description = "Name prefix for resources"
type = string
}
variable "common_tags" {
description = "Common tags for all resources"
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.this.id
}
output "vpc_cidr_block" {
description = "CIDR block of the VPC"
value = aws_vpc.this.cidr_block
}
Uso del módulo
# environments/prod/main.tf
module "vpc" {
source = "../../modules/vpc"
name_prefix = "production"
cidr_block = "10.0.0.0/16"
common_tags = {
Environment = "production"
Project = "web-app"
Owner = "devops-team"
}
}
module "web_servers" {
source = "../../modules/ec2"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.public_subnet_ids
instance_count = 3
instance_type = "t3.medium"
common_tags = {
Environment = "production"
Project = "web-app"
}
}
🔄 Workflows y mejores prácticas
1. Workflow básico
# 1. Planificar cambios
terraform plan -out=tfplan
# 2. Revisar plan
terraform show tfplan
# 3. Aplicar cambios
terraform apply tfplan
# 4. Verificar estado
terraform state list
terraform show
2. Workflow con CI/CD
# .github/workflows/terraform.yml
name: Terraform CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -no-color
env:
TF_VAR_environment: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve
env:
TF_VAR_environment: prod
3. State management
# Backend configuration
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "environments/prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
# State locking con DynamoDB
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "terraform-state-lock"
hash_key = "LockID"
read_capacity = 5
write_capacity = 5
attribute {
name = "LockID"
type = "S"
}
}
🔒 Seguridad en IaC
1. Secrets management
# ❌ Malo: hardcoded secrets
resource "aws_db_instance" "bad_example" {
password = "supersecret123" # ¡NUNCA!
}
# ✅ Bueno: usando variables
resource "aws_db_instance" "good_example" {
password = var.db_password
}
# ✅ Mejor: usando AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/password"
}
resource "aws_db_instance" "best_example" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
2. Security scanning
# Checkov - Static analysis para IaC
pip install checkov
checkov -f main.tf
# tfsec - Security scanner específico para Terraform
tfsec .
# Terrascan - Multi-cloud security scanner
terrascan scan -t terraform
3. Policy as Code
# Sentinel policy example (Terraform Cloud)
import "tfplan"
# Require specific instance types
allowed_instance_types = ["t2.micro", "t2.small", "t3.micro", "t3.small"]
main = rule {
all tfplan.resource_changes as _, rc {
rc.type is "aws_instance" implies
rc.change.after.instance_type in allowed_instance_types
}
}
📊 Testing de infraestructura
1. Unit testing con Terratest
// test/vpc_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVPCCreation(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"name_prefix": "test",
"cidr_block": "10.0.0.0/16",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
}
2. Integration testing
#!/bin/bash
# test/integration_test.sh
# Apply infrastructure
terraform apply -auto-approve
# Get outputs
VPC_ID=$(terraform output -raw vpc_id)
LB_DNS=$(terraform output -raw load_balancer_dns)
# Test connectivity
curl -f "http://${LB_DNS}/health" || {
echo "Health check failed"
exit 1
}
# Test database connectivity
aws rds describe-db-instances --db-instance-identifier myapp-db || {
echo "Database not accessible"
exit 1
}
echo "All integration tests passed!"
3. Compliance testing
# compliance/cis_checks.yml
controls:
- id: "2.1"
title: "Ensure CloudTrail is enabled in all regions"
query: |
SELECT * FROM aws_cloudtrail
WHERE is_multi_region_trail = true
AND is_logging = true
- id: "2.2"
title: "Ensure S3 bucket logging is enabled"
query: |
SELECT * FROM aws_s3_bucket
WHERE logging_enabled = false
🎯 Casos de uso prácticos
1. Aplicación web típica
# Arquitectura completa
module "networking" {
source = "./modules/networking"
vpc_cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b"]
environment = var.environment
}
module "security" {
source = "./modules/security"
vpc_id = module.networking.vpc_id
}
module "database" {
source = "./modules/rds"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
security_group = module.security.database_sg_id
}
module "application" {
source = "./modules/ecs"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
load_balancer_sg = module.security.alb_sg_id
database_endpoint = module.database.endpoint
}
2. Microservicios en Kubernetes
# EKS cluster
module "eks" {
source = "terraform-aws-modules/eks/aws"
cluster_name = "microservices-cluster"
cluster_version = "1.27"
vpc_id = module.networking.vpc_id
subnet_ids = module.networking.private_subnet_ids
node_groups = {
main = {
desired_capacity = 3
max_capacity = 10
min_capacity = 1
instance_types = ["t3.medium"]
}
}
}
# Helm releases para aplicaciones
resource "helm_release" "ingress_nginx" {
name = "ingress-nginx"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
values = [
file("${path.module}/helm-values/nginx-values.yaml")
]
}
🚀 Ejercicios prácticos
Ejercicio 1: VPC básica
- Crea un módulo de VPC con subnets públicas y privadas
- Incluye Internet Gateway y NAT Gateway
- Implementa route tables apropiadas
- Añade outputs necesarios
Ejercicio 2: Pipeline de IaC
- Configura backend remoto para Terraform
- Implementa workflow de CI/CD
- Añade security scanning
- Configura aprobaciones manuales para producción
Ejercicio 3: Multi-environment
- Estructura proyecto para dev/staging/prod
- Usa variables específicas por entorno
- Implementa módulos reutilizables
- Configura state separation
La Infraestructura como Código es fundamental para operaciones escalables y confiables. En el próximo capítulo exploraremos la gestión de configuraciones.