En este post, exploraremos cómo implementar infraestructura en AWS utilizando Terraform, desplegando componentes esenciales como una VPC, Subnets, Route Tables, un Internet Gateway y una instancia EC2 en cada subred. Aprenderás a definir y gestionar estos recursos de manera automatizada, asegurando una configuración escalable y reproducible en la nube. ¡Vamos a construir nuestra infraestructura como código paso a paso!
Pasos: Crear la VPC, Subnet Pública con su rango de IPs, Crear una Subnet Privada con su rango de IPs, Crear un Internet Gateway, Crear una Instancia EC2 dentro de la Subnet Pública, Crear una Instancia EC2 dentro de la Subnet Privada, Crear la Table de Rutas de ambas subredes, Configurar un VPC Endpoint. El VPC Endpoint ayudará a la Instancia EC2 que se encuentra en la subred Privada a comunicarse con un Bucket S3 que reside en la cuenta de AWS. Crear los archivos y directorios
Crear la VPC
crear el archivo main.tf Dentro del main.tf crear un modulo para Networking y definir las variables para el cidr, nombre de la vpc, cidr de la subred publica, cidr de la subred privada, y la variable de la zona de disponiblidad
En el modulo podemos ver un directorio source = “./networking” y tenemos la carpeta networking. Dentro del directorio networking esta el archivo main.tf que contiene variables de salida y entre otras cosas la configuración de la VPC donde se requiere asignarle el rango de ip 11.0.0.0/16. Allí mismo definimos la VPC donde le pasamos la variable cidr_block = var.vpc_cidr que aun no hemos creado.
Debemos crear el archivo terraform.tfvars también en la raíz del proyecto. Allí definimos los valores de las variables.
Dentro de terraform.tfvars le asignamos el valor a la variable -> vpc_cidr = “11.0.0.0/16” y de el resto de valores de las variables. Como queremos crear 2 subredes publicas y 2 subredes privadas enviamos los valores en forma de lista, asi como las 2 zonas de disponiblidad.
En este momento podriamos hacer un terraform init. Es el primer comando que debes ejecutar cuando comienzas un nuevo proyecto en Terraform, y lo que hace es Descargar proveedores (providers) y los instala cuando están definidos en el archivo main.tf
Abrimos la terminal, nos aseguramos de estar en la misma carpeta del proyecto y ejecutamos:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
terraform init
terraform init
terraform init
Una vez terminado el proceso, se mostrara el mensaje Terraform has been successfully initialized!
Ahora ejecutamos el comando:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
terraform plan
terraform plan
terraform plan
terraform plan analiza tu configuración (.tf) y el estado actual de la infraestructura (en el archivo terraform.tfstate) y muestra un resumen de lo que va a hacer antes de aplicar.
Recibimos el resumen de lo que terraform hará y si todo esta conforme a lo solicitado, procedemos con el comando:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
terraform apply -auto-approve
terraform apply -auto-approve
terraform apply -auto-approve
El comando terraform apply es el paso final del flujo principal de Terraform. Después de terraform init y terraform plan, este comando se encarga de crear, modificar o destruir recursos en tu proveedor de infraestructura (como AWS, Azure, GCP, etc.), según lo definido en tus archivos .tf.
Deberas tener configuradas tus credenciales de acceso a AWS desde la terminal. (aws configure)
Ten cuidado con el parametro -auto-approve, es un modificador para terraform apply que le dice a Terraform que ejecute el plan sin pedir confirmación del usuario.
Crea múltiples subnets según la cantidad de CIDR definidos en la variable var.cidr_public_subnet. count es una meta-argumento de Terraform que permite hacer una cantidad dinámica de recursos. length(var.cidr_public_subnet) determina cuántas subnets crear. Por ejemplo, si cidr_public_subnet tiene 3 elementos, se crearán 3 subnets.
Asigna un rango CIDR específico a cada subnet. element() toma un valor de la lista var.cidr_public_subnet usando el índice actual (count.index). Esto asegura que cada subnet tenga un rango distinto, como: [“11.0.1.0/24”, “11.0.2.0/24”, “11.0.3.0/24”].
Revisar y aplicar
terraform plan terraform apply.
Ve a AWS -> VPC y mira la magia -> Se creará una Route Table por defecto.
Crear Internet Gateway
Deberá ser adjuntada a la VPC En el main.tf del modulo networking ->
En la Public Route Table, se debe adjuntar el ID de la VPC utilizando:
vpc_id = aws_vpc.dev_proj_1_vpc_us_east_1.id
Además, en el CIDR block, se debe definir el rango de salida hacia y desde internet como 0.0.0.0/0 Finalmente, es necesario adjuntar el ID del Internet Gateway. En el caso de la Private Route Table de la subred privada, es mas sencillo porque no requiere internet Gateway.
Asociar las Route Table a las subnets en el mismo main.tf del módulo Networking
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Public Route Table and Public Subnet Association
Las instancias EC2 requieren: el id de una ami, el id de la VPC, un id de subred, un security group, y un public_key. Para generar un par de llaves se puede hacer con ssh-keygen y luego pasar las credenciales manualmente a terraform en terraform.tfvars.
Se debe tener en cuenta que las instancias t2.medium tienen costo. No forma parte del free tier.
Asignar una subred a la instancia ec2 en la subred pública. subnet_id = tolist(module.networking.dev_proj_1_public_subnets_ids )[0] Acá se hace una lista de las subredes publicas que se están creando, luego se toma el primer elemento de la lista y se asigna el id a la instancia EC2. Asignar una subred a la instancia ec2 en la subred privada.
La instancia EC2 en la subred pública debe tener la opcion enable_public_ip_address = true mientras que la instancia EC2 en la subred debe colocarse en false ya que no es necesario.
Crear el Security Group
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
module "security_group"{
source = "./security-groups"
ec2_sg_name = "SG for EC2 to enable SSH(22), HTTPS(443), HTTP(8080) and HTTP(80)"
vpc_id = module.networking.dev_proj_1_vpc_id
}
module "security_group" {
source = "./security-groups"
ec2_sg_name = "SG for EC2 to enable SSH(22), HTTPS(443), HTTP(8080) and HTTP(80)"
vpc_id = module.networking.dev_proj_1_vpc_id
}
module "security_group" {
source = "./security-groups"
ec2_sg_name = "SG for EC2 to enable SSH(22), HTTPS(443), HTTP(8080) and HTTP(80)"
vpc_id = module.networking.dev_proj_1_vpc_id
}
Cada modulo debe tener su respectiva carpeta con el nombre del recurso
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
source = "./ec2"
source = "./security-groups"
source = "./ec2"
source = "./security-groups"
source = "./ec2"
source = "./security-groups"
En la carpeta ec2 -> main.tf creamos las variables y creamos las instancias EC2
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
variable "ami_id"{}
variable "instance_type"{}
variable "tag_name"{}
variable "public_key"{}
variable "subnet_id"{}
variable "sg_for_EC2"{}
variable "enable_public_ip_address"{}
variable "user_data_install_jenkins"{
default = ""
}
output "jenkins_ec2_instance_ip"{
value = aws_instance.jenkins_ec2_instance_ip.id
}
output "dev_proj_1_ec2_instance_public_ip"{
value = aws_instance.jenkins_ec2_instance_ip.public_ip
Se debe tener un par de llaves y se deben asignar a la instancia EC2 para poder acceder a ellas por ssh.
En el archivo variables.tf agregamos las sigientes líneas:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
variable "public_key"{
type = string
description = "Project 1 Public key for EC2 instance"
}
variable "ec2_ami_id"{
type = string
description = "Project 1 AMI Id for EC2 instance"
}
variable "public_key" {
type = string
description = "Project 1 Public key for EC2 instance"
}
variable "ec2_ami_id" {
type = string
description = "Project 1 AMI Id for EC2 instance"
}
variable "public_key" {
type = string
description = "Project 1 Public key for EC2 instance"
}
variable "ec2_ami_id" {
type = string
description = "Project 1 AMI Id for EC2 instance"
}
También agregamos la variable para la AMI.
En el archivo terraform.tfvars se indican los valores estas dos variables.
Creamos una llave publica. Abrimos una linea de comandos con bash. Vamos a la dirección donde queremos guardar el par de llaves. Ejecutamos el comando:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ssh-keygen
ssh-keygen
ssh-keygen
Le indicamos en que carpeta queremos que guarde las llaves generadas y le damos un nombre. Por ejemplo:
/c/Users/Usuario/.ssh/terraform-vpc-keys
Presionamos enter y dejamos el resto de valores por defecto.
Verificamos que esten la llave privada y la llave publica.
Copiamos la llave publica
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cat terraform-vpc-keys.pub
cat terraform-vpc-keys.pub
cat terraform-vpc-keys.pub
Pegamos el contenido total del archivo en public_key = “ssh-rsa AAAAB3NzaC1yc2EAABAAABgQ……….”
Ya tenemos configurada la llave publica en la instancia EC2 para el acceso.
Ahora, vamos a la carpeta security-groups->main.tf
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
variable "ec2_sg_name"{}
variable "vpc_id"{}
output "sg_ec2_sg_ssh_http_id"{
value = aws_security_group.ec2_sg_ssh_http.id
}
output "sg_ec2_jenkins_port_8080"{
value = aws_security_group.ec2_jenkins_port_8080.id
}
resource "aws_security_group""ec2_sg_ssh_http"{
name = var.ec2_sg_name
description = "Enable the Port 22(SSH) & Port 80(http)"
vpc_id = var.vpc_id
# ssh for terraform remote exec
ingress {
description = "Allow remote SSH from anywhere"
cidr_blocks = ["0.0.0.0/0"]
from_port = 22
to_port = 22
protocol = "tcp"
}
# enable http
ingress {
description = "Allow HTTP request from anywhere"
cidr_blocks = ["0.0.0.0/0"]
from_port = 80
to_port = 80
protocol = "tcp"
}
# enable http
ingress {
description = "Allow HTTP request from anywhere"
cidr_blocks = ["0.0.0.0/0"]
from_port = 443
to_port = 443
protocol = "tcp"
}
#Outgoing request
egress {
description = "Allow outgoing request"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "Security Groups to allow SSH(22) and HTTP(80)"