VPC-App Terraform Module
This Terraform Module launches a single VPC meant to house applications. By contrast, DevOps-related services such as Jenkins or InfluxDB should be in a "mgmt" VPC. (See the vpc-mgmt module.)
What's a VPC?
A VPC or Virtual Private Cloud is a logically isolated section of your AWS cloud. Each VPC defines a virtual network within which you run your AWS resources, as well as rules for what can go in and out of that network. This includes subnets, route tables that tell those subnets how to route inbound and outbound traffic, security groups, access controls lists for the network (NACLs), and any other network components such as VPN connections.
Three Subnet Tiers
This VPC defines three "tiers" of subnets:
- Public Subnets: Resources in these subnets are directly addressable from the Internet. Only public-facing resources (typically just load balancers) should be put here.
- Private/App Subnets: Resources in these subnets are NOT directly addressable from the Internet but they can make outbound connections to the Internet through a NAT Gateway. You can connect to the resources in this subnet only from resources within the VPC, so you should put your app servers here and allow the load balancers in the Public Subnet to route traffic to them.
- Private/Persistence Subnets: Resources in these subnets are neither directly addressable from the Internet nor able to make outbound Internet connections. You can connect to the resources in this subnet only from within the VPC, so you should put your databases, cache servers, and other stateful resources here and allow your apps to talk to them.
VPC Architecture
The three-tier VPC is inspired by the VPC Architecture described by Ben Whaley in his blog post A Reference VPC Architecture. That blog post proposed the following VPC structure:
VPC Diagram
To summarize:
- Each environment (prod, stage, etc.) is represented by a separate VPC.
- Each VPC has three "tiers" of subnets to allow AWS resources to be publicly addressable, addressable only from the public tier, or only from the private/app tier.
- In a given subnet tier, there are usually three or four actual subnets, one for each Availability Zone.
- Therefore, if we created a single VPC in the
us-west-2
region, which has Availability Zonesus-west-2a
,us-west-2b
, andus-west-2c
, each subnet tier would have three subnets (one per Availability Zone) for a total of 9 subnets in all. - The only way to reach this VPC is from the public Internet via a publicly exposed sevice, or via the mgmt VPC, which uses VPC Peering to make this VPC accessible from the mgmt VPC.
- Philosophically, everything in a VPC should be isolated from all resources in any other VPC. In particular, we want to ensure that our stage environment is completely independent from prod. This architecture helps to reinforce that.
Throughout our diagrams and examples we recommend a /16 CIDR range for VPCs. The reason for this is that a /16 makes CIDR math quite straightforward. If using the 10.0.0.0/8 RFC1918 address space, this allows for 256 VPCs (10.0.0.0/16-10.255.255.255/16) with 65,534 IP addresses per VPC. This should be sufficient for nearly all use-cases, and is consistent with many examples and existing documentation found elsewhere.
Gotchas
- If the
num_availability_zones
variable in the mgmt VPC and thenum_availability_zones
variable in the app VPC don't match, there are problems with the routes that are created between the two VPCs as part of setting up VPC Peering. If your use case requires different numbers of Availability Zones for each of these VPCs, please let us know and we'll investigate further!
Other VPC Core Concepts
Learn about Other VPC Core Concepts like subnets, NAT Gateways, and VPC Endpoints.
Reference
- Inputs
- Outputs
Required
cidr_block
stringThe IP address range of the VPC in CIDR notation. A prefix of /16 is recommended. Do not use a prefix higher than /27. Examples include '10.100.0.0/16', '10.200.0.0/16', etc.
num_nat_gateways
numberThe number of NAT Gateways to launch for this VPC. For production VPCs, a NAT Gateway should be placed in each Availability Zone (so likely 3 total), whereas for non-prod VPCs, just one Availability Zone (and hence 1 NAT Gateway) will suffice.
vpc_name
stringName of the VPC. Examples include 'prod', 'dev', 'mgmt', etc.
Optional
Should the private persistence subnet be allowed outbound access to the internet?
false
If true, will apply the default NACL rules in default_nacl_ingress_rules
and default_nacl_egress_rules
on the default NACL of the VPC. Note that every VPC must have a default NACL - when this is false, the original default NACL rules managed by AWS will be used.
false
If true, will associate the default NACL to the public, private, and persistence subnets created by this module. Only used if apply_default_nacl_rules
is true. Note that this does not guarantee that the subnets are associated with the default NACL. Subnets can only be associated with a single NACL. The default NACL association will be dropped if the subnets are associated with a custom NACL later.
true
availability_zone_exclude_ids
list(string)List of excluded Availability Zone IDs.
[]
availability_zone_exclude_names
list(string)List of excluded Availability Zone names.
[]
availability_zone_ids
list(string)List of specific Availability Zone IDs to use. If null (default), all availability zones in the configured AWS region will be used.
null
availability_zone_state
stringAllows to filter list of Availability Zones based on their current state. Can be either 'available', 'information', 'impaired' or 'unavailable'. By default the list includes a complete set of Availability Zones to which the underlying AWS account has access, regardless of their state.
null
aws_region
stringDEPRECATED. The AWS Region where this VPC will exist. This variable is no longer used and only kept around for backwards compatibility. We now automatically fetch the region using a data source.
""
create_igw
boolIf the VPC will create an Internet Gateway. There are use cases when the VPC is desired to not be routable from the internet, and hence, they should not have an Internet Gateway. For example, when it is desired that public subnets exist but they are not directly public facing, since they can be routed from other VPC hosting the IGW.
true
If set to false, this module will NOT create the private app subnet tier.
true
If set to false, this module will NOT create the private persistence subnet tier.
true
If set to false, this module will NOT create the public subnet tier. This is useful for VPCs that only need private subnets. Note that setting this to false also means the module will NOT create an Internet Gateway or the NAT gateways, so if you want any public Internet access in the VPC (even outbound access—e.g., to run apt get), you'll need to provide it yourself via some other mechanism (e.g., via VPC peering, a Transit Gateway, Direct Connect, etc).
true
Create VPC endpoints for S3 and DynamoDB.
true
custom_nat_eips
list(string)The list of EIPs (allocation ids) to use for the NAT gateways. Their number has to match the one given in 'num_nat_gateways'. Must be set if use_custom_nat_eips
us true.
[]
custom_tags
map(string)A map of tags to apply to the VPC, Subnets, Route Tables, Internet Gateway, default security group, and default NACLs. The key is the tag name and the value is the tag value. Note that the tag 'Name' is automatically added by this module but may be optionally overwritten by this variable.
{}
The egress rules to apply to the default NACL in the VPC. This is the security group that is used by any subnet that doesn't have its own NACL attached. The value for this variable must be a map where the keys are a unique name for each rule and the values are objects with the same fields as the egress block in the aws_default_network_acl resource: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_network_acl.
Any types represent complex values of variable type. For details, please consult `variables.tf` in the source repo.
{
AllowAll = {
action = "allow",
cidr_block = "0.0.0.0/0",
from_port = 0,
protocol = "-1",
rule_no = 100,
to_port = 0
}
}
The ingress rules to apply to the default NACL in the VPC. This is the NACL that is used by any subnet that doesn't have its own NACL attached. The value for this variable must be a map where the keys are a unique name for each rule and the values are objects with the same fields as the ingress block in the aws_default_network_acl resource: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_network_acl.
Any types represent complex values of variable type. For details, please consult `variables.tf` in the source repo.
{
AllowAll = {
action = "allow",
cidr_block = "0.0.0.0/0",
from_port = 0,
protocol = "-1",
rule_no = 100,
to_port = 0
}
}
The egress rules to apply to the default security group in the VPC. This is the security group that is used by any resource that doesn't have its own security group attached. The value for this variable must be a map where the keys are a unique name for each rule and the values are objects with the same fields as the egress block in the aws_default_security_group resource: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_security_group#egress-block.
Any types represent complex values of variable type. For details, please consult `variables.tf` in the source repo.
{
AllowAllOutbound = {
cidr_blocks = [
"0.0.0.0/0"
],
from_port = 0,
ipv6_cidr_blocks = [
"::/0"
],
protocol = "-1",
to_port = 0
}
}
The ingress rules to apply to the default security group in the VPC. This is the security group that is used by any resource that doesn't have its own security group attached. The value for this variable must be a map where the keys are a unique name for each rule and the values are objects with the same fields as the ingress block in the aws_default_security_group resource: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_security_group#ingress-block.
Any types represent complex values of variable type. For details, please consult `variables.tf` in the source repo.
{
AllowAllFromSelf = {
from_port = 0,
protocol = "-1",
self = true,
to_port = 0
}
}
dynamodb_endpoint_policy
stringIAM policy to restrict what resources can call this endpoint. For example, you can add an IAM policy that allows EC2 instances to talk to this endpoint but no other types of resources. If not specified, all resources will be allowed to call this endpoint.
null
If set to false, the default security groups will NOT be created. This variable is a workaround to a terraform limitation where overriding default_security_group_ingress_rules
= {} and default_security_group_egress_rules
= {} does not remove the rules. More information at: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_security_group#removing-aws_default_security_group-from-your-configuration
true
Specify true to indicate that instances launched into the public subnet should be assigned a public IP address (versus a private IP address)
false
nat_gateway_custom_tags
map(string)A map of tags to apply to the NAT gateways, on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
num_availability_zones
numberHow many AWS Availability Zones (AZs) to use. One subnet of each type (public, private app, private persistence) will be created in each AZ. All AZs will be used if you provide a value that is more than the number of AZs in a region. A value of null means all AZs should be used. For example, if you specify 3 in a region with 5 AZs, subnets will be created in just 3 AZs instead of all 5. On the other hand, if you specify 6 in the same region, all 5 AZs will be used with no duplicates (same as setting this to 5).
null
If set to true, create one route table shared amongst all the public subnets; if set to false, create a separate route table per public subnet. Historically, we created one route table for all the public subnets, as they all routed through the Internet Gateway anyway, but in certain use cases (e.g., for use with Network Firewall), you may want to have separate route tables for each public subnet.
true
persistence_propagating_vgws
list(string)A list of Virtual Private Gateways that will propagate routes to persistence subnets. All routes from VPN connections that use Virtual Private Gateways listed here will appear in route tables of persistence subnets. If left empty, no routes will be propagated.
[]
persistence_subnet_bits
numberTakes the CIDR prefix and adds these many bits to it for calculating subnet ranges. MAKE SURE if you change this you also change the CIDR spacing or you may hit errors. See cidrsubnet interpolation in terraform config for more information.
5
The amount of spacing between the private persistence subnets. Default: 2 times the value of private_subnet_spacing.
null
private_app_route_table_custom_tags
map(string)A map of tags to apply to the private-app route table(s), on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
private_app_subnet_cidr_blocks
map(string)A map listing the specific CIDR blocks desired for each private-app subnet. The key must be in the form AZ-0, AZ-1, ... AZ-n where n is the number of Availability Zones. If left blank, we will compute a reasonable CIDR block for each subnet.
{}
private_app_subnet_custom_tags
map(string)A map of tags to apply to the private-app Subnet, on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
private_persistence_route_table_custom_tags
map(string)A map of tags to apply to the private-persistence route tables(s), on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
private_persistence_subnet_cidr_blocks
map(string)A map listing the specific CIDR blocks desired for each private-persistence subnet. The key must be in the form AZ-0, AZ-1, ... AZ-n where n is the number of Availability Zones. If left blank, we will compute a reasonable CIDR block for each subnet.
{}
private_persistence_subnet_custom_tags
map(string)A map of tags to apply to the private-persistence Subnet, on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
private_propagating_vgws
list(string)A list of Virtual Private Gateways that will propagate routes to private subnets. All routes from VPN connections that use Virtual Private Gateways listed here will appear in route tables of private subnets. If left empty, no routes will be propagated.
[]
private_subnet_bits
numberTakes the CIDR prefix and adds these many bits to it for calculating subnet ranges. MAKE SURE if you change this you also change the CIDR spacing or you may hit errors. See cidrsubnet interpolation in terraform config for more information.
5
private_subnet_spacing
numberThe amount of spacing between private app subnets.
null
public_propagating_vgws
list(string)A list of Virtual Private Gateways that will propagate routes to public subnets. All routes from VPN connections that use Virtual Private Gateways listed here will appear in route tables of public subnets. If left empty, no routes will be propagated.
[]
public_route_table_custom_tags
map(string)A map of tags to apply to the public route table(s), on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
public_subnet_bits
numberTakes the CIDR prefix and adds these many bits to it for calculating subnet ranges. MAKE SURE if you change this you also change the CIDR spacing or you may hit errors. See cidrsubnet interpolation in terraform config for more information.
5
public_subnet_cidr_blocks
map(string)A map listing the specific CIDR blocks desired for each public subnet. The key must be in the form AZ-0, AZ-1, ... AZ-n where n is the number of Availability Zones. If left blank, we will compute a reasonable CIDR block for each subnet.
{}
public_subnet_custom_tags
map(string)A map of tags to apply to the public Subnet, on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
The timeout for the creation of the Route Tables. It defines how long to wait for a route table to be created before considering the operation failed. Ref: https://www.terraform.io/language/resources/syntax#operation-timeouts
"5m"
The timeout for the deletion of the Route Tables. It defines how long to wait for a route table to be deleted before considering the operation failed. Ref: https://www.terraform.io/language/resources/syntax#operation-timeouts
"5m"
The timeout for the update of the Route Tables. It defines how long to wait for a route table to be updated before considering the operation failed. Ref: https://www.terraform.io/language/resources/syntax#operation-timeouts
"2m"
s3_endpoint_policy
stringIAM policy to restrict what resources can call this endpoint. For example, you can add an IAM policy that allows EC2 instances to talk to this endpoint but no other types of resources. If not specified, all resources will be allowed to call this endpoint.
null
security_group_tags
map(string)A map of tags to apply to the default Security Group, on top of the custom_tags. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
subnet_spacing
numberThe amount of spacing between the different subnet types
10
tenancy
stringThe allowed tenancy of instances launched into the selected VPC. Must be one of: default, dedicated, or host.
"default"
Set to true to use existing EIPs, passed in via custom_nat_eips
, for the NAT gateway(s), instead of creating new ones.
false
vpc_custom_tags
map(string)A map of tags to apply just to the VPC itself, but not any of the other resources. The key is the tag name and the value is the tag value. Note that tags defined here will override tags defined as custom_tags in case of conflict.
{}
A map of all private-app subnets, with the subnet name as the key, and all aws-subnet
properties as the value.
A map of all private-persistence subnets, with the subnet name as the key, and all aws-subnet
properties as the value.
A map of all public subnets, with the subnet name as the key, and all aws-subnet
properties as the value.