用多云Terraform代码进行模块化的多账户 “与时俱进的多云”

首先

你好,我是 Terraform 的二年级用户。

最近我沉迷于使用Terraform。现在我将介绍我正在进行的项目中所遇到的问题。

适合这种人

アカウントやクラウドをまたいでTerraformでコード管理している人

Module化したコード上でMultiple Provider Configurationsを見てもどうやってもTerraform plan/applyが通らない人

事情的开始

目前参与的项目采用了AWS+公司自有数据中心(本地环境)的混合云架构。

我們決定在AWS上使用ECS+NLB來構建能夠處理來自本地伺服器的請求的系統。

为了从本地服务器连接到NLB,您需要在Route53私有主机区域中添加别名记录来解析NLB的私有DNS名称。

然而,已经存在一个以在另一个AWS账户中构建为导向的Route53私有区域+解析器的混合云环境。

因此,作为一个项目

尽量共享资源吧。另外,请把私有主机区域集中到一个地方!因为直接连接和解析器真是太贵了!(超级翻译)

因此,我们决定采用多帐户设置的方式来构建私有主机区域。

此外,由于两个环境都使用了基于Terraform的IaC来进行配置管理,因此也需要以Terraform代码来进行智能化管理。那么,我们该如何处理呢?

请以中文将以下句子表达出来,仅需要一个选项:

我想实现的目标。

首先将要实现的事情概括出来。

作为要件,以下的事项如下。

    • 構築済みAWSアカウント(以下、Account A)上で構築されているRoute53プライベートホストゾーン上に、今回新しく構築するAWSアカウント(以下、Account B)のNLBに紐づくプライベートDNS名のエイリアスレコードを作成する。

 

    • オンプレミス環境からの名前解決およびRoute53 Resolverのコストを考慮し、Route53プライベートホストゾーンはAccount A上のものを使用する。

 

    オンプレミスからAccount Bへの接続経路はコストを考慮し、Acount Aにて敷設済みDirect connect Gatewayを経由する。
system_architecture_image.png

①成为从本地服务器进行名称解析的路径。

我們使用Route53 Resolver來解析名稱,使其能夠解析至私有主機區域。

如果在步骤①中,名字解析能够成功并且得到与帐户B上的NLB相关联的私有IP地址,那么它将成为连接路径。

另外,尽管图中没有显示,B账户上也存在着EC2实例,并且与私有云服务器一样,会与NLB进行通信,因此还会进行VPC对等连接和私有主机域的跨账户引用。

不过,由于偏离了主题,在这里将其省略了。

现在,问题在于如何将要注册到私有主机区域的私有DNS名称反映出来。

Alias记录将被注册到NLB的私有DNS名称中。由于在创建时会分配唯一的值给NLB的私有DNS名称,因此无法由使用者来确定。

因此,无法事先创建别名记录。

另外,例如在对于Account B进行Terraform apply之后,检查NLB的私有DNS并将其反映到Account A的Terraform代码中,然后再进行Terraform apply的做法绝对无法在实际运营中长久地承受。

利用多供应商配置

在使用Terraform的Multiple Provider Configurations中,参考了一篇名为”使用Terraform的Multiple Provider Configurations轻松构建AWS跨账户环境”的文章后,决定采用Multiple Provider Configurations中提到的代码结构,进行AWS跨账户的环境配置。

Multi Provider Configurations 是指为账户 A 和账户 B 准备不同的提供者(定义了区域和连接配置的)并在 Terraform 代码中根据需要进行切换。(如果有错误,请指正)

在已经进行模块化的代码中,需要怎样书写呢?

有关这一主题已经有了一些文章,但是它们只是停留在“只需要这样写”的解释上,并没有提及如何处理模块化的代码结构。

以下是本次针对Account B所用的Terraform代码的结构。

基本上,环境相关参数和模块化代码(以下简称模块代码)被严格地分离开来,此次需要添加的代码将属于模块代码一侧。

■ 修改之前的文件目录结构

|-- envs ★環境面ごとのパラメータを定義
|   |-- prd
|   |   |-- resource.tf  ★このファイルが起点となり、modules配下のModuleコードを呼び出す
|   |   `-- versions.tf
|   |-- stg1
|   |   |-- :
|   |   `-- versions.tf
|   |-- stg2
|   |   |-- :
|   |   `-- versions.tf
|   `-- stgX
|       |-- :
|       `-- versions.tf
|
`-- modules  ★Moduleコードを定義
    |-- compute
    |-- container
    |-- datasync
    |-- loadbalancer
    |   |-- lb.tf  ★NLBに関するコードを格納
    |   `-- variable.tf
    `-- :

■envs//resource.tf

■envs/<环境>/resource.tf = ■envs//resource.tf.

provider "aws" {
  profile = "<Account B用AWS Profile名>" ※後述するAWS Profile例では"accountB"
  region  = "ap-northeast-1"
}

module "loadbalancer" {
  source = "../../modules/loadbalancer"
    :
}

■ envs/<环境>/version.tf
■ 环境/<环境>/版本.tf

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "4.35.0"
    }
  }
  required_version = "1.1.7"
}

■模块/负载均衡器/lb.tf

resource "aws_lb" "nlb" {
    :
}

由于作为提供者进行注册的参数需要根据每个环境进行不同的设置,因此需要在/envs目录下的每个环境面进行定义。

因此,在环境依赖参数中定义的提供者需要传递给模块代码,我们面临了一个问题。

如果这样做就好了。

话虽说得有点长,但是我会先给出已经经过测试的代码。

确认过的环境如下所示。

Terraform:4.35.0

AWS提供商:1.1.7版本

■文件和目录结构经过修改后

|-- envs
|   |-- prd
|   |   |-- resource.tf <-- Change !!
|   |   `-- versions.tf
|   |-- stg1
|   |   |-- :
|   |   `-- versions.tf
|   |-- stg2
|   |   |-- :
|   |   `-- versions.tf
|   `-- stgX
|       |-- :
|       `-- versions.tf
|
`-- modules ★Moduleコードを定義
    |-- compute
    |-- container
    |-- datasync
    |-- dns  <--New !!
    |   |-- dns.tf <--New !!
    |   |-- providers.tf <--New !!
    |   `-- variable.tf <--New !!
    |-- loadbalancer
    |   |-- lb.tf
    |   |-- output.tf <-- New !!
    |   `-- variable.tf
    |-- :
    `-- :

■envs/<环境>/资源.tf

# Provider for Account B
provider "aws" {
  profile = "<Account B用AWS Profile名>" ※後述するAWS Profile例では"accountB"
  region  = "ap-northeast-1"
}

# Provider for Account A
provider "aws" {
  alias   = "accountA"
  profile = "<Account A用AWS Profile名>" ※後述するAWS Profile例では"accountA"
  region  = "ap-northeast-1"
}

module "loadbalancer" {
  source = "../../modules/loadbalancer"
    :
}

module "dns" {
  source = "../../modules/dns"
  providers = {
    aws = aws
    aws.accoutA = aws.accountA
  }

  lb_alias_name = module.loadbalancer.accountB_lb_dns_name
  lb_alias_zone_id = module.loadbalancer.accountB_lb_dns_name
}

■模块/dns/providers.tf

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "4.35.0"
      configuration_aliases = [ aws.accountA ]
    }
  }
  required_version = "1.1.7"
}

■模块/域名系统/变量.tf

variable "lb_alias_name" {}
variable "lb_alias_zone_id" {}

■模块/域名系统/dns.tf

data "aws_route53_zone" "accountA_zone" {
  provider     = aws.accountA
  name         = "account_a.internal.jp"
  private_zone = true
}

resource "aws_route53_record" "log-aggregator" {
  provider = aws.accountA
  zone_id  = data.aws_route53_zone.accountA.zone_id
  name     = "account_b_nlb"
  type     = "A"

  alias {
    name                   = var.lb_alias_name
    zone_id                = var.lb_alias_zone_id
    evaluate_target_health = true
  }
}

■模块/负载均衡器/output.tf

output "accountB_lb_dns_name" {
  value = nlb.lb_dns_name
}

output "accountB_lb_dns_name" {
  value = nlb.lb_zone_id
}

以下,我将加入解释说明。

・envs/<环境>/资源.tf
# Provider for Account B
provider "aws" {
  profile = "<Account B用AWS Profile名>" ※後述するAWS Profile例では"accountB"
  region  = "ap-northeast-1"
}

# Provider for Account A
provider "aws" {
  alias   = "accountA"
  profile = "<Account A用AWS Profile名>" ※後述するAWS Profile例では"accountA"
  region  = "ap-northeast-1"
}

我正在添加和定义与帐户A相关的提供者。

如果没有指定后续提供者,则在Account B中设置默认提供者,同时Account A已经指定了别名。

为了减小对现有代码的影响,本次将对现有代码进行添加。如果在不明确定义提供者的情况下,我们将按照传统代码的方式选择暗示地选择帐户B的提供者。

module "dns" {
  source = "../../modules/dns"
  providers = {
    aws = aws
    aws.accountA = aws.accountA
  }

  lb_alias_name = module.loadbalancer.accountB_lb_dns_name
  lb_alias_zone_id = module.loadbalancer.accountB_lb_dns_name
}

这里正在调用与私有托管区域相关的模块代码。

这次将成为关键的部分一。

在Module代码中,将Provider的设置传递给Account A和Account B,分别在providers内传递各自的Provider。

当阅读Multi Provider Configurations时,你可能会认为应该按照以下方式写出它,但是在本次的代码中,计划却没有通过。

module "dns" {
  source = "../../modules/dns"
  providers = {
    aws = aws.accountA ※ダメな書き方
  }

在NLB部署模块代码中获取私有DNS名称和HostedZoneId,并将其分别命名为module.loadbalancer.accountB_lb_dns_name和module.loadbalancer.accountB_lb_dns_name,以便在模块代码中将它们作为变量lb_alias_name和lb_alias_zone_id使用。

・模块/域名解析器/providers.tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "4.35.0"
      configuration_aliases = [ aws.accountA ]
    }
  }
  required_version = "1.1.7"
}

这是这次的重点话题之二。

在Multi Provider Configurations中提到的configuration_aliases是根据试错的结果,在Module代码中需要进行定义。

在模块中声明一个配置别名,以便从父模块接收替代提供者配置,只需将该提供者的条目添加到该参数即可。
由于明确规定了此事,回过头来看,确实是如此。但在不断尝试的过程中,我没有达到这一步骤,而是错误地将配置别名添加到envs/<环境>/version.tf中,导致陷入困境。

【modules/dns/dns.tf】的翻译选项如下:

– 模块/域名解析/域名解析.tf

data "aws_route53_zone" "accountA_zone" {
  provider     = aws.accountA
  name         = "account_a.internal.jp"
  private_zone = true
}

选择Account A的提供商,获取指定的私有主机区域的信息。

resource "aws_route53_record" "log-aggregator" {
  provider = aws.accountA
  zone_id  = data.aws_route53_zone.accountA.zone_id
  name     = "account_b_nlb"
  type     = "A"

  alias {
    name                   = var.lb_alias_name
    zone_id                = var.lb_alias_zone_id
    evaluate_target_health = true
  }
}

使用`data “aws_route53_zone” “accountA_zone”`获取的私有主机区域ID将被定义为`zone_id`,在NLB部署用Module代码中,通过输出定义获取私有DNS名称和HostedZoneId,并指定为别名的名称和`zone_id`,以创建别名记录。

请注意

执行Terraform命令需要访问Account A和Account B的权限。

因此,在执行AWS CLI环境中的Terraform plan/apply之前,需要设置具有变更权限的Profile来适用于两个环境。

以下是AWS配置文件的示例设置,这样可以在上述Terraform代码中进行引用。

■~/.aws/credentials(例如)

[accountA_iam]
aws_access_key_id = xxxxxxxxxxxxxxxxxxxx
aws_secret_access_key = ***************************************

[accountB_iam]
aws_access_key_id = yyyyyyyyyyyyyyyyyyy
aws_secret_access_key = ***************************************

~/.aws/config(示例)

[profile accountA]
region = ap-northeast-1
role_arn = arn:aws:iam::123456789012:role/DeveloperRole
source_profile = accountA_iam
output = json

[profile accountB]
region = ap-northeast-1
role_arn = arn:aws:iam::123456789013:role/DeveloperRole
source_profile = accountB_iam
output = json

最后

我們在這個使用情境中使用多供應商配置 (Multi Provider Configurations) 來管理同一個雲端內的多個帳號資源,並且使用單一的 Terraform 代碼。

Multi Provider Configurations明确指出,不仅可以在多个地区进行管理,而且可以在多云环境下使用单一的Terraform代码进行管理。

在理论上,我们认为可以组合使用AWS+ Azure,Azure+OCI。

也许Terraform的多供应商配置能够提供一个解决方案,满足希望采用多云架构却担心基础设施管理成本的需求。

在这篇文章中参考的文件

Provider Configuration – Configuration Language | Terraform | HashiCorp Developer

Terraform の Multiple Provider Configurationsを使ってAWSクロスアカウントの環境構築を楽ちんに

广告
将在 10 秒后关闭
bannerAds