Gestionando Contenedores con Terraform y Amazon ECS: Despliegue y Orquestación Escalable
Este tutorial te guiará a través del despliegue y la gestión de contenedores utilizando Amazon Elastic Container Service (ECS) y Terraform. Aprenderás a definir clústeres, servicios, definiciones de tareas y equilibradores de carga, automatizando tu infraestructura de contenedores de forma escalable y eficiente.
🚀 Introducción a Terraform y Amazon ECS
En el mundo moderno de la infraestructura como código (IaC), Terraform se ha convertido en una herramienta indispensable para aprovisionar y gestionar recursos en la nube. Cuando se combina con Amazon Elastic Container Service (ECS), una solución de orquestación de contenedores altamente escalable y fiable, puedes automatizar completamente el ciclo de vida de tus aplicaciones en contenedores.
Amazon ECS te permite ejecutar, detener y gestionar contenedores Docker en un clúster de instancias EC2 o con AWS Fargate, eliminando la necesidad de gestionar la infraestructura subyacente. Terraform, por su parte, nos permite describir la infraestructura de ECS de forma declarativa, asegurando que el estado deseado de nuestro clúster y servicios se mantenga de manera consistente.
Este tutorial te proporcionará los conocimientos y pasos prácticos para desplegar una aplicación contenedorizada completa en Amazon ECS utilizando Terraform, desde la configuración del clúster hasta el despliegue del servicio y el balanceador de carga.
🎯 Objetivos del Tutorial
Al finalizar este tutorial, serás capaz de:
- Configurar un proveedor de AWS en Terraform.
- Crear una Virtual Private Cloud (VPC) con subredes públicas.
- Aprovisionar un clúster de Amazon ECS.
- Definir una tarea ECS y un servicio ECS.
- Desplegar un Application Load Balancer (ALB) para distribuir el tráfico.
- Actualizar y destruir tu infraestructura de ECS usando Terraform.
🛠️ Requisitos Previos
Antes de empezar, asegúrate de tener lo siguiente:
- Cuenta de AWS: Acceso a una cuenta de AWS con credenciales configuradas (AWS CLI o variables de entorno).
- Terraform: Versión 1.0 o superior instalada. Puedes descargarlo desde terraform.io.
- Docker: Para construir y subir imágenes si vas a usar una imagen personalizada.
- Conocimientos básicos: Familiaridad con AWS, Docker y Terraform es útil, pero el tutorial está diseñado para ser accesible.
¿Cómo configurar las credenciales de AWS?
Puedes configurar las credenciales de AWS de varias maneras:- Variables de entorno:
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"yexport AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY". - AWS CLI: Ejecuta
aws configurey sigue las instrucciones. - Archivos de credenciales: El proveedor de Terraform buscará
~/.aws/credentialsy~/.aws/config.
📖 Paso 1: Configuración Inicial de Terraform y Proveedor AWS
Comenzaremos configurando nuestro directorio de Terraform y definiendo el proveedor de AWS.
Crea un nuevo directorio para tu proyecto y, dentro de él, un archivo llamado main.tf.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = "~> 1.0"
}
provider "aws" {
region = "us-east-1" # Puedes cambiar la región a tu preferencia
}
Inicializa Terraform para descargar el proveedor de AWS:
terraform init
Deberías ver un mensaje de éxito indicando que Terraform ha sido inicializado.
🌐 Paso 2: Creación de la Infraestructura de Red (VPC)
Para que ECS funcione, necesitamos una red virtual donde desplegar nuestros contenedores. Crearemos una VPC, subredes públicas, una tabla de ruteo y una puerta de enlace a internet.
Agrega lo siguiente a main.tf (o crea un nuevo archivo network.tf para modularizar):
# network.tf (o en main.tf)
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "ecs-terraform-vpc"
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "ecs-terraform-igw"
}
}
# Public Subnets
resource "aws_subnet" "public" {
count = 2 # Creamos dos subredes públicas para alta disponibilidad
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index}.0/24"
map_public_ip_on_launch = true
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "ecs-terraform-public-subnet-${count.index}"
}
}
# Routing Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "ecs-terraform-public-rtb"
}
}
# Route Table Associations
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# Data Source for Availability Zones
data "aws_availability_zones" "available" {
state = "available"
}
Este bloque define una VPC, un Internet Gateway para conectividad externa, dos subredes públicas distribuidas en diferentes zonas de disponibilidad para resiliencia, y una tabla de ruteo que dirige todo el tráfico saliente al Internet Gateway.
🏗️ Paso 3: Creación del Clúster ECS
El clúster ECS es el componente lógico donde se ejecutarán tus tareas. No contiene instancias EC2 directamente, sino que es un agrupamiento de recursos que pueden ser EC2 o Fargate.
Agrega lo siguiente a tu archivo de configuración (por ejemplo, ecs.tf):
# ecs.tf (o en main.tf)
# ECS Cluster
resource "aws_ecs_cluster" "main" {
name = "ecs-terraform-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
tags = {
Name = "ecs-terraform-cluster"
}
}
# IAM Role for ECS Task Execution
resource "aws_iam_role" "ecs_task_execution_role" {
name = "ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
Aquí estamos creando un clúster ECS llamado ecs-terraform-cluster y habilitando Container Insights para monitoreo. También definimos un rol IAM (ecs-task-execution-role) que las tareas de ECS utilizarán para extraer imágenes de ECR, enviar logs a CloudWatch, etc. Este rol es crucial para el funcionamiento de tus contenedores.
📦 Paso 4: Definición de la Tarea ECS
Una definición de tarea es como un blueprint para tu aplicación en contenedores. Especifica la imagen Docker a usar, los recursos (CPU, memoria), puertos, variables de entorno, etc.
Agrega lo siguiente a ecs.tf (o un nuevo archivo task.tf):
# task.tf (o en ecs.tf)
# ECS Task Definition
resource "aws_ecs_task_definition" "app" {
family = "my-webapp"
cpu = "256" # 0.25 vCPU
memory = "512" # 512 MB
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"] # Usaremos Fargate para simplificar
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
# task_role_arn = aws_iam_role.ecs_task_role.arn # Opcional: para permisos de la app
container_definitions = jsonencode([
{
name = "nginx-container"
image = "nginx:latest"
cpu = 256
memory = 512
essential = true
portMappings = [
{
containerPort = 80
hostPort = 80
protocol = "tcp"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/my-webapp"
"awslogs-region" = "${data.aws_region.current.name}"
"awslogs-stream-prefix" = "ecs"
}
}
}
])
tags = {
Name = "my-webapp-task"
}
}
# CloudWatch Log Group for ECS Task Logs
resource "aws_cloudwatch_log_group" "ecs_logs" {
name = "/ecs/my-webapp"
retention_in_days = 7
}
data "aws_region" "current" {}
En esta definición, especificamos:
family: Un nombre lógico para agrupar revisiones de tareas.cpuymemory: Recursos asignados a la tarea (en Fargate, estos se aplican a la tarea completa, no solo a un contenedor).network_mode:awsvpces el modo recomendado para Fargate, dando a cada tarea su propia interfaz de red.requires_compatibilities: IndicamosFARGATE.execution_role_arn: El rol IAM que creamos anteriormente.container_definitions: Un JSON que describe los contenedores. Aquí usamos una imagennginx:latest, mapeamos el puerto 80 y configuramos el log driver para enviar logs a CloudWatch.
🚦 Paso 5: Despliegue del Application Load Balancer (ALB)
Un ALB es fundamental para distribuir el tráfico entrante a tus contenedores y proporcionar alta disponibilidad. Crearemos un ALB, un grupo de destino y un listener.
Agrega lo siguiente a un archivo alb.tf:
# alb.tf
# Security Group for ALB
resource "aws_security_group" "alb" {
name = "alb-sg"
description = "Allow HTTP/S inbound traffic to ALB"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Optional: For HTTPS
# 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 = "alb-sg"
}
}
# Application Load Balancer
resource "aws_lb" "main" {
name = "ecs-terraform-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = [for subnet in aws_subnet.public : subnet.id]
enable_deletion_protection = false # Set to true for production
tags = {
Name = "ecs-terraform-alb"
}
}
# Target Group
resource "aws_lb_target_group" "main" {
name = "ecs-terraform-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
target_type = "ip" # Para Fargate
health_check {
path = "/"
protocol = "HTTP"
matcher = "200-299"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
}
tags = {
Name = "ecs-terraform-tg"
}
}
# Listener
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main.arn
}
}
Aquí estamos creando:
- Un
aws_security_groupque permite el tráfico HTTP (puerto 80) desde cualquier IP al ALB. - Un
aws_lbde tipoapplicationexpuesto a internet (internal = false) y asociado a nuestras subredes públicas. - Un
aws_lb_target_groupcontarget_type = "ip", que es lo adecuado para Fargate, y configuramos un health check básico para el/. - Un
aws_lb_listeneren el puerto 80 que reenvía todo el tráfico al grupo de destino.
⚙️ Paso 6: Creación del Servicio ECS
El servicio ECS es responsable de mantener un número deseado de instancias de la definición de tarea en ejecución, manejando despliegues, autoescalado y balanceo de carga.
Agrega lo siguiente a ecs.tf:
# ecs.tf (adicional)
# Security Group for ECS Tasks (Fargate)
resource "aws_security_group" "ecs_task" {
name = "ecs-task-sg"
description = "Allow inbound from ALB and outbound all for ECS Tasks"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id] # Solo permite tráfico del ALB
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "ecs-task-sg"
}
}
# ECS Service
resource "aws_ecs_service" "app" {
name = "my-webapp-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2 # Número de tareas deseadas
launch_type = "FARGATE"
network_configuration {
subnets = [for subnet in aws_subnet.public : subnet.id]
security_groups = [aws_security_group.ecs_task.id]
assign_public_ip = true # Necesario para conectividad a internet si no hay NAT Gateway
}
load_balancer {
target_group_arn = aws_lb_target_group.main.arn
container_name = "nginx-container"
container_port = 80
}
depends_on = [
aws_lb_listener.http # Asegura que el listener del ALB esté listo
]
tags = {
Name = "my-webapp-service"
}
}
Aquí estamos definiendo:
- Un
aws_security_grouppara nuestras tareas ECS que solo permite el tráfico entrante desde el ALB en el puerto 80 y permite todo el tráfico saliente. Esto es una buena práctica de seguridad. - Un
aws_ecs_serviceque usa nuestro clúster (aws_ecs_cluster.main), nuestra definición de tarea (aws_ecs_task_definition.app) y desea mantener 2 instancias de la tarea en ejecución (desired_count = 2). - Configuramos la
network_configurationpara usar nuestras subredes públicas y el security group de la tarea, yassign_public_ip = truepara que las tareas puedan comunicarse con internet (por ejemplo, para descargar la imagen de Docker). Si tu VPC tuviera NAT Gateway y subredes privadas, las tareas irían en las privadas y no necesitarían IP pública. - Finalmente, asociamos el servicio con el
aws_lb_target_group.mainque creamos, indicando el nombre del contenedor y el puerto a mapear.
✅ Paso 7: Despliegue de la Infraestructura
Ahora que tenemos todos nuestros archivos de configuración (main.tf, network.tf, ecs.tf, alb.tf), podemos ejecutar Terraform para desplegar la infraestructura.
- Formatear y Validar:
terraform fmt
terraform validate
Esto asegura que tu código Terraform está bien formateado y libre de errores de sintaxis.
2. Revisar el Plan de Ejecución:
terraform plan
Este comando te mostrará una vista previa de todos los recursos que Terraform creará, modificará o destruirá. **¡Revisa cuidadosamente esta salida antes de proceder!**
3. Aplicar la Configuración:
terraform apply
Terraform te pedirá confirmación. Escribe `yes` y presiona <kbd>Enter</kbd> para iniciar el aprovisionamiento. Este proceso puede tardar varios minutos.
Una vez que terraform apply finalice, verás un mensaje de éxito. Terraform te proporcionará las salidas si las has definido.
Para ver la URL de tu ALB, agrega una sección outputs.tf:
# outputs.tf
output "alb_dns_name" {
description = "The DNS name of the Load Balancer"
value = aws_lb.main.dns_name
}
output "ecs_cluster_name" {
description = "The name of the ECS cluster"
value = aws_ecs_cluster.main.name
}
Después de aplicar, ejecuta terraform output para ver la DNS de tu ALB y acceder a tu aplicación Nginx.
terraform output alb_dns_name
Copia la URL obtenida y pégala en tu navegador. Deberías ver la página de bienvenida predeterminada de Nginx.
Éxito
🔄 Paso 8: Actualización de la Infraestructura
Una de las mayores ventajas de Terraform es la facilidad para actualizar la infraestructura. Por ejemplo, si quisiéramos cambiar la imagen de Nginx o la cantidad de memoria asignada a la tarea:
Modifica la sección container_definitions en aws_ecs_task_definition.app:
# ... en aws_ecs_task_definition.app
container_definitions = jsonencode([
{
name = "nginx-container"
# Cambia la imagen a una versión específica
image = "nginx:1.21.6" # Antes era nginx:latest
cpu = 256
memory = 512
essential = true
portMappings = [
{
containerPort = 80
hostPort = 80
protocol = "tcp"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/my-webapp"
"awslogs-region" = "${data.aws_region.current.name}"
"awslogs-stream-prefix" = "ecs"
}
}
}
])
# ...
Guarda el archivo y ejecuta nuevamente:
terraform plan
terraform apply
Terraform detectará el cambio en la definición de la tarea y creará una nueva revisión. El servicio ECS realizará un rolling update para reemplazar las tareas antiguas con las nuevas, sin tiempo de inactividad si tienes suficientes instancias.
🗑️ Paso 9: Limpieza de Recursos
Para evitar cargos inesperados y mantener tu cuenta de AWS limpia, es crucial destruir los recursos que ya no necesitas. Terraform facilita este proceso.
Desde el directorio raíz de tu proyecto Terraform, ejecuta:
terraform destroy
Terraform te mostrará un plan de los recursos que se eliminarán. Escribe yes y presiona Enter para confirmar.
El proceso de destrucción puede tomar varios minutos. Una vez finalizado, todos los recursos de ECS, ALB, VPC y IAM que creaste con este tutorial serán eliminados de tu cuenta de AWS.
💡 Consideraciones Adicionales y Buenas Prácticas
- Estado Remoto: Para entornos de equipo o producción, el estado de Terraform (
terraform.tfstate) debe gestionarse de forma remota (por ejemplo, en un bucket S3 con bloqueo de DynamoDB) para evitar conflictos y corrupción del estado. Ver Gestionando Estado Remoto con Terraform: S3 y DynamoDB para Colaboración y Resiliencia. - Módulos de Terraform: A medida que tu infraestructura crece, considera organizar tu código en módulos para mejorar la reusabilidad y la organización.
- Autoescalado: Explora cómo configurar el autoescalado para tu servicio ECS (tanto horizontalmente con Service Auto Scaling como verticalmente ajustando CPU/memoria) para manejar cargas variables.
- CI/CD: Integra Terraform en tu pipeline de CI/CD para automatizar los despliegues y las actualizaciones de infraestructura.
- Variables: Utiliza variables de Terraform para hacer tu configuración más flexible y reutilizable (
variables.tf). - Redes Privadas: Para entornos de producción, se recomienda usar subredes privadas para las tareas de ECS y un NAT Gateway para la salida a internet, en lugar de asignar IPs públicas directamente a las tareas.
- HTTPS y Certificados: Para una aplicación en producción, configura HTTPS en tu ALB y asocia un certificado SSL/TLS (gestionado por AWS Certificate Manager) con tu listener.
¿Por qué es importante el estado remoto de Terraform?
El archivo de estado de Terraform (`terraform.tfstate`) es una instantánea de la infraestructura que Terraform está gestionando. Si trabajas en equipo y cada persona tiene su propio archivo de estado local, los despliegues pueden sobrescribirse mutuamente o causar inconsistencias. Al usar un estado remoto centralizado, Terraform puede coordinar mejor los cambios y ofrecer características como el bloqueo de estado para evitar ediciones simultáneas.🔚 Conclusión
Has completado un tutorial exhaustivo sobre cómo desplegar y gestionar una aplicación contenedorizada en Amazon ECS utilizando Terraform. Hemos cubierto la configuración de la red, el clúster ECS, las definiciones de tareas, los servicios y el balanceador de carga, demostrando el poder de la infraestructura como código para la orquestación de contenedores.
Terraform te proporciona el control y la automatización necesarios para construir y escalar tu infraestructura de contenedores de manera eficiente y reproducible. ¡Ahora estás listo para aplicar estos conocimientos a tus propios proyectos y llevar tus despliegues de contenedores al siguiente nivel!
Tutoriales relacionados
- Gestionando Estado Remoto con Terraform: S3 y DynamoDB para Colaboración y Resilienciaintermediate20 min
- Migrando Infraestructura Existente a Terraform: Guía Completa de Importaciónintermediate18 min
- Automatización de la Configuración de Kubernetes con Terraform: Un Enfoque Declarativointermediate15 min
- Gestionando Redes y Conectividad con Terraform y AWS VPC: Una Guía Esencialintermediate20 min
- Gestionando Configuración de Aplicaciones y Secrets con Terraform y AWS Parameter Storeintermediate20 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!