Aviatrix Answers

Why should I use the Aviatrix Terraform Provider to build and maintain my AWS Transit Gateway?


Key Concepts
8 minute read


Terraform is a great tool for managing your environment including most of the AWS services like the newly released AWS Transit Gateway.

When the AWS Transit Gateway was announced at re:Invent 2018, Hashicorp released the associated Terraform resources at the same time. Aviatrix also released its Transit Gateway Orchestrator and Terraform resources to further simply the deployment.

So, why should you use Aviatrix over the standard AWS Terraform provider? In this article, we'll explore the additional value obtained using Aviatrix's Terraform provider to deploy the AWS Transit Gateway, including:

  1. Simplify the Transit Gateway Deployment
  2. Reduce Time for Ongoing Maintenance of Transit Gateway
  3. Troubleshooting Tools, Monitoring, Auditing, and Increased Visibility

Simplify the Transit Gateway Deployment

Using the AWS console to deploy and maintain your AWS Transit Gateway is manageable for a small number of VPCs. The complexity grows exponentially as you add VPCs and especially as you add additional route domains. The number route tables and subnet attachments create a challenge for maintaining and troubleshooting your environment. This only gets worse when you consider multiple accounts.

Using Terraform simplifies this by allowing you to use code to document your environment. The addition of variable names and comments go a long way to improve the challenging task of keeping track of everything.

However, there are still a lot of resources involved to add a new VPC and even more when moving a VPC from one route domain to another. Instead of worrying about all of those steps, the Aviatrix Terraform provider takes care of it for you.

Let's take a look at how you can create an AWS Transit Gateway with 3 route domains and 3 VPCs using the AWS Terraform provider and then, after that, using the Aviatrix Terraform provider:

resource "aws_ec2_transit_gateway" "tgw1" {
    tags = {
        Name = "terraform-test"
    }
}

/* Transit Gateway (tgw1) VPC Attachments */
resource "aws_ec2_transit_gateway_vpc_attachment" "tgw1-dev1" {
    subnet_ids         = [
        "${aws_subnet.dev1_private1.id}",
        "${aws_subnet.dev1_private2.id}",
    ]
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
    vpc_id             = "${aws_vpc.dev1.id}"
    tags = {
        Name = "tgw1-to-dev1"
    }
}
resource "aws_ec2_transit_gateway_vpc_attachment" "tgw1-dev2" {
    subnet_ids         = [
        "${aws_subnet.dev2_private1.id}",
        "${aws_subnet.dev2_private2.id}",
    ]
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
    vpc_id             = "${aws_vpc.dev2.id}"
    tags = {
        Name = "tgw1-to-dev2"
    }
}

resource "aws_ec2_transit_gateway_vpc_attachment" "tgw1-prod1" {
    subnet_ids         = [
        "${aws_subnet.prod1_private1.id}",
        "${aws_subnet.prod1_private2.id}",
    ]
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
    vpc_id             = "${aws_vpc.prod1.id}"
    tags = {
        Name = "tgw1-to-prod1"
    }
}

resource "aws_ec2_transit_gateway_vpc_attachment" "tgw1-shared-services" {
    subnet_ids         = [
        "${aws_subnet.shared_services_private1.id}",
        "${aws_subnet.shared_services_private2.id}",
    ]
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
    vpc_id             = "${aws_vpc.shared_services.id}"
    tags = {
        Name = "tgw1-to-shared_services"
    }
}


/* Transit Gateway (tgw1) Route Domain: Dev */
resource "aws_ec2_transit_gateway_route_table" "tgw1-rt-dev" {
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
    tags = {
        Name = "dev"
    }
}
resource "aws_ec2_transit_gateway_route_table_propagation" "tgw1-dev1" {
    transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-dev1.id}"
    transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-dev.id}"
}
resource "aws_ec2_transit_gateway_route_table_association" "tgw1-dev1" {
  transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-dev1.id}"
  transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-dev.id}"
}
resource "aws_ec2_transit_gateway_route_table_propagation" "tgw1-dev2" {
    transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-dev2.id}"
    transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-dev.id}"
}
resource "aws_ec2_transit_gateway_route_table_association" "tgw1-dev2" {
  transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-dev2.id}"
  transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-dev.id}"
}

/* Add route table entry in spoke VPC subnets: dev1 */
resource "aws_route" "dev1-private1-tgw-route" {
    route_table_id = "${aws_route_table.dev1_private1.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}
resource "aws_route" "dev1-private2-tgw-route" {
    route_table_id = "${aws_route_table.dev1_private2.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}

/* Add route table entry in spoke VPC subnets: dev2 */
resource "aws_route" "dev2-private1-tgw-route" {
    route_table_id = "${aws_route_table.dev2_private1.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}
resource "aws_route" "dev2-private2-tgw-route" {
    route_table_id = "${aws_route_table.dev2_private2.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}

/* Transit Gateway (tgw1) Route Domain: Prod1 */
resource "aws_ec2_transit_gateway_route_table" "tgw1-rt-prod1" {
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
    tags = {
        Name = "prod1"
    }
}
resource "aws_ec2_transit_gateway_route_table_propagation" "tgw1-prod1" {
    transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-prod1.id}"
    transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-prod1.id}"
}
resource "aws_ec2_transit_gateway_route_table_association" "tgw1-prod1" {
  transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-prod1.id}"
  transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-prod1.id}"
}

/* Add route table entry in spoke VPC subnets: prod1 */
resource "aws_route" "prod1-private1-tgw-route" {
    route_table_id = "${aws_route_table.prod1_private2.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}
resource "aws_route" "prod1-private2-tgw-route" {
    route_table_id = "${aws_route_table.prod1_private2.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}

/* Transit Gateway (tgw1) Route Domain: Shared Services */
resource "aws_ec2_transit_gateway_route_table" "tgw1-rt-shared-services" {
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
    tags = {
        Name = "Shared Services"
    }
}
resource "aws_ec2_transit_gateway_route_table_propagation" "tgw1-shared-services" {
    transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-shared-services.id}"
    transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-shared-services.id}"
}
resource "aws_ec2_transit_gateway_route_table_association" "tgw1-shared-services" {
  transit_gateway_attachment_id  = "${aws_ec2_transit_gateway_vpc_attachment.tgw1-shared-services.id}"
  transit_gateway_route_table_id = "${aws_ec2_transit_gateway_route_table.tgw1-rt-shared-services.id}"
}

/* Add route table entry in spoke VPC subnets: shared services */
resource "aws_route" "shared-services-private1-tgw-route" {
    route_table_id = "${aws_route_table.shared_services_private1.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}
resource "aws_route" "shared-services-private2-tgw-route" {
    route_table_id = "${aws_route_table.shared_services_private2.id}"
    destination_cidr_block = "10.0.0.0/8"
    transit_gateway_id = "${aws_ec2_transit_gateway.tgw1.id}"
}

And, the same thing using the Aviatrix Terraform provider:

data "aws_region" "current" {
}

resource "aviatrix_aws_tgw" "tgw1" {
    tgw_name = "terraform-test"
    account_name = "demo-application"
    region = "${data.aws_region.current.name}"
    aws_side_as_number = "64512"
    security_domains = [
        {
            security_domain_name = "dev"
            connected_domains = ["shared-services"]
            attached_vpc = [
                {
                    vpc_region = "${data.aws_region.current.name}"
                    vpc_account_name = "demo-application"
                    vpc_id = "${aws_vpc.dev1.id}"
                },
                {
                    vpc_region = "${data.aws_region.current.name}"
                    vpc_account_name = "demo-application"
                    vpc_id = "${aws_vpc.dev2.id}"
                }
            ]
        },
        {
            security_domain_name = "prod"
            connected_domains = ["shared-services"]
            attached_vpc = [
                {
                    vpc_region = "${data.aws_region.current.name}"
                    vpc_account_name = "demo-application"
                    vpc_id = "${aws_vpc.prod1.id}"
                }
            ]
        },
        {
            security_domain_name = "shared-services"
            connected_domains = ["dev", "prod"]
            attached_vpc = [
                {
                    vpc_region = "${data.aws_region.current.name}"
                    vpc_account_name = "shared-services"
                    vpc_id = "${aws_vpc.shared_services.id}"
                }
            ]
        }
    ]
}

Aviatrix simplifies everything down to the essentials and allow you to see what is connected to what in one contained resource.

In this example, we only consider a single AWS account. Even with that, there is still a lot of complexity using the AWS Terraform provider. On the other hand, the Aviatrix provider keeps things simple: it handles subnets, route tables, and even multiple account connectivity so you can get back to doing what matters.

Reduce Time for Ongoing Maintenance of Transit Gateway

AWS environments are dynamic, changing frequently. VPCs get added often along with new subnets in existing VPCs. And, connectivity needs change as the requirements of VPCs and the applications contained within evolve.

When your environment changes, you likely update your Terraform scripts. But, it's not always easy to remember all the Transit Gateway resource dependencies.

For example, for each new subnet that is a different Availability Zone (AZ), you will need to remember to update the aws_ec2_transit_gateway_vpc_attachment resource to add the new subnet ID. And, you can't add it if there is already a subnet listed in the same AZ because it will fail. You will also need to manage the route table for the new subnet (if different from the existing). These types of things make the script error prone and causes time to be lost on debugging and troubleshooting.

There are other soft values that come from having a central controller manage the environment too. For example, if a new engineer joins the team, using the Aviatrix Terraform resource they can quickly get a sense for the domains and VPCs that are connected and easily add to it or modify the existing deployment.

Troubleshooting Tools, Monitoring, Auditing, and Increased Visibility

After using the Aviatrix Terraform provider, the Aviatrix Controller is now connected to your AWS environment and can provide even more value to your Transit Gateway deployment:

1. Troubleshooting and debugging networking problems

The Aviatrix Controller provides tools such as FlightPath for troubleshooting problems connecting two EC2 instances. This tool is invaluable for finding missing route table entries or ACLs blocking access. Take a look at an example below:

2. Monitoring and Auditing

The Aviatrix Controller can perform a routine audit of your Transit Gateway deployment to make sure the route tables and configuration are accurate. So, if someone accidentally removes an entry, you'll know about it right away.

3. Visualization

Finally, the AWS console makes it difficult to tell what is connected to each TGW and how the VPCs are attached to route domains. When you use the Aviatrix Controller to orchestrate and manage the Transit Gateway, you know all of that with a simple click of the mouse. Here's an example showing the dev domain created in our earlier example with one additional VPC: