Skip to content

AWS VPC (Virtual Private Cloud)

Si tuvieras tu propia infraestructura, tendrías un centro de datos con toda la arquitectura de una red que debe ser administrada y con ciertos servicios o recursos que se publican a través de Internet. Cuando pensamos en la nube de AWS no disponemos de una red visible en la cual accedemos a nuestros aparatos y servidores sino que se proporciona una red virtual en la nube o VPC (Virtual Private Cloud). De este modo se aislan los recursos disponibles para nuestra red en la nube.

Imagen tomada de https://docs.aws.amazon.com

Para comprender los elementos claves de una VPC debemos considerar los siguientes conceptos:

  • Region: Corresponde a la división y sección definida por AWS para disponer de la infraestructura cercana a nuestra ubicación.
  • Zonas de disponibilidad (AZ - Availability Zone): Son zonas que se encuentran dentro de la misma región definida, obteniendo por ejemplo, Zona 1 y Zona 2.
  • Subredes: Si comprendemos que una VPC es una red en la nube, AWS nos proporciona también subredes, podemos definir una subred de tipo pública y privada. De este modo, una página web estaría en un recurso que corresponda la subred pública (la cual tiene salida por medio de un Gateway de Internet), mientras en la subred privada podemos colocar nuestra base de datos.
  • Instancias de Computo: Correspondería a nuestro mecanismo de computo, semejante a una máquina virtual o VPS (Virtual Private Server), sobre la cual se basan varios servicios de AWS. Las instancias corren en una subred de nuestra VPC.

Una VPC estaría en una sola región y es transversal a las subredes, es decir, mi VPC puede conformarse por 2 zonas de disponibilidad y tener cada una de éstas 2 subredes, en las cuales puede variar el número de instancias de computo. Se puede definir otra VPC para otras regiones.

Otros conceptos claves más avanzados que podemos encontrar cuando definimos instancias de computo en nuestra VPC serían los siguientes:

  • Internet Gateway: Se trata del componente que encamina la salida a Internet para exponer componentes que se encuentra en una red privada virtual pública.
  • Classless Inter-Domain Routing (CIDR): Se refiere a los rangos asignados para establecer una serie de números de IP (Internet Protocol) a los dispositivos o servicios de nuestra red privada virtual.
  • NAT Gateway: Cuando tenemos una subred privada es neceario el redireccionamiento entre subredes mediante un NAT Gateway.
  • Network ACLs: Puede verse como un Firewall con reglas a nivel de subred.
  • Security Groups: Puede verse como un Firewall con reglas a nivel de instancia de computo. Por ejemplo, para abrir el puerto de un servicio web (80, 443) y exponerlo a Internet.

Imagen tomada de https://docs.aws.amazon.com

Puedes administrar t VPC desde la consola web de AWS con la opción VPC Dashboard.

Ejemplo de VPC con CloudFormation

El siguiente contenido nos puede servir para preparar la configuración de Networking (red) en la nube de AWS que se organiza a partir de una VPC (Virtual Private Cloud). Esta configuración se puede definr con AWS Cloudformation.

Para ello, abrimos un nuevo archivo, por ejemplo cf-vpc-net.yaml, y copiamos dentro cada trozo o bloque a continuación. Comenzaremos por definir el encabezado con los atributos AWSTemplateFormatVersion y Description, así:

yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Network Stack for AWS Services'

A continuación especificamos parámetros usando el atributo Parameters de la siguiente manera:

yaml
Parameters:
  EnvironmentName:
    Description: An environment name that is used in resource names
    Type: String
    Default: Lab

  VpcCIDR:
    Description: The IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1CIDR:
    Description: The IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  PublicSubnet2CIDR:
    Description: The IP range (CIDR notation) for the public subnet in the second Availability Zone
    Type: String
    Default: 10.0.2.0/24

  PrivateSubnet1CIDR:
    Description: The IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24

  PrivateSubnet2CIDR:
    Description: The IP range (CIDR notation) for the private subnet in the second Availability Zone
    Type: String
    Default: 10.0.4.0/24

Nótese que cada parámetro se define bajo su propio nombre con propiedades como Description y Type (ejemplo: String).
Es conveniente un valor por defecto (usando Default) de ser posible.

Iniciamos el bloque Resources definiendo el recurso para la VPC (de tipo AWS::EC2::VPC), es decir, asignamos un nombre lógico o etiqueta para una nube privada virtual (que comprenderá componentes de red en AWS)...

yaml
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub VPC-${EnvironmentName}

Semejante a Parameters, cada recurso se define bajo su propio nombre y tendrá unas propiedades dependiendo del Type.
La propiedad CidrBlock tendrá el valor referido en el parámetro VpcCIDR y por defecto sería 10.0.0.0/16.

Nótese que encontramos la función intrínseca !Ref que se usa para referenciar recursos o parámetros a partir del nombre especificado. También encontramos la función !Sub que se usa para sustituir expresiones que incluyen variables reflejando su valor, por ejemplo el uso de ${EnvironmentName}

Continuamos con el recurso para Internet Gateway (puerta de enlace a internet), el cual da paso a la comunicación de la VPC hacia Internet...

yaml
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub IGW-${EnvironmentName}

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

No solo se crea el recurso de tipo AWS::EC2::InternetGateway sino que también es necesario asociarlo a la VPC (asignando en VpcId el valor obtenido en el recurso VPC)

Podemos definir inicialmente una tabla de enrutamiento con una ruta por defecto para establecer conectividad de red en la VPC. Esto permitirá que instancias en una subred pública puedan tener acceso a Internet a través de la puerta de enlace a internet (Internet Gateway). Veamos:

yaml
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

Nótese que PublicRouteTable es de tipo AWS::EC2::RouteTable y se estable como ruta DefaultPublicRoute de tipo AWS::EC2::Route cuyo DestinationCidrBlock sería 0.0.0.0/0
DependsOn se usa para indicar una dependencia, en este caso con InternetGatewayAttachment

En una VPC es común tener al menos dos subredes de tipo público y dos subredes de tipo privado, dado que cada una se ubicaría en un AZ (Zona de disponibilidad) de la Region (una región puede brindar dos zonas de disponibilidad). Ilustraremos esto en Cloudformation con el siguiente bloque...

yaml
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet (AZ1)

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Subnet (AZ2)

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet (AZ1)

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      CidrBlock: !Ref PrivateSubnet2CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Subnet (AZ2)

Además de asignar VpcId es clave el atributo AvailabilityZone (que se resuelve obteniendo AZs) y CidrBlock, cuyos valores pueden ser por defecto 10.0.1.0/24, 10.0.2.0/24, 10.0.3.0/24 y 10.0.4.0/24 repectivamente.

Será necesario incorporar algún NAT (Network Address Translation), el cual actua como traductor entre redes privadas (sin acceso al público) y publicas (con acceso a Internet) tomando la IP privada de una red para trasladarla en una IP pública cuando se tiene una comunicación hacia el mundo (sin exponer la IP privada al público). Se pueden crear 2 NATs para cada subred pública y por tanto 2 IP públicas fijas (EIP) así:

yaml
  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway2EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2

!GetAtt permite recuperar el valor de la propiedad de un recurso, en este caso NatGateway1EIP.AllocationId y NatGateway2EIP.AllocationId para asignarlo a la propieadad AllocationId.
NatGateway1EIP y NatGateway2EIP son de tipo AWS::EC2::EIP y se refieren al servicio EIP (IP v4 fijas). Podría simplificarse a un EIP y NAT si hablamos de entornos No productivos.

Para dirigir el tráfico dentro de una VPC se crean tablas de enrrutamiento que habilitan destinos externos. Es necesario además asociar estas a las subredes a la ruta por defecto, que involucra el Internet Gateway (o un NAT en el caso de subredes privadas).

Veamos el escenario para asociar las dos subredes públicas involucrando la tabla de enrutamiento del Internet Gateway...

yaml
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

Este bloque de código se asocia con uno anterior en el que definimos previamente el PublicRouteTable para InternetGateway (podríamos organizar este código dejando estas definiciones conjuntamente con la asociada anteriormente)

Ahora veamos el escenario para las subredes privadas involucrando un NAT...

yaml
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes (AZ1)

  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes (AZ2)

  DefaultPrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

En este caso, PrivateRouteTable1 y PrivateRouteTable2 tendrían un DefaultPrivateRoute1 y DefaultPrivateRoute2 (respectivamente) en donde se referencia un NAT en NatGatewayId y cuyo DestinationCidrBlock sería 0.0.0.0/0

Para retornar ciertos valores de recursos que intervienen en el Stack (Pila), en el bloque final del archivo usaremos bajo el atributo Outputs un contenido como el siguiente...

yaml
Outputs:
  VPC:
    Description: A reference to the created VPC
    Value: !Ref VPC
    Export:
      Name: !Sub VPCID-${EnvironmentName}

  PublicSubnets:
    Description: A list of the public subnets
    Value: !Join [ ",", [ !Ref PublicSubnet1, !Ref PublicSubnet2 ]]
    Export:
      Name: !Sub PUBLIC-SUBNETS-${EnvironmentName}

  PrivateSubnets:
    Description: A list of the private subnets
    Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ]]
    Export:
      Name: !Sub PRIVATE-SUBNETS-${EnvironmentName}

  PublicSubnet1:
    Description: A reference to the public subnet in the 1st Availability Zone
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub PUBLIC-SUBNET-AZ1-${EnvironmentName}

  PublicSubnet2:
    Description: A reference to the public subnet in the 2nd Availability Zone
    Value: !Ref PublicSubnet2
    Export:
      Name: !Sub PUBLIC-SUBNET-AZ2-${EnvironmentName}

  PrivateSubnet1:
    Description: A reference to the private subnet in the 1st Availability Zone
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub PRIVATE-SUBNET-AZ1-${EnvironmentName}

  PrivateSubnet2:
    Description: A reference to the private subnet in the 2nd Availability Zone
    Value: !Ref PrivateSubnet2
    Export:
      Name: !Sub PRIVATE-SUBNET-AZ2-${EnvironmentName}

!Join es usado para unir valores de una lista y expresarlos en una cadena usando un separador (,)

Finalmente, podemos crear un Stack (Pila) ejecutando un comando como el siguiente:

bash
aws cloudformation create-stack \
--stack-name my-vpc-net \
--template-body file://cf-vpc-net.yaml \
--region us-east-1

my-vpc-net se refiere al nombre asignado al Stack (Pila) y cf-vpc-net.yaml se refire el archivo elaborado. us-east-1 sería la región respectiva.
Se podrían incorporar parámetros indicando por ejemplo: --parameters ParameterKey=EnvironmentName,ParameterValue=Test

Si se requiere deshacer el Stack (Pila), se puede ejecutar lo siguiente:

bash
aws cloudformation delete-stack  --stack-name my-vpc-net --region us-east-1

Ejemplo ajustado para VPC de pruebas

Para reducir costos, por usar doble EIP y NAT, cuando se trata de un entorno transitorio o de prueba se puede considerar la siguiente configuración (reemplazando los atributos respectivos)...

yaml
  NatGatewayEIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP.AllocationId
      SubnetId: !Ref PublicSubnet1

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes (AZ1)

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes (AZ2)

  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  DefaultPrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

Tanto EIP como NatGateway aún pueden generar algún costo. Para evitar costos en un entorno de pruebas o laboratorio, se puede evitar el uso de EIP y remover el NatGateway siempre y cuando No se necesiten que en las redes privadas se tenga acceso a Internet. Y es que lo común es que se tenga acceso a Internet en los recursos de una subred pública y No en una subred privada (salvo excepciones como bases de datos). Siendo así el bloque de configuración anterior quedaría simplificado de la siguiente manera:

yaml
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes (AZ1)

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Private Routes (AZ2)

En este último escenario las subredes privadas No tendrían acceso a Internet por lo que no aplican los recursos: NatGatewayEIP, NatGateway, DefaultPrivateRoute1, DefaultPrivateRoute1