AWS VPC peering in CloudformationThu, 02 Apr 2020
I was in the process of migrating a mobile backend application into a custom Amazon VPC. Unfortunately, the mobile client was directly connected to a single EC2 instance public IP in a default VPC 🤬. The mobile team couldn't force the update for all the clients, therefore I could not possibly terminate the old instance and redirect all the traffic to a new load balancer. I needed to "slowly" migrate to the new architecture piece by piece. To make the matter worse, the database and the redis cache where all running in that single EC2 server 😵.
So, the first thing to do, was to move out the database and the cache into their own instance, but these had to be placed inside the new VPC and still be able to communicate with the EC2 instance in the default VPC. The "final temporary" architecture would look something like this:
How is this possible to achieve? Well AWS offer VPC Peering to allow VPC-to-VPC communication. A VPC peering connection simply is a networking connection between two VPCs that enables you to route traffic between them using private IP addresses. In the AWS terminology we have a requester VPC (custom, which will request for peering connection) and an accepter VPC (default, which will accept the connection).
To send private IP traffic from your instance to an instance in the accepter VPC, you must add a route to the route table that is associated with your subnet in which your instance resides. The route points to the CIDR block of the accepter VPC. In my case the communication was happening in between the default and custom VPC, so I put everything in one template as it was temporary. If you have two custom VPCs you should make two distinct templates.
In Cloudformation, the route that points to the accepter would be the following:
ToAccepterVPCRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: !Ref AccepterVPCCIDR # Custom VPC CIDR block or subnet
RouteTableId: !Ref RequesterVPCRouteTable # Route table in the default VPC
VpcPeeringConnectionId: !Ref VPCPeeringConnection
Vice versa, there should be another route on the accepter VPC side that points to the requester:
ToRequesterVPCRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: !Ref RequesterVPCCIDR # Default VPC CIDR block or subnet
RouteTableId: !Ref AccepterVPCRouteTable # Route table in the custom VPC
VpcPeeringConnectionId: !Ref VPCPeeringConnection
And of course we must create the VPC peering connection itself, but only on one side:
VPCPeeringConnection: # From the requester side
Type: AWS::EC2::VPCPeeringConnection
Properties:
VpcId: !Ref VPC
PeerVpcId: !Ref AccepterVPC # The custom VPC
Even though we have specified only one peering resource, the connection is not one sided, both VPCs' services can receive and send requests to each others, but only if the route tables are configured accordingly.
You need to associate respectively the route tables from the two VPCs with the appropriate subnets in which your resources you want to connect reside. Let's say your RDS instance is in a private subnet A, then you need a route table associated with subnet A, this table will have a route which will point to the other VPC CIDR block.
RequesterVPCRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
RequesterVPCSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RequesterVPCRouteTable
SubnetId: !Ref PrivateSubnetA
You should to the same thing for the other VPC.
AccepterVPCRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref AccepterVPC
AccepterVPCSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref AccepterVPCRouteTable
SubnetId: !Ref PrivateSubnetInTheAccepterVPC
One more interesting thing, as in my case, if you want to connect the ALB with the EC2 instance in the default VPC through private IP, you will need to set the parameter AvailabilityZone
to all
.
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: !Ref EC2InAccepterVPCPort
Protocol: HTTP
Targets:
- Id: !Ref EC2InAccepterVPCPrivateIp
AvailabilityZone: all
Port: !Ref EC2InAccepterVPCPort
TargetType: ip
VpcId: !Ref VPC
I hope you got something from this tutorial! Certainly to achieve this it took me a while as the Cloudformation tutorials are really rare these days 🤔 . Anyway I have made a template out of everything so you can check the various parts assembled together.
Cheers!