Terraform AWS: How to provision one public subnet per AZ and multiple subnets for private and database subnets per AZ?

huangapple go评论82阅读模式
英文:

Terraform AWS: How to provision one public subnet per AZ and multiple subnets for private and database subnets per AZ?

问题

Sure, here is the translated code:

以下是 provisions 的代码,用于定义 private_subnets、database_subnets 和 public_subnets 的子网(参考 cidrsubnets(var.cidr, 4, 4, 4))。
有三个可用区。它 provisions 六个 private_subnets、六个 database_subnets 和六个 public_subnets。每个可用区有两个子网(参考代码:cidrsubnets(cidr_block, 4, 4, 4, 4, 4, 4))。

azs = slice(data.aws_availability_zones.available.names, 0, 3)
subnets = [for cidr_block in cidrsubnets(var.cidr, 4, 4, 4) : cidrsubnets(cidr_block, 4, 4, 4, 4, 4, 4)]
private_subnets = local.subnets[0]
database_subnets = local.subnets[1]
public_subnets = local.subnets[2]

Outputs:
private_subnet_ids_per_az = {
  "us-west-2a" = tolist([
    "subnet-01bc44399240a22c2",
    "subnet-0465fe06d7826ef05",
  ])
  "us-west-2b" = tolist([
    "subnet-08abc356b49f09b5c",
    "subnet-006dd6b818f7afb89",
  ])
  "us-west-2c" = tolist([
    "subnet-00319273cb1a389af",
    "subnet-058adb50bf65377e8",
  ])
}
public_subnet_ids_per_az = {
  "us-west-2a" = tolist([
    "subnet-01134239b0849264e",
    "subnet-06748362d73fd82b3",
  ])
  "us-west-2b" = tolist([
    "subnet-0e6fbc9252f72b32c",
    "subnet-03aca82e1973fb2d9",
  ])
  "us-west-2c" = tolist([
    "subnet-00015dec4d8525c01",
    "subnet-0fd832a71b9b1bd88",
  ])
}

请注意,这段代码描述了如何为每个可用区 provision 多个子网。如果您想要每个可用区只 provisions 一个 public 子网,以及两个 private 和 database 子网,可能需要修改代码以实现这个目标。

英文:

The code as follows provisions subnets of private_subnets, database_subnets and public_subnets (refer to cidrsubnets(var.cidr, 4, 4, 4)).
There are three AZs. It provisions six private_subnets, six database_subnets and six public_subnets. Each AZ has two subnets (refer to the code: cidrsubnets(cidr_block, 4, 4, 4, 4, 4, 4).

  azs = slice(data.aws_availability_zones.available.names, 0, 3)
  subnets = [for cidr_block in cidrsubnets(var.cidr, 4, 4, 4) : cidrsubnets(cidr_block, 4, 4, 4, 4, 4, 4)]
  private_subnets = local.subnets[0]
  database_subnets = local.subnets[1]
  public_subnets = local.subnets[2]

Outputs:

private_subnet_ids_per_az = {
  "us-west-2a" = tolist([
    "subnet-01bc44399240a22c2",
    "subnet-0465fe06d7826ef05",
  ])
  "us-west-2b" = tolist([
    "subnet-08abc356b49f09b5c",
    "subnet-006dd6b818f7afb89",
  ])
  "us-west-2c" = tolist([
    "subnet-00319273cb1a389af",
    "subnet-058adb50bf65377e8",
  ])
}
public_subnet_ids_per_az = {
  "us-west-2a" = tolist([
    "subnet-01134239b0849264e",
    "subnet-06748362d73fd82b3",
  ])
  "us-west-2b" = tolist([
    "subnet-0e6fbc9252f72b32c",
    "subnet-03aca82e1973fb2d9",
  ])
  "us-west-2c" = tolist([
    "subnet-00015dec4d8525c01",
    "subnet-0fd832a71b9b1bd88",
  ])
}

I would like to only provision one public subnet per AZ. And two private and database subnets per AZ. Is it possible?
As follows is the entire code:
main.tf

provider "aws" {
  region = var.region
}

terraform {
  # The configuration for this backend will be filled in by Terragrunt
  backend "s3" {}
}

data "aws_availability_zones" "available" {
  state = "available"
}

locals {

  tags = {
    EKS           = var.cluster_name
    ProvisionedBy = "Terraform"
    Environment   = var.environment
  }

  number_azs = var.number_azs

  azs = slice(data.aws_availability_zones.available.names, 0, local.number_azs)
  subnets = [for cidr_block in cidrsubnets(var.cidr, 4, 4, 4) : cidrsubnets(cidr_block, 4, 4, 4, 4, 4, 4)]
  private_subnets = local.subnets[0]
  database_subnets = local.subnets[1]
  public_subnets = local.subnets[2]

  public_subnet_ids_azs   = {for k, v in data.aws_subnets.public_subnets_azs : tolist(v.filter)[0].values[0] => v.ids if length(v.ids) > 0}
  private_subnet_ids_azs  = {for k, v in data.aws_subnets.private_subnets_azs : tolist(v.filter)[0].values[0] => v.ids if length(v.ids) > 0}

}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  cidr                                            = var.cidr
  name                                            = "vpc-${var.cluster_name}"
  azs                                             = local.azs
  private_subnets                                 = local.private_subnets
  public_subnets                                  = local.public_subnets
  database_subnets                                = local.database_subnets
  enable_nat_gateway                              = true
  single_nat_gateway                              = false
  one_nat_gateway_per_az                          = true
  create_database_subnet_route_table              = true
  enable_dns_hostnames                            = true
  enable_dns_support                              = true

  tags                                            = local.tags

  public_subnet_tags = {
    "subnet"                                       = "public"
  }

  private_subnet_tags = {
    "subnet"                                       = "private"
  }
}

data "aws_subnets" "public_subnets_azs" {
  for_each = toset(data.aws_availability_zones.available.names)

  filter {
    name   = "vpc-id"
    values = [module.vpc.vpc_id]
  }

  filter {
    name   = "tag:subnet"
    values = ["public"]
  }

  filter {
    name   = "availability-zone"
    values = ["${each.value}"]
  }
}

data "aws_subnets" "private_subnets_azs" {
  for_each = toset(data.aws_availability_zones.available.names)

  filter {
    name   = "vpc-id"
    values = [module.vpc.vpc_id]
  }

  filter {
    name   = "tag:subnet"
    values = ["private"]
  }

  filter {
    name   = "availability-zone"
    values = ["${each.value}"]
  }
}

outputs.tf

output "vpc_id" {
  description = "The ID of the VPC"
  value       = module.vpc.vpc_id
}

output "azs" {
  description = "A list of availability zones specified as argument"
  value       = local.azs
}

output "public_subnet_ids_per_az" {
  description = "List of IDs of public subnets"
  value       = local.public_subnet_ids_azs
}

output "private_subnet_ids_per_az" {
  description = "List of IDs of private subnets"
  value       = local.private_subnet_ids_azs
}

variables.tf

variable "region" {
  description = "Region for the VPC"
  default     = "us-west-2"
}

variable "cluster_name" {
  type = string
  default = "eks-test"
}

variable "environment" {
  default = "test"
}

variable "cidr" {
  default = "10.2.0.0/16"
}

variable "number_azs" {
  type        = number
  description = "Number of Availability Zones to create resources in the VPC."
  default     = 3

  validation {
    condition     = var.number_azs > 0 && var.number_azs < 4
    error_message = "The number of AZs to configure has to be between 1 - 3."
  }
}

Note: I am working on provision AWs network firewall. It requires to have one firewall subnets per AZ. I will be able to do it if I am able to provision one public subnet per AZ. Thank you.

When I replaced the line, public_subnets = local.subnets[2] with

public_subnets = slice(local.subnets[2], 0, 2) # takes the first 3 items from the list, I got errors as follows:

data.aws_availability_zones.available: Reading...
data.aws_availability_zones.available: Read complete after 0s [id=us-west-2]
╷
│ Error: Error in function call
│
│   on .terraform/modules/vpc/main.tf line 142, in resource "aws_route_table_association" "public":
│  142:   subnet_id      = element(aws_subnet.public[*].id, count.index)
│     ├────────────────
│     │ while calling element(list, index)
│     │ aws_subnet.public is empty tuple
│     │ count.index is 0
│
│ Call to function "element" failed: cannot use element function with an
│ empty list.
╵
╷
│ Error: Error in function call
│
│   on .terraform/modules/vpc/main.tf line 142, in resource "aws_route_table_association" "public":
│  142:   subnet_id      = element(aws_subnet.public[*].id, count.index)
│     ├────────────────
│     │ while calling element(list, index)
│     │ aws_subnet.public is empty tuple
│     │ count.index is 1
│
│ Call to function "element" failed: cannot use element function with an
│ empty list.
╵
╷
│ Error: Error in function call
│
│   on .terraform/modules/vpc/main.tf line 1067, in resource "aws_nat_gateway" "this":
│ 1067:   subnet_id = element(
│ 1068:     aws_subnet.public[*].id,
│ 1069:     var.single_nat_gateway ? 0 : count.index,
│ 1070:   )
│     ├────────────────
│     │ while calling element(list, index)
│     │ aws_subnet.public is empty tuple
│     │ count.index is 0
│     │ var.single_nat_gateway is false
│
│ Call to function "element" failed: cannot use element function with an
│ empty list.
╵
╷
│ Error: Error in function call
│
│   on .terraform/modules/vpc/main.tf line 1067, in resource "aws_nat_gateway" "this":
│ 1067:   subnet_id = element(
│ 1068:     aws_subnet.public[*].id,
│ 1069:     var.single_nat_gateway ? 0 : count.index,
│ 1070:   )
│     ├────────────────
│     │ while calling element(list, index)
│     │ aws_subnet.public is empty tuple
│     │ count.index is 1
│     │ var.single_nat_gateway is false
│
│ Call to function "element" failed: cannot use element function with an
│ empty list.
╵
╷
│ Error: Error in function call
│
│   on .terraform/modules/vpc/main.tf line 1067, in resource "aws_nat_gateway" "this":
│ 1067:   subnet_id = element(
│ 1068:     aws_subnet.public[*].id,
│ 1069:     var.single_nat_gateway ? 0 : count.index,
│ 1070:   )
│     ├────────────────
│     │ while calling element(list, index)
│     │ aws_subnet.public is empty tuple
│     │ count.index is 2
│     │ var.single_nat_gateway is false
│
│ Call to function "element" failed: cannot use element function with an
│ empty list.
╵
Releasing state lock. This may take a few moments...
ERRO[0007] Terraform invocation failed in /home/perryluo/firewall/try
ERRO[0007] 1 error occurred:
        * exit status 1

答案1

得分: 1

以下是翻译好的部分:

如果您不介意非连续的区块,一个简单的选择是使用slice函数从列表中获取前三个项目。

  subnets = [for cidr_block in cidrsubnets(var.cidr, 4, 4, 4) : cidrsubnets(cidr_block, 4, 4, 4, 4, 4, 4)]
  private_subnets = local.subnets[0]
  database_subnets = local.subnets[1]
  public_subnets = slice(local.subnets[2], 0, 3) # 从列表中获取前3个项目

Terraform文档中的Slice函数

更改之前的计划输出尝试创建62个资源

计划:62个添加,0个更改,0个销毁。

Outputs的更改:
  + azs                       = [
      + "us-west-2a",
      + "us-west-2b",
      + "us-west-2c",
    ]
  + private_subnet_ids_per_az = (申请后已知)
  + public_subnet_ids_per_az  = (申请后已知)
  + vpc_id                    = (申请后已知)

更改后的计划将仅创建56个资源

计划:56个添加,0个更改,0个销毁。

Outputs的更改:
  + azs                       = [
      + "us-west-2a",
      + "us-west-2b",
      + "us-west-2c",
    ]
  + private_subnet_ids_per_az = (申请后已知)
  + public_subnet_ids_per_az  = (申请后已知)
  + vpc_id                    = (申请后已知)

英文:

If you don't mind having non-consecutive blocks, a straightforward option is to get the first three items from the list using the slice function.

  subnets = [for cidr_block in cidrsubnets(var.cidr, 4, 4, 4) : cidrsubnets(cidr_block, 4, 4, 4, 4, 4, 4)]
  private_subnets = local.subnets[0]
  database_subnets = local.subnets[1]
  public_subnets = slice(local.subnets[2], 0, 3) # takes the first 3 items from the list

Slice function in terraform docs

The plan output before the change tries to create 62 resources

Plan: 62 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + azs                       = [
      + "us-west-2a",
      + "us-west-2b",
      + "us-west-2c",
    ]
  + private_subnet_ids_per_az = (known after apply)
  + public_subnet_ids_per_az  = (known after apply)
  + vpc_id                    = (known after apply)

After the change the plan will only create 56 resources

Plan: 56 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + azs                       = [
      + "us-west-2a",
      + "us-west-2b",
      + "us-west-2c",
    ]
  + private_subnet_ids_per_az = (known after apply)
  + public_subnet_ids_per_az  = (known after apply)
  + vpc_id                    = (known after apply)

huangapple
  • 本文由 发表于 2023年6月9日 02:52:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76434902.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定