├── .gitignore ├── README.md ├── instances.tf ├── main.tf ├── test-instance-connectivity.php ├── variables.tf ├── versions.tf ├── vpc-primary.tf └── vpc-secondary.tf /.gitignore: -------------------------------------------------------------------------------- 1 | *.tfstate* 2 | .terraform 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example - Peering 2 VPCs with Terraform 2 | 3 | This project aims to provide a very straight-forward example of peering 2 entire 4 | VPCs using Terraform. 5 | 6 | Terraform provisions the following architecture: 7 | 8 | ![Terraform diagram](http://www.nicksantamaria.net/img/peer-vpc-diagram-connectivity.png) 9 | 10 | Once provisioning is complete, run the [test script](test-instance-connectivity.php) to validate each of the connections. 11 | 12 | ## Dependencies 13 | 14 | * Terraform v0.7 or greater 15 | * An AWS account 16 | 17 | ## Usage 18 | 19 | **1\. Ensure your AWS credentials are set up.** 20 | 21 | This can be done using environment variables: 22 | 23 | ``` bash 24 | $ export AWS_SECRET_ACCESS_KEY='your secret key' 25 | $ export AWS_ACCESS_KEY_ID='your key id' 26 | ``` 27 | 28 | ... or the `~/.aws/credentials` file. 29 | 30 | ``` 31 | $ cat ~/.aws/credentials 32 | [default] 33 | aws_access_key_id = your key id 34 | aws_secret_access_key = your secret key 35 | 36 | ``` 37 | 38 | **2\. Review the Terraform plan.** 39 | 40 | Execute the below command and ensure you are happy with the plan. 41 | 42 | ``` bash 43 | $ terraform plan 44 | ``` 45 | 46 | **3\. Execute the Terraform apply.** 47 | 48 | Now execute the plan to provision the AWS resources. 49 | 50 | ``` bash 51 | $ terraform apply 52 | ``` 53 | 54 | **4\. Run the test PHP script.** 55 | 56 | ``` bash 57 | $ php test-instance-connectivity.php 58 | ``` 59 | 60 | **5\. Destroy the resources.** 61 | 62 | Once you are finished your testing, ensure you destroy the resources to avoid unnecessary AWS charges. 63 | 64 | ``` bash 65 | $ terraform destroy 66 | ``` 67 | -------------------------------------------------------------------------------- /instances.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "primary-az1" { 2 | instance_type = var.instance_class 3 | ami = var.ami_id 4 | key_name = var.key_name 5 | subnet_id = aws_subnet.primary-az1.id 6 | vpc_security_group_ids = [aws_security_group.primary-default.id] 7 | 8 | connection { 9 | host = coalesce(self.public_ip, self.private_ip) 10 | type = "ssh" 11 | user = "ubuntu" 12 | } 13 | 14 | provisioner "remote-exec" { 15 | inline = [ 16 | "sudo apt-get update", 17 | "sudo apt-get install -y nginx", 18 | ] 19 | } 20 | } 21 | 22 | resource "aws_instance" "primary-az2" { 23 | instance_type = var.instance_class 24 | ami = var.ami_id 25 | key_name = var.key_name 26 | subnet_id = aws_subnet.primary-az2.id 27 | vpc_security_group_ids = [aws_security_group.primary-default.id] 28 | 29 | connection { 30 | host = coalesce(self.public_ip, self.private_ip) 31 | type = "ssh" 32 | user = "ubuntu" 33 | } 34 | 35 | provisioner "remote-exec" { 36 | inline = [ 37 | "sudo apt-get update", 38 | "sudo apt-get install -y nginx", 39 | ] 40 | } 41 | } 42 | 43 | resource "aws_instance" "secondary-az1" { 44 | instance_type = var.instance_class 45 | ami = var.ami_id 46 | key_name = var.key_name 47 | subnet_id = aws_subnet.secondary-az1.id 48 | vpc_security_group_ids = [aws_security_group.secondary-default.id] 49 | 50 | connection { 51 | host = coalesce(self.public_ip, self.private_ip) 52 | type = "ssh" 53 | user = "ubuntu" 54 | } 55 | 56 | provisioner "remote-exec" { 57 | inline = [ 58 | "sudo apt-get update", 59 | "sudo apt-get install -y nginx", 60 | ] 61 | } 62 | } 63 | 64 | resource "aws_instance" "secondary-az2" { 65 | instance_type = var.instance_class 66 | ami = var.ami_id 67 | key_name = var.key_name 68 | subnet_id = aws_subnet.secondary-az2.id 69 | vpc_security_group_ids = [aws_security_group.secondary-default.id] 70 | 71 | connection { 72 | host = coalesce(self.public_ip, self.private_ip) 73 | type = "ssh" 74 | user = "ubuntu" 75 | } 76 | 77 | provisioner "remote-exec" { 78 | inline = [ 79 | "sudo apt-get update", 80 | "sudo apt-get install -y nginx", 81 | ] 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Setup AWS provider. 3 | */ 4 | provider "aws" { 5 | region = var.region 6 | } 7 | 8 | /** 9 | * Make AWS account ID available. 10 | * 11 | * This is added as an output so that other stacks can reference this. Usually 12 | * required for VPC peering. 13 | */ 14 | data "aws_caller_identity" "current" {} 15 | 16 | /** 17 | * VPC peering connection. 18 | * 19 | * Establishes a relationship resource between the "primary" and "secondary" VPC. 20 | */ 21 | resource "aws_vpc_peering_connection" "primary2secondary" { 22 | peer_owner_id = data.aws_caller_identity.current.account_id 23 | peer_vpc_id = aws_vpc.secondary.id 24 | vpc_id = aws_vpc.primary.id 25 | auto_accept = true 26 | } 27 | 28 | /** 29 | * Route rule. 30 | * 31 | * Creates a new route rule on the "primary" VPC main route table. All requests 32 | * to the "secondary" VPC's IP range will be directed to the VPC peering 33 | * connection. 34 | */ 35 | resource "aws_route" "primary2secondary" { 36 | route_table_id = aws_vpc.primary.main_route_table_id 37 | destination_cidr_block = aws_vpc.secondary.cidr_block 38 | vpc_peering_connection_id = aws_vpc_peering_connection.primary2secondary.id 39 | } 40 | 41 | /** 42 | * Route rule. 43 | * 44 | * Creates a new route rule on the "secondary" VPC main route table. All 45 | * requests to the "secondary" VPC's IP range will be directed to the VPC 46 | * peering connection. 47 | */ 48 | resource "aws_route" "secondary2primary" { 49 | route_table_id = aws_vpc.secondary.main_route_table_id 50 | destination_cidr_block = aws_vpc.primary.cidr_block 51 | vpc_peering_connection_id = aws_vpc_peering_connection.primary2secondary.id 52 | } 53 | -------------------------------------------------------------------------------- /test-instance-connectivity.php: -------------------------------------------------------------------------------- 1 | $info['public_ip'], 29 | 'private' => $info['private_ip'], 30 | ]; 31 | } 32 | 33 | // Run through each permutation and ensure machines can all connect to each other. 34 | foreach ($stack as $source_vpc => $source_instances) { 35 | foreach ($source_instances as $source_az => $source_ip) { 36 | foreach ($stack as $dest_vpc => $dest_instances) { 37 | foreach ($dest_instances as $dest_az => $dest_ip) { 38 | if ($source_az == $dest_az && $source_ip == $dest_ip) continue; 39 | 40 | $pid = pcntl_fork(); 41 | if ($pid == -1) { 42 | exit("Error forking...\n"); 43 | } 44 | else if ($pid == 0) { 45 | //echo "- Testing connection between ${source_vpc}.${source_az} ${dest_vpc}.${dest_az}\n"; 46 | $response = test_connection($source_ip, $dest_ip); 47 | print_table_row([ 48 | 'source_vpc' => $source_vpc, 49 | 'source_az' => $source_az, 50 | 'dest_vpc' => $dest_vpc, 51 | 'dest_az' => $dest_az, 52 | 'source_ip' => $source_ip['private'], 53 | 'dest_ip' => $dest_ip['private'], 54 | 'response' => trim($response), 55 | ]); 56 | exit(); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | while(pcntl_waitpid(0, $status) != -1); 64 | 65 | function test_connection($source_ip, $dest_ip) { 66 | $remote_command = "curl -s --connect-timeout 3 -I -XGET ${dest_ip['private']} | grep HTTP"; 67 | $local_command = "ssh -q ubuntu@${source_ip['public']} '${remote_command}'"; 68 | $response = `$local_command`; 69 | return $response; 70 | } 71 | 72 | function print_table_row($row) { 73 | foreach ($row as $i => & $col) { 74 | $col = str_pad($col, 16, " ", STR_PAD_BOTH); 75 | } 76 | print "| " . implode(" | ", $row) . " |\n"; 77 | } 78 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | default = "ap-southeast-2" 3 | } 4 | 5 | variable "ami_id" { 6 | default = "ami-a0360bc3" 7 | } 8 | 9 | variable "instance_class" { 10 | default = "m3.medium" 11 | } 12 | 13 | variable "key_name" { 14 | description = "SSH key name to launch instances with" 15 | } 16 | 17 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12" 3 | } 4 | -------------------------------------------------------------------------------- /vpc-primary.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * VPC. 3 | * 4 | * Virtual network which will be referred to as "secondary" in this example. 5 | */ 6 | resource "aws_vpc" "primary" { 7 | cidr_block = "172.30.0.0/16" 8 | } 9 | 10 | /** 11 | * Internet gateway (IGW). 12 | * 13 | * Gateway which allows traffic to/from the "primary" VPC to the public 14 | * internet. 15 | */ 16 | resource "aws_internet_gateway" "primary" { 17 | vpc_id = aws_vpc.primary.id 18 | } 19 | 20 | /** 21 | * Route rule. 22 | * 23 | * Directs all traffic (which doesn't match another route) to the IGW. 24 | */ 25 | resource "aws_route" "primary-internet_access" { 26 | route_table_id = aws_vpc.primary.main_route_table_id 27 | destination_cidr_block = "0.0.0.0/0" 28 | gateway_id = aws_internet_gateway.primary.id 29 | } 30 | 31 | /** 32 | * Subnet. 33 | * 34 | * /24 subnet for availability zone "a". 35 | */ 36 | resource "aws_subnet" "primary-az1" { 37 | vpc_id = aws_vpc.primary.id 38 | cidr_block = "172.30.131.0/24" 39 | map_public_ip_on_launch = true 40 | availability_zone = "ap-southeast-2a" 41 | } 42 | 43 | /** 44 | * Subnet. 45 | * 46 | * /24 subnet for availability zone "b". 47 | */ 48 | resource "aws_subnet" "primary-az2" { 49 | vpc_id = aws_vpc.primary.id 50 | cidr_block = "172.30.132.0/24" 51 | map_public_ip_on_launch = true 52 | availability_zone = "ap-southeast-2b" 53 | } 54 | 55 | /** 56 | * Security group. 57 | * 58 | * Basic security group with the following rules: 59 | * - Inbound traffic on port 80 restricted to private IPs. 60 | * - Inbound traffic on port 22 unrestricted (enables us to setup instances 61 | * and run tests). 62 | * - Outbound traffic unrestricted. 63 | */ 64 | resource "aws_security_group" "primary-default" { 65 | name_prefix = "default-" 66 | description = "Default security group for all instances in VPC ${aws_vpc.primary.id}" 67 | vpc_id = aws_vpc.primary.id 68 | 69 | ingress { 70 | from_port = 80 71 | to_port = 80 72 | protocol = "tcp" 73 | cidr_blocks = [ 74 | aws_vpc.primary.cidr_block, 75 | aws_vpc.secondary.cidr_block, 76 | ] 77 | } 78 | 79 | ingress { 80 | from_port = 22 81 | to_port = 22 82 | protocol = "tcp" 83 | cidr_blocks = ["0.0.0.0/0"] 84 | } 85 | 86 | egress { 87 | from_port = 0 88 | to_port = 0 89 | protocol = "-1" 90 | cidr_blocks = ["0.0.0.0/0"] 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /vpc-secondary.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * VPC. 3 | * 4 | * Virtual network which will be referred to as "secondary" in this example. 5 | */ 6 | resource "aws_vpc" "secondary" { 7 | cidr_block = "10.0.0.0/16" 8 | } 9 | 10 | /** 11 | * Internet gateway (IGW). 12 | * 13 | * Gateway which allows traffic to/from the "secondary" VPC to the public 14 | * internet. 15 | */ 16 | resource "aws_internet_gateway" "secondary" { 17 | vpc_id = aws_vpc.secondary.id 18 | } 19 | 20 | /** 21 | * Route rule. 22 | * 23 | * Directs all traffic (which doesn't match another route) to the IGW. 24 | */ 25 | resource "aws_route" "secondary-internet_access" { 26 | route_table_id = aws_vpc.secondary.main_route_table_id 27 | destination_cidr_block = "0.0.0.0/0" 28 | gateway_id = aws_internet_gateway.secondary.id 29 | } 30 | 31 | /** 32 | * Subnet. 33 | * 34 | * /24 subnet for availability zone "a". 35 | */ 36 | resource "aws_subnet" "secondary-az1" { 37 | vpc_id = aws_vpc.secondary.id 38 | cidr_block = "10.0.1.0/24" 39 | map_public_ip_on_launch = true 40 | availability_zone = "${var.region}a" 41 | } 42 | 43 | /** 44 | * Subnet. 45 | * 46 | * /24 subnet for availability zone "b". 47 | */ 48 | resource "aws_subnet" "secondary-az2" { 49 | vpc_id = aws_vpc.secondary.id 50 | cidr_block = "10.0.2.0/24" 51 | map_public_ip_on_launch = true 52 | availability_zone = "${var.region}b" 53 | } 54 | 55 | /** 56 | * Security group. 57 | * 58 | * Basic security group with the following rules: 59 | * - Inbound traffic on port 80 restricted to private IPs. 60 | * - Inbound traffic on port 22 unrestricted (enables us to setup instances 61 | * and run tests). 62 | * - Outbound traffic unrestricted. 63 | */ 64 | resource "aws_security_group" "secondary-default" { 65 | name_prefix = "default-" 66 | description = "Default security group for all instances in VPC ${aws_vpc.secondary.id}" 67 | vpc_id = aws_vpc.secondary.id 68 | 69 | ingress { 70 | from_port = 80 71 | to_port = 80 72 | protocol = "tcp" 73 | cidr_blocks = [ 74 | aws_vpc.primary.cidr_block, 75 | aws_vpc.secondary.cidr_block, 76 | ] 77 | } 78 | 79 | ingress { 80 | from_port = 22 81 | to_port = 22 82 | protocol = "tcp" 83 | cidr_blocks = ["0.0.0.0/0"] 84 | } 85 | 86 | egress { 87 | from_port = 0 88 | to_port = 0 89 | protocol = "-1" 90 | cidr_blocks = ["0.0.0.0/0"] 91 | } 92 | } 93 | --------------------------------------------------------------------------------