Saltar al contenido principal

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

  1. Crea un módulo de VPC con subnets públicas y privadas
  2. Incluye Internet Gateway y NAT Gateway
  3. Implementa route tables apropiadas
  4. Añade outputs necesarios

Ejercicio 2: Pipeline de IaC

  1. Configura backend remoto para Terraform
  2. Implementa workflow de CI/CD
  3. Añade security scanning
  4. Configura aprobaciones manuales para producción

Ejercicio 3: Multi-environment

  1. Estructura proyecto para dev/staging/prod
  2. Usa variables específicas por entorno
  3. Implementa módulos reutilizables
  4. 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.