Dual Region Transit Gateway Peering with Ansible configuration
Overview
I built dual-region AWS infrastructure connected through Transit Gateway peering between Ireland and London, automating deployment with Terraform and server configuration with Ansible.
You can find the code files in this GITHUB REPO. To run it you will need:
- AWS Access Key ID and Secret Access Key configured
- Terraform (v1.0+)
- Ansible (V2.9+)
- SSH Key Pair
- You will need to know your own ip (use https://ipv4.icanhazip.com or https://https://checkip.amazonaws.com/)
Check out the project's readme file for instructions on how to download it and run it on your machine
Architecture and Tech Stack
Network Topology components
- 2 Regions: EU-WEST-1 (Ireland) + EU-WEST-2 (LONDON)
- 5 VPCs, of which 3 in Ireland and 2 in London
- 2 Transit Gateways; 1 in each region
- Transit Gateways are connected through peering
- All VPCs have a public subnet, with their own Internet Gateway
- 5 EC2s, 1 in each subnet
- Flask loaded on one EC2. In this scenario it is on VPC-2
- Security Groups
- Route Tables
Other technologies used
- Github version control
- Bash: for storing environment variables, managing key pairs + SSH-ing into EC2 instsances
- Ansible: for configuring EC2s
- Nginx loaded on EC2s
- Operating System: Ubuntu 22.04
Code Snippets
Transit Gateway Peering
# peering attachment from Ireland to London
resource "aws_ec2_transit_gateway_peering_attachment" "ireland_to_london" {
provider = aws.ireland
peer_region = "eu-west-2"
peer_transit_gateway_id = aws_ec2_transit_gateway.tgw_london.id
transit_gateway_id = aws_ec2_transit_gateway.tgw_ireland.id
tags = {
Name = "ireland-london-peering"
}
}
resource "aws_ec2_transit_gateway_peering_attachment_accepter" "london_accept" {
provider = aws.london
transit_gateway_attachment_id = aws_ec2_transit_gateway_peering_attachment.ireland_to_london.id
tags = {
Name = "london-accept-ireland-peering"
}
}
Multi-Provider Configuration (Terraform)
provider "aws" {
alias = "ireland"
region = "eu-west-1"
}
provider "aws" {
alias = "london"
region = "eu-west-2"
}
Nginx Reverse Proxy Playbook (Ansible)
- name: Configure Nginx as reverse proxy to Flask
hosts: nginx_servers
become: yes
tasks:
- name: Create Nginx proxy configuration
copy:
dest: /etc/nginx/sites-available/flask-proxy
content: |
server {
listen 80;
location / {
proxy_pass http://{{ flask_backend }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
- name: Reload Nginx
service:
name: nginx
state: reloaded
Challenges & Key Takeaways
MULTI-REGION: The main challenge was understanding that AWS resources are region-specific. AMI IDs, which I initially assumed were universal, are actually unique to each region because AWS maintains separate AMI catalogs. SSH keypairs also cannot be shared across regions and must be registered in each location independently. This required careful attention to Terraform provider aliases. Every resource needed an explicit 'provider' attribute to deploy in the correct region. Security groups, keypairs, and AMIs all required this regional awareness too. This experience taught me an important lesson: assumptions about infrastructure need to be verified, especially in multi-region architectures where resources that appear identical actually operate under different regional constraints.
MANAGING RESOURCE CREATION: Managing resource creation order proved critical, particularly with Transit Gateway peering. Routes couldn't be created until the peering attachment was fully established and accepted. The peering attachment progresses through specific states—from pendingAcceptance when Ireland initiates the request, to available once London accepts it. Both directional routes (Ireland-to-London and London-to-Ireland) depend on the same peering attachment, meaning both required explicit depends_on declarations referencing the accepter resource. Without these dependencies, Terraform would attempt to create routes before the attachment reached the available state, causing deployment failures. This reinforced the importance of understanding AWS resource state transitions and using Terraform's dependency management to orchestrate correct provisioning sequences.
PRIVATE SUBNET INTERNET CONNECTIVITY: A significant challenge arose when deploying Flask to the private subnet instance. Ansible's package installation failed because the instance couldn't reach Ubuntu's package repositories—it lacked internet access. The proper solution would be adding a NAT Gateway for outbound connectivity while maintaining security isolation. However, NAT Gateways cost approximately $32/month, which wasn't justified for a proof-of-concept project. Instead, I moved Flask from the private instance to a public instance already running Nginx. This decision highlighted an important DevOps consideration: architectural best practices must be balanced against project constraints. While production environments warrant the security benefits of private subnets with NAT Gateways, learning projects can make pragmatic tradeoffs without compromising core objectives.