Skip to content

Commit a94f1d8

Browse files
adding support for regional nat to have availability_mode and eip_allocation types
1 parent 89dc190 commit a94f1d8

File tree

7 files changed

+147
-28
lines changed

7 files changed

+147
-28
lines changed

README.md

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,33 @@ Note that in the example we allocate 3 IPs because we will be provisioning 3 NAT
6767
If, on the other hand, `single_nat_gateway = true`, then `aws_eip.nat` would only need to allocate 1 IP.
6868
Passing the IPs into the module is done by setting two variables `reuse_nat_ips = true` and `external_nat_ip_ids = "${aws_eip.nat.*.id}"`.
6969

70+
**For Regional NAT Gateways:**
71+
When using Regional NAT Gateway with `nat_gateway_connectivity_type.eip_allocation = "manual"`, the module will allocate one EIP per Availability Zone. For example, if you have 3 AZs:
72+
73+
```hcl
74+
resource "aws_eip" "regional_nat" {
75+
count = 3 # One per AZ
76+
77+
vpc = true
78+
}
79+
80+
module "vpc" {
81+
source = "terraform-aws-modules/vpc/aws"
82+
83+
enable_nat_gateway = true
84+
nat_gateway_connectivity_type = {
85+
availability_mode = "regional"
86+
eip_allocation = "manual"
87+
}
88+
reuse_nat_ips = false
89+
}
90+
```
91+
92+
Alternatively, you can use `eip_allocation = "auto"` to let AWS automatically manage EIPs for the Regional NAT Gateway.
93+
7094
## NAT Gateway Scenarios
7195

72-
This module supports three scenarios for creating NAT gateways. Each will be explained in further detail in the corresponding sections.
96+
This module supports four scenarios for creating NAT gateways. Each will be explained in further detail in the corresponding sections.
7397

7498
- One NAT Gateway per subnet (default behavior)
7599
- `enable_nat_gateway = true`
@@ -83,9 +107,15 @@ This module supports three scenarios for creating NAT gateways. Each will be exp
83107
- `enable_nat_gateway = true`
84108
- `single_nat_gateway = false`
85109
- `one_nat_gateway_per_az = true`
110+
- Regional NAT Gateway
111+
- `enable_nat_gateway = true`
112+
- `nat_gateway_connectivity_type.availability_mode = "regional"`
113+
- `nat_gateway_connectivity_type.eip_allocation = "auto"` or `"manual"`
86114

87115
If both `single_nat_gateway` and `one_nat_gateway_per_az` are set to `true`, then `single_nat_gateway` takes precedence.
88116

117+
> **Note**: Regional NAT Gateway requires Terraform AWS provider >= 6.24.0.
118+
89119
### One NAT Gateway per subnet (default)
90120

91121
By default, the module will determine the number of NAT Gateways to create based on the `max()` of the private subnet lists (`database_subnets`, `elasticache_subnets`, `private_subnets`, and `redshift_subnets`). The module **does not** take into account the number of `intra_subnets`, since the latter are designed to have no Internet access via NAT Gateway. For example, if your configuration looks like the following:
@@ -111,6 +141,43 @@ If `one_nat_gateway_per_az = true` and `single_nat_gateway = false`, then the mo
111141
- The variable `var.azs` **must** be specified.
112142
- The number of public subnet CIDR blocks specified in `public_subnets` **must** be greater than or equal to the number of availability zones specified in `var.azs`. This is to ensure that each NAT Gateway has a dedicated public subnet to deploy to.
113143

144+
### Regional NAT Gateway
145+
146+
Regional NAT Gateway is a highly available NAT solution that automatically scales across multiple Availability Zones within your VPC. It provides a single NAT Gateway that serves all Availability Zones, eliminating the need for multiple zonal NAT Gateways.
147+
148+
**Key Features:**
149+
- **Single NAT Gateway**: One NAT Gateway serves all Availability Zones in your VPC
150+
- **Automatic High Availability**: Automatically expands and contracts across AZs based on workload distribution
151+
- **No Public Subnets Required**: Regional NAT Gateways operate without requiring public subnets (though public subnets can still be created for other purposes)
152+
- **Simplified Management**: Single NAT Gateway ID for consistent route entries across all subnets
153+
- **Increased Capacity**: Supports up to 32 Elastic IP addresses per AZ (compared to 8 for zonal NAT Gateways)
154+
155+
**Configuration:**
156+
157+
```hcl
158+
enable_nat_gateway = true
159+
nat_gateway_connectivity_type = {
160+
availability_mode = "regional" # "regional" or "zonal"
161+
eip_allocation = "auto" # "auto" or "manual"
162+
}
163+
```
164+
165+
**EIP Allocation Options:**
166+
- `"auto"`: AWS automatically provisions and manages EIPs for the Regional NAT Gateway
167+
- `"manual"`: You provide EIPs via `external_nat_ip_ids` (one EIP per AZ). The module will create EIPs based on the number of AZs if `reuse_nat_ips = false`
168+
169+
**Important Notes:**
170+
1. **Expansion Timing**: When deploying workloads in a new AZ, the regional NAT Gateway typically takes 15-20 minutes (up to 60 minutes) to expand to that AZ. During this period, traffic may be temporarily routed through existing AZs.
171+
2. **Private Connectivity**: Regional NAT Gateways do not support private connectivity. For workloads requiring private connectivity, continue using zonal NAT Gateways.
172+
3. **Availability**: This feature is available in all commercial AWS Regions, except for AWS GovCloud (US) Regions and China Regions.
173+
4. **Cost Considerations**: Regional NAT Gateways are charged per hour and per GB processed, similar to zonal NAT Gateways, but you only pay for one NAT Gateway instead of multiple.
174+
175+
**Requirements:**
176+
- Terraform AWS provider >= 6.24.0
177+
- The variable `var.azs` **must** be specified
178+
179+
See the [regional-nat example](examples/regional-nat/) for a complete working example.
180+
114181
## "private" versus "intra" subnets
115182

116183
By default, if NAT Gateways are enabled, private subnets will be configured with routes for Internet traffic that point at the NAT Gateways configured by use of the above options.
@@ -488,7 +555,7 @@ No modules.
488555
| <a name="input_map_public_ip_on_launch"></a> [map\_public\_ip\_on\_launch](#input\_map\_public\_ip\_on\_launch) | Specify true to indicate that instances launched into the subnet should be assigned a public IP address. Default is `false` | `bool` | `false` | no |
489556
| <a name="input_name"></a> [name](#input\_name) | Name to be used on all the resources as identifier | `string` | `""` | no |
490557
| <a name="input_nat_eip_tags"></a> [nat\_eip\_tags](#input\_nat\_eip\_tags) | Additional tags for the NAT EIP | `map(string)` | `{}` | no |
491-
| <a name="input_nat_gateway_connectivity_type"></a> [nat\_gateway\_connectivity\_type](#input\_nat\_gateway\_connectivity\_type) | Connectivity type for the NAT Gateway. Valid values are:<br/>- 'zonal' (default): Traditional AZ-specific NAT gateways that require public subnets<br/>- 'regional': A single NAT Gateway that automatically scales across all AZs (does not require public subnets)<br/><br/>Regional NAT Gateway support requires Terraform AWS provider >= 6.24.0.<br/>When using 'regional' mode, only one NAT Gateway is created for the entire VPC. | `string` | `"zonal"` | no |
558+
| <a name="input_nat_gateway_connectivity_type"></a> [nat\_gateway\_connectivity\_type](#input\_nat\_gateway\_connectivity\_type) | Configuration block for NAT Gateway connectivity type.<br/>- availability_mode: "zonal" (default) or "regional"<br/> - 'zonal': Traditional AZ-specific NAT gateways that require public subnets<br/> - 'regional': A single NAT Gateway that automatically scales across all AZs (does not require public subnets)<br/>- eip_allocation: "auto" (default) or "manual"<br/> - 'auto': Automatically provision EIPs for the NAT Gateway<br/> - 'manual': Use existing EIPs provided via external_nat_ip_ids<br/><br/>Regional NAT Gateway support requires Terraform AWS provider >= 6.24.0.<br/>When using 'regional' mode, only one NAT Gateway is created for the entire VPC. | `object({ availability_mode = string, eip_allocation = optional(string, "auto") })` | `{ availability_mode = "zonal", eip_allocation = "auto" }` | no |
492559
| <a name="input_nat_gateway_destination_cidr_block"></a> [nat\_gateway\_destination\_cidr\_block](#input\_nat\_gateway\_destination\_cidr\_block) | Used to pass a custom destination route for private NAT Gateway. If not specified, the default 0.0.0.0/0 is used as a destination route | `string` | `"0.0.0.0/0"` | no |
493560
| <a name="input_nat_gateway_tags"></a> [nat\_gateway\_tags](#input\_nat\_gateway\_tags) | Additional tags for the NAT gateways | `map(string)` | `{}` | no |
494561
| <a name="input_one_nat_gateway_per_az"></a> [one\_nat\_gateway\_per\_az](#input\_one\_nat\_gateway\_per\_az) | Should be true if you want only one NAT Gateway per availability zone. Requires `var.azs` to be set, and the number of `public_subnets` created to be greater than or equal to the number of availability zones specified in `var.azs` | `bool` | `false` | no |

examples/regional-nat/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ Note that this example may create resources which can cost money (AWS Elastic IP
3838
The key configuration for Regional NAT Gateway is:
3939

4040
```hcl
41-
enable_nat_gateway = true
42-
nat_gateway_connectivity_type = "regional"
41+
enable_nat_gateway = true
42+
nat_gateway_connectivity_type = {
43+
availability_mode = "regional" # "regional" or "zonal"
44+
eip_allocation = "auto" # "auto" or "manual"
45+
}
4346
```
4447

4548
## Comparison: Regional vs Zonal NAT Gateway

examples/regional-nat/main.tf

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ module "vpc" {
3131

3232
# Regional NAT Gateway Configuration
3333
# Requires Terraform AWS provider >= 6.24.0
34-
enable_nat_gateway = true
35-
nat_gateway_connectivity_type = "regional"
36-
tags = local.tags
34+
enable_nat_gateway = true
35+
nat_gateway_connectivity_type = {
36+
availability_mode = "regional" # "regional" or "zonal"
37+
eip_allocation = "auto" # "auto" or "manual", for availablility_mode = "zonal", eip_allocation won't be used
38+
}
39+
tags = local.tags
3740
}

main.tf

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,21 +1207,25 @@ resource "aws_route" "private_ipv6_egress" {
12071207
################################################################################
12081208

12091209
locals {
1210-
nat_gateway_is_regional = var.nat_gateway_connectivity_type == "regional"
1211-
nat_gateway_count = local.nat_gateway_is_regional ? 1 : var.single_nat_gateway ? 1 : var.one_nat_gateway_per_az ? length(var.azs) : local.max_subnet_length
1210+
nat_gateway_is_regional = var.nat_gateway_connectivity_type.availability_mode == "regional"
1211+
nat_gateway_count = local.nat_gateway_is_regional ? 1 : var.single_nat_gateway || var.nat_gateway_connectivity_type.availability_mode == "zonal" ? 1 : var.one_nat_gateway_per_az ? length(var.azs) : local.max_subnet_length
12121212
nat_gateway_ips = var.reuse_nat_ips ? var.external_nat_ip_ids : aws_eip.nat[*].id
1213+
1214+
# Regional NAT Gateway EIP handling
1215+
# Always create EIPs automatically based on the number of AZs
1216+
regional_nat_gateway_eip_count = local.nat_gateway_is_regional ? length(var.azs) : 0
12131217
}
12141218

12151219
resource "aws_eip" "nat" {
1216-
count = local.create_vpc && var.enable_nat_gateway && !var.reuse_nat_ips ? local.nat_gateway_count : 0
1220+
count = local.create_vpc && var.enable_nat_gateway && !local.nat_gateway_is_regional && !var.reuse_nat_ips && (var.nat_gateway_connectivity_type.availability_mode == "zonal" || var.nat_gateway_connectivity_type.availability_mode == null) ? local.nat_gateway_count : 0
12171221

12181222
region = var.region
12191223

12201224
domain = "vpc"
12211225

12221226
tags = merge(
12231227
{
1224-
"Name" = local.nat_gateway_is_regional ? var.name : format(
1228+
"Name" = format(
12251229
"${var.name}-%s",
12261230
element(var.azs, var.single_nat_gateway ? 0 : count.index),
12271231
)
@@ -1233,6 +1237,27 @@ resource "aws_eip" "nat" {
12331237
depends_on = [aws_internet_gateway.this]
12341238
}
12351239

1240+
resource "aws_eip" "regional_nat" {
1241+
count = local.create_vpc && var.enable_nat_gateway && local.nat_gateway_is_regional && var.nat_gateway_connectivity_type.eip_allocation == "manual" && var.nat_gateway_connectivity_type.availability_mode == "regional" ? local.regional_nat_gateway_eip_count : 0
1242+
1243+
region = var.region
1244+
1245+
domain = "vpc"
1246+
1247+
tags = merge(
1248+
{
1249+
"Name" = format(
1250+
"${var.name}-%s",
1251+
element(var.azs, count.index),
1252+
)
1253+
},
1254+
var.tags,
1255+
var.nat_eip_tags,
1256+
)
1257+
1258+
depends_on = [aws_internet_gateway.this]
1259+
}
1260+
12361261
resource "aws_nat_gateway" "this" {
12371262
count = local.create_vpc && var.enable_nat_gateway && !local.nat_gateway_is_regional ? local.nat_gateway_count : 0
12381263

@@ -1268,7 +1293,17 @@ resource "aws_nat_gateway" "regional" {
12681293
vpc_id = aws_vpc.this[0].id
12691294

12701295
connectivity_type = "public"
1271-
availability_mode = "regional"
1296+
availability_mode = var.nat_gateway_connectivity_type.availability_mode
1297+
1298+
dynamic "availability_zone_address" {
1299+
for_each = var.nat_gateway_connectivity_type.eip_allocation == "manual" && var.nat_gateway_connectivity_type.availability_mode == "regional" ? {
1300+
for idx, az in var.azs : az => aws_eip.regional_nat[idx].id
1301+
} : {}
1302+
content {
1303+
allocation_ids = toset([availability_zone_address.value])
1304+
availability_zone = availability_zone_address.key
1305+
}
1306+
}
12721307

12731308
tags = merge(
12741309
{

outputs.tf

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,15 @@ output "intra_network_acl_arn" {
511511

512512
output "nat_ids" {
513513
description = "List of allocation ID of Elastic IPs created for AWS NAT Gateway"
514-
value = aws_eip.nat[*].id
514+
value = concat(aws_eip.nat[*].id, aws_eip.regional_nat[*].id)
515515
}
516516

517517
output "nat_public_ips" {
518518
description = "List of public Elastic IPs created for AWS NAT Gateway"
519-
value = var.reuse_nat_ips ? var.external_nat_ips : aws_eip.nat[*].public_ip
519+
value = concat(
520+
var.reuse_nat_ips ? var.external_nat_ips : aws_eip.nat[*].public_ip,
521+
var.nat_gateway_connectivity_type.availability_mode == "regional" ? aws_eip.regional_nat[*].public_ip : []
522+
)
520523
}
521524

522525
output "natgw_ids" {

variables.tf

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,19 +1236,27 @@ variable "one_nat_gateway_per_az" {
12361236

12371237
variable "nat_gateway_connectivity_type" {
12381238
description = <<-EOT
1239-
Connectivity type for the NAT Gateway. Valid values are:
1240-
- 'zonal' (default): Traditional AZ-specific NAT gateways that require public subnets
1241-
- 'regional': A single NAT Gateway that automatically scales across all AZs (does not require public subnets)
1242-
1243-
Regional NAT Gateway support requires Terraform AWS provider >= 6.24.0.
1244-
When using 'regional' mode, only one NAT Gateway is created for the entire VPC.
1239+
Configuration block for NAT Gateway connectivity type.
1240+
- availability_mode: "zonal" (default) or "regional"
1241+
- 'zonal': Traditional AZ-specific NAT gateways that require public subnets
1242+
- 'regional': A single NAT Gateway that automatically scales across all AZs (does not require public subnets)
1243+
- eip_allocation: "auto" (default) or "manual"
1244+
- 'auto': Automatically provision EIPs for the NAT Gateway
1245+
- 'manual': Will create the set of EIPs based on the number of AZs
12451246
EOT
1246-
type = string
1247-
default = "zonal"
1248-
validation {
1249-
condition = contains(["zonal", "regional"], var.nat_gateway_connectivity_type)
1250-
error_message = "The nat_gateway_connectivity_type must be either 'zonal' or 'regional'."
1251-
}
1247+
type = object({
1248+
availability_mode = string # "zonal" or "regional"
1249+
eip_allocation = string # "auto" or "manual"
1250+
})
1251+
default = { availability_mode = null, eip_allocation = null }
1252+
# validation {
1253+
# condition = contains(["zonal", "regional"], var.nat_gateway_connectivity_type.availability_mode)
1254+
# error_message = "The availability_mode must be either 'zonal' or 'regional'."
1255+
# }
1256+
# validation {
1257+
# condition = contains(["auto", "manual"], var.nat_gateway_connectivity_type.eip_allocation)
1258+
# error_message = "The eip_allocation must be either 'auto' or 'manual'."
1259+
# }
12521260
}
12531261

12541262
variable "reuse_nat_ips" {
@@ -1264,7 +1272,7 @@ variable "external_nat_ip_ids" {
12641272
}
12651273

12661274
variable "external_nat_ips" {
1267-
description = "List of EIPs to be used for `nat_public_ips` output (used in combination with reuse_nat_ips and external_nat_ip_ids)"
1275+
description = "List of EIPs to be used for `nat_public_ips` output (used in combination with reuse_nat_ips and external_nat_ip_ids). For regional NAT gateways, EIPs will be mapped to availability zones in order."
12681276
type = list(string)
12691277
default = []
12701278
}

wrappers/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ module "wrapper" {
232232
map_public_ip_on_launch = try(each.value.map_public_ip_on_launch, var.defaults.map_public_ip_on_launch, false)
233233
name = try(each.value.name, var.defaults.name, "")
234234
nat_eip_tags = try(each.value.nat_eip_tags, var.defaults.nat_eip_tags, {})
235-
nat_gateway_connectivity_type = try(each.value.nat_gateway_connectivity_type, var.defaults.nat_gateway_connectivity_type, "zonal")
235+
nat_gateway_connectivity_type = try(each.value.nat_gateway_connectivity_type, var.defaults.nat_gateway_connectivity_type, { availability_mode = null, eip_allocation = null })
236236
nat_gateway_destination_cidr_block = try(each.value.nat_gateway_destination_cidr_block, var.defaults.nat_gateway_destination_cidr_block, "0.0.0.0/0")
237237
nat_gateway_tags = try(each.value.nat_gateway_tags, var.defaults.nat_gateway_tags, {})
238238
one_nat_gateway_per_az = try(each.value.one_nat_gateway_per_az, var.defaults.one_nat_gateway_per_az, false)

0 commit comments

Comments
 (0)