AWS Intra-Region VPC Peering: Conectando VPCs en la Misma Región con Terraform
Published: 2024-04-27 00:35:11


## Descripción General

Una conexión de red conocida como enlace de VPC peering te permite transportar tráfico entre dos VPC utilizando direcciones IP privadas. Puedes utilizar VPC peering para implementar recursos en la nube en una red virtual definida por el usuario. Las VPC permiten la comunicación entre instancias como si estuvieran en la misma red. Estos recursos permiten un transporte más seguro de datos.

## ¿Qué es VPC Peering y Cómo Funciona?

VPC peering es un método de conexión entre dos o más nubes privadas virtuales (VPCs). Una conexión de VPC peering se define como "un enlace de red entre dos VPC que permite enviar tráfico utilizando direcciones IP privadas (IPv4 o IPv6)." Las instancias en una VPC pueden comunicarse entre sí como si estuvieran en la misma red una vez unidas mediante una conexión de VPC peering.

![vpc peering](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tgcck0kw1xq6059ytjbn.png)

## Resumen de AWS VPC Peering Connection

AWS crea una conexión de VPC peering utilizando la arquitectura actual de una VPC; no es ni una puerta de enlace ni una conexión VPN, y no requiere ningún hardware físico adicional. En la comunicación, no hay un solo punto de fallo ni limitación de ancho de banda. 💻🔗

Para establecer una conexión de VPC peering, debes realizar lo siguiente:

1. El propietario de la VPC solicitante pide al propietario de la VPC aceptadora que cree una conexión de VPC peering. Tú u otra cuenta de AWS pueden controlar la VPC aceptadora, **pero no puede tener un bloque CIDR idéntico al de la VPC solicitante**.

2. El propietario de la VPC aceptadora aprueba la solicitud de conexión de VPC peering para iniciar la conexión.

3. Cada propietario de VPC en la conexión de VPC peering debe **agregar manualmente una ruta que apunte al rango de direcciones IP de la otra VPC** en una o más de sus tablas de enrutamiento de VPC para permitir el tráfico entre las VPC utilizando direcciones IP privadas.

4. Para asegurarte de que el tráfico hacia y desde la VPC emparejada no esté restringido, **actualiza las reglas del grupo de seguridad relacionadas con tu instancia según sea necesario.** Puedes utilizar un grupo de seguridad de la VPC emparejada como origen o destino en las reglas de tu grupo de seguridad si ambas VPC están en la misma región.

5. Cuando utilizas los parámetros de conexión de VPC peering predeterminados, un nombre de host DNS público es utilizado por las instancias EC2 hacia la dirección IP pública de la instancia. Para cambiar este comportamiento, habilita la resolución de nombres DNS para tu conexión de VPC. Después de habilitar la resolución de nombres DNS, si las instancias en ambos lados de la conexión de VPC peering se dirigen mutuamente utilizando un nombre de host DNS público, el nombre de host se resuelve a la dirección IP privada de la instancia.

### Archivos Terraform

1. **001-vpc.tf**:
   - **Descripción**: Define la infraestructura de la VPC.
   - **Propósito**: Establece la red básica para la infraestructura.
   - **Funcionalidades**: Configura subredes, tablas de rutas y grupos de seguridad.

```hcl
# Definición de la primera VPC
module "vpc_a_1" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.1"

  name = "vpc-a-1-${var.env}"
  cidr = "172.16.0.0/16"
  azs  = slice(data.aws_availability_zones.available.names, 0, 1)

  private_subnets = ["172.16.0.0/24"]
  public_subnets  = ["172.16.1.0/24"]

  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  map_public_ip_on_launch = true

  public_subnet_tags = {
    orion = "public-subnet-a-1"
  }

  private_subnet_tags = {
    orion = "private-subnet-a-1"
  }
}

# Definición de la segunda VPC
module "vpc_b_2" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.1"

  name = "vpc-b-2-${var.env}"
  cidr = "10.1.0.0/16" # CIDR para la segunda VPC
  azs  = slice(data.aws_availability_zones.available.names, 0, 2)

  private_subnets = ["10.1.0.0/24"]
  public_subnets  = ["10.1.1.0/24"]

  enable_nat_gateway   = true
  single_nat_gateway   = false
  enable_dns_hostnames = true

  map_public_ip_on_launch = true

  public_subnet_tags = {
    orion = "public-subnet-b-2"
  }

  private_subnet_tags = {
    orion = "private-subnet-b-2"
  }
}
```

2. **002-sg.tf**:
   - **Descripción**: Define los grupos de seguridad.
   - **Propósito**: Controla el tráfico de red dentro de la infraestructura.
   - **Funcionalidades**: Establece reglas de seguridad para permitir o denegar el tráfico entre las vpc para este propósito.

```hcl
resource "aws_security_group" "web_sg_a_1" {
  name        = "web-security-group-a-1-${var.env}"
  description = "Permite el trafico HTTP desde el CIDR de la VPC"

  vpc_id = module.vpc_a_1.vpc_id # Reemplaza "tu-vpc-id" con el ID de tu VPC

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [module.vpc_a_1.vpc_cidr_block] # Reemplaza con el CIDR de tu VPC
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [module.vpc_b_2.vpc_cidr_block] # Reemplaza con el CIDR de tu VPC secundaria
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "web-security-group-a-1-${var.env}"
  }
}

resource "aws_security_group" "web_sg_b_2" {
  name        = "web-security-group-b-2-${var.env}"
  description = "Permite el trafico HTTP desde el CIDR de la VPC"

  vpc_id = module.vpc_b_2.vpc_id # Reemplaza "tu-vpc-id" con el ID de tu VPC

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [module.vpc_b_2.vpc_cidr_block] # Reemplaza con el CIDR de tu VPC secundaria
  }
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [module.vpc_a_1.vpc_cidr_block] # Reemplaza con el CIDR de tu VPC primaria
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "web-security-group-b-2-${var.env}"
  }
}
```

3. **003-ec2.tf**:
   - **Descripción**: Configura las instancias EC2.
   - **Propósito**: Define las características y cantidad de instancias necesarias.
   - **Funcionalidades**: Especifica tipo de instancia, AMI y reglas de seguridad, adicionalmente se aprovisiona con apache para propósitos de pruebas y conexión entre ellas por el peering.

```hcl
################################################################################
# EC2 Module
################################################################################
module "ec2_a_1" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "5.6.1"

  name                        = "ec2-vpc-a-1-${var.env}"
  instance_type               = "t3a.micro"
  subnet_id                   = element(module.vpc_a_1.private_subnets, 0)
  vpc_security_group_ids      = [aws_security_group.web_sg_a_1.id]
  user_data                   = file("apache-config.sh")
  user_data_replace_on_change = true

  create_iam_instance_profile = true
  iam_role_description        = "IAM role for EC2 instance"
  iam_role_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }
  metadata_options = {
    http_tokens = "required"
  }
}

module "ec2_b_2" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "5.6.1"

  name                        = "ec2-vpc-b-2-${var.env}"
  instance_type               = "t3a.micro"
  subnet_id                   = element(module.vpc_b_2.private_subnets, 0)
  vpc_security_group_ids      = [aws_security_group.web_sg_b_2.id]
  user_data                   = file("apache-config.sh")
  user_data_replace_on_change = true

  create_iam_instance_profile = true
  iam_role_description        = "IAM role for EC2 instance"
  iam_role_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }
  metadata_options = {
    http_tokens = "required"
  }
}
```

4. **004-peering.tf**:
   - **Descripción**: Configura el peering de VPC.
   - **Propósito**: Conecta dos VPC para la comunicación privada.
   - **Funcionalidades**: Define recursos para el peering y enrutamiento.

```hcl
resource "aws_vpc_peering_connection" "vpc_peer" {
  vpc_id      = module.vpc_a_1.vpc_id
  peer_vpc_id = module.vpc_b_2.vpc_id
  peer_region = local.aws_region
  tags = {
    Name = "Requester ${module.vpc_b_2.name}"
    Side = "Requester"
  }
}

resource "aws_vpc_peering_connection_options" "peer_options" {
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peer.id
  accepter {
    allow_remote_vpc_dns_resolution = true
  }
  requester {
    allow_remote_vpc_dns_resolution = true
  }
}

resource "aws_route" "primary_to_secondary" {
  for_each = toset(module.vpc_a_1.private_route_table_ids)

  route_table_id            = each.value
  destination_cidr_block    = module.vpc_b_2.vpc_cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peer.id
}

resource "aws_route" "secondary_to_primary" {
  for_each = toset(module.vpc_b_2.private_route_table_ids)

  route_table_id            = each.value
  destination_cidr_block    = module.vpc_a_1.vpc_cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.vpc_peer.id
}
```

### Otros Archivos

5. **apache-config.sh**:
   - **Descripción**: Script de configuración de Apache.
   - **Propósito**: Configura Apache en instancias EC2.
   - **Funcionalidades**: Define la configuración del servidor web.

```sh
#!/bin/bash
yum update -y
yum install -y httpd 
systemctl start httpd
systemctl enable httpd

TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
IPV4=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/local-ipv4)

# Crear el contenido HTML
hostname=$(hostname)
content="Deployed via Terraform\nHostname: $hostname\nIP-v4: $IPV4"

# Escribir el contenido en el archivo index.html
echo -e "$content" | sudo tee -a /var/www/html/index.html
```

6. **demo.tfvars**:
   - **Descripción**: Archivo de variables Terraform.
   - **Propósito**: Almacena valores para la ejecución de Terraform.
   - **Funcionalidades**: Define variables para personalizar la infraestructura.

```hcl
tf_version = "1.5.7"
env        = "demo"
project    = "vpc-tf"
owner      = "franco.navarro"
cost       = "0001-vpc-demo"
```

7. **main.tf**:
   - **Descripción**: Archivo principal de Terraform.
   - **Propósito**: Orquesta la creación de recursos.
   - **Funcionalidades**: Incluye otros archivos, define proveedores y recursos.

```hcl
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
data "aws_availability_zones" "available" {}
data "aws_subnets" "public" {}

data "aws_ami" "latest_linux" {
  most_recent = true

  filter {
    name   = "name"
    values = ["*amzn2-ami-hvm-*"] # Filtra por AMIs de Amazon Linux 2
  }

  filter {
    name   = "architecture"
    values = ["x86_64"] # Filtra por arquitectura x86_64 (64-bit)
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"] # Filtra por AMIs respaldadas por EBS (Elastic Block Store)
  }
}


locals {
  aws_account_id = data.aws_caller_identity.current.account_id
  aws_region     = data.aws_region.current.name
}
```

8. **variables.tf**:
   - **Descripción**: Define variables Terraform.
   - **Propósito**: Declara variables para la configuración.
   - **Funcionalidades**: Parametriza la infraestructura.

```hcl
variable "env" {
  type        = string
  description = "Environment name."
}

variable "project" {
  description = "Project Name or service"
  type        = string
}

variable "owner" {
  description = "Owner Name or service"
  type        = string
}

variable "cost" {
  description = "Center of cost"
  type        = string
}

variable "tf_version" {
  description = "Terraform version that used for the project"
  type        = string
}
```

Screeshots:

![vpcs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tqtw7eir52k2o5niua3e.png)


![peering](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r6d37nrzl310yb4s7ys5.png)


![peering description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vv5cpwvjcfjlgs5it5dd.png)


![ec2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5x8j6jsudaosvjj2jheg.png)

## Pruebas de conexión entre EC2's
Primero vemos que localmente cada ec2 tiene un servicio web que nos muestra su ip local:

![ec21](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d9dakxxj87f7dtoatuk5.png)


![ec22](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2tu8tqp6g8pl73x0wrfy.png)

Luego hacemos la consulta cruzada entre instancias y nos resuelve el curl:

![curl test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfsrbsma2rpd1n4q387z.png)


🚀 En resumen, estos archivos de Terraform proporcionan la configuración necesaria para desplegar una infraestructura en la nube de AWS. Desde la definición de la red hasta la configuración de las instancias EC2 y el establecimiento de reglas de seguridad, cada archivo desempeña un papel crucial en la construcción y gestión de la infraestructura.

💡 Si tienes alguna pregunta o sugerencia para mejorar estos archivos, no dudes en ponerte en contacto. ¡Tu colaboración es bienvenida y valorada! Juntos, podemos construir y optimizar una infraestructura sólida y eficiente en la nube.

✉️ Si estás interesado en colaborar, por favor contáctame por este medio o en [linkedin](https://www.linkedin.com/in/franconavarro/).

¡Gracias por tu interés y apoyo! 🙌

Extra:
Infracost (aprox de los recursos usados en esta demo):

![infracost](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrwscdtgcamcdwjz9oqr.png)


Destruimos los recursos:

![destroy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofzr1yucpmplr3sd3z0b.png)