Terraform + クロスアカウント + VPCピアリング

この記事は[ぷりぷりあぷりけーしょんず Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ppap)の3日目の記事です。

# 概要
AWSのproductionアカウントとバックオフィスの人間が利用するAWSアカウントを分けて運用することは多いと思います。

両者のアカウントに作成したVPCをピアリングして疎通させるTerraformのメモです。

# 要件
新規に作成した`アカウントA`から既存の`アカウントB`へと繋ぎこみます。

# 実装
## アカウント情報
下記のようにすることで、`provider = aws.accountB`を指定することで既存のアカウントBに関するリソースの定義を行えます。

```terraform:aws.tf
provider "aws" {}

provider "aws" {
  alias = "accountB"
}

data "aws_caller_identity" "accountB" {
  provider = aws.accountB
}
```

## VPCリソース
既存アカウントBのVPCリソースはdataリソースで引っ張るようにします。**provider = aws.accountB**を忘れずに。

アカウントAとアカウントBの`cidr_block`が被っていない必要があります。

```terraform:vpc.tf
resource "aws_vpc" "accountA" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "accountA-vpc"
  }
}

data "aws_vpc" "accountB" {
  provider = aws.accountB

  filter {
    name   = "tag:Name"
    values = ["accountB-vpc"]
  }
}
```

## Peering
```terraform:peer.tf
resource "aws_vpc_peering_connection" "peer" {
  peer_vpc_id   = data.aws_vpc.accountB.id
  vpc_id        = aws_vpc.accountA.id
  peer_owner_id = data.aws_caller_identity.accountB.account_id
  peer_region   = "ap-northeast-1"
  auto_accept   = false

  tags = {
    Name = "accountA"
    Side = "Requester"
  }
}

resource "aws_vpc_peering_connection_accepter" "peer" {
  vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
  provider                  = aws.accountB
  auto_accept               = true

  tags = {
    Name = "accountB"
    Side = "Accepter"
  }
}

resource "aws_vpc_peering_connection_options" "requester" {
  vpc_peering_connection_id = aws_vpc_peering_connection_accepter.peer.id

  requester {
    allow_remote_vpc_dns_resolution = true
  }
}

resource "aws_vpc_peering_connection_options" "accepter" {
  vpc_peering_connection_id = aws_vpc_peering_connection_accepter.peer.id
  provider                  = aws.accountB

  accepter {
    allow_remote_vpc_dns_resolution = true
  }
}
```

## Route53の連携
AWSproviderのバージョンが新しいものでないと動作しません。筆者の環境は`3.17`です。

アカウントAから既存アカウントBのプライベートレコードを引くことができるようにします。

検証してないですが、RDSのエンドポイントからAレコードを引けない気がしてならないので設定してます(未検証)。やるに越したことないので、DNS名を利用したい場合は設定します。

```terraform:private_r53.tf
data "aws_route53_zone" "accountB" {
  provider     = aws.accountB
  name         = "accountB.local"
  private_zone = true
}

resource "aws_route53_vpc_association_authorization" "assoc" {
  provider = aws.accountB
  vpc_id   = aws_vpc.accountA.id
  zone_id  = data.aws_route53_zone.accountB.id
}

resource "aws_route53_zone_association" "assoc" {
  vpc_id  = aws_route53_vpc_association_authorization.accountB.vpc_id
  zone_id = aws_route53_vpc_association_authorization.accountB.zone_id
}
```

## ルートテーブルの設定
実際にはVPCのメインルートテーブルではなくサブネット単位でルートテーブルを作成してピアリング先と通信した方が良いです。

```terraform:rtb.tf
resource "aws_route_table" "accountA" {
  vpc_id = aws_vpc.accountA.id

  tags = {
    Name = "accountA-rtb"
  }
}

resource "aws_route" "accontA" {
  route_table_id            = aws_route_table.accountA.id
  destination_cidr_block    = data.aws_vpc.accountB.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
}

resource "aws_main_route_table_association" "accountA" {
  route_table_id = aws_route_table.accountA.id
  vpc_id         = aws_vpc.accountA.id
}

data "aws_route_table" "accountB" {
  provider = aws.accountB

  filter {
    name   = "tag:Name"
    values = ["accountB-rtb"]
  }
}

resource "aws_route" "accountB" {
  provider                  = aws.accountB
  route_table_id            = data.aws_route_table.accountB.id
  destination_cidr_block    = aws_vpc.accountA.cidr_block
  vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
}
```

# デプロイ
ここまでで作成したファイルを`module`として利用すると運用が簡単になります。Network周りのリソースは変更や削除をする機会も少ないと思うので。

例えば下記のような構成も考えられます。めちゃくちゃ適当なので、ディレクトリ構成のプラクティスは別途調べると良いでしょう(tfstateのバックエンドの設定とか諸々)。

```
├── network
│   ├── aws.tf
│   ├── vpc.tf
│   ├── peer.tf
│   ├── private_r53.tf
│   └── rtb.tf
└── main.tf
```

```terraform:main.tf
provider "aws" {
  version = "~> 3.17"
  region  = "ap-northeast-1"
  profile = アカウントAのプロファイル
}

provider "aws" {
  version = "~> 3.17"
  alias   = "accountB"
  region  = "ap-northeast-1"
  profile = アカウントBのプロファイル
}

module "network" {
  source = "./network"
  providers = {
    aws          = aws
    aws.accountB = aws.accountB
  }
}
```

# まとめ
AWSの構成管理はTerraformやCDKなどで行うことが多いですが、やはりマネジメントコンソールからリソースを管理することもまだまだ多いと思います。

クロスアカウントなリソースの管理は煩雑なイメージもありますし、Terraformで管理するハードルも高いようなイメージがあります。

しかし実際に実装してみた所感としてはそこまで煩雑で手間がかかる印象もなかったので、今後もガンガン利用していきたいためメモとしてまとめてみました。