During a recent project, I ran into an issue where the new stack (defined using Terraform) needed to connect to a legacy server via private network interfaces. The Terraform stack was encapsulated within its own VPC, and the legacy server was in the default VPC. Both VPCs were in the same region.

AWS has a feature called “VPC peering” which establishes a connection between two VPCs - exactly what was required for this use case. AWS has some documentation available, but I wasn’t able to find any clear examples on how to achieve this with Terraform (inspiring this post).

Solution

Once you have the 2 VPCs you wish to peer, there are 3 key resources you will need to establish connectivity.

  1. A VPC peering connection between VPC 1 and 2.
  2. A route on VPC 1’s main route table to the CIDR block of VPC 2.
  3. A route on VPC 2’s main route table to the CIDR block of VPC 1.

The key here is to add routes to the main route table for each VPC. Initially I made the mistake of creating new route tables for each VPC, and then wondered why the routes were not working!

1. aws_vpc_peering_connection between VPC 1 and 2

resource "aws_vpc_peering_connection" "primary2secondary" {
  # Main VPC ID.
  vpc_id = "${aws_vpc.primary.id}"

  # AWS Account ID. This can be dynamically queried using the
  # aws_caller_identity data resource.
  # https://www.terraform.io/docs/providers/aws/d/caller_identity.html
  peer_owner_id = "${data.aws_caller_identity.current.account_id}"

  # Secondary VPC ID.
  peer_vpc_id = "${aws_vpc.secondary.id}"

  # Flags that the peering connection should be automatically confirmed. This
  # only works if both VPCs are owned by the same account.
  auto_accept = true
}

2. aws_route on VPC 1 main route table.

resource "aws_route" "primary2secondary" {
  # ID of VPC 1 main route table.
  route_table_id = "${aws_vpc.primary.main_route_table_id}"

  # CIDR block / IP range for VPC 2.
  destination_cidr_block = "${aws_vpc.secondary.cidr_block}"

  # ID of VPC peering connection.
  vpc_peering_connection_id = "${aws_vpc_peering_connection.primary2secondary.id}"
}

3. aws_route on VPC 2 main route table.

resource "aws_route" "secondary2primary" {
  # ID of VPC 2 main route table.
  route_table_id = "${aws_vpc.secondary.main_route_table_id}"

  # CIDR block / IP range for VPC 2.
  destination_cidr_block = "${aws_vpc.primary.cidr_block}"

  # ID of VPC peering connection.
  vpc_peering_connection_id = "${aws_vpc_peering_connection.primary2secondary.id}"
}

Complete Example

UPDATE 2019-03-14: If you are using terraform >= 0.12, make sure you use the terraform-v0.12 branch - https://github.com/nicksantamaria/example-terraform-aws-vpc-peering/tree/terraform-v0.12

I created a project on Github called nicksantamaria/example-terraform-aws-vpc-peering which contains Terraform templates to provision the following architecture:

Network diagram

Each of the arrows indicates desired HTTP connectivity. To achieve this, each of the instances has a security group which allows requests on port 80 from the private IP ranges of both VPCs.

To validate this model worked, I wrote a test script called test-instance-connectivity.php. The script SSH’s into each instance and attempts a HTTP request to all other instances, then outputs a matrix showing connectivity within the network.

See the project README.md for usage information.

References: