Terraform RegistryのAWSモジュールを使いつつ既存VPCをインポートする

AWSでWebアプリを動かす場合、stagingやproductionといった環境ごとにVPCを分けるケースが多いと思います。VPCを作ったことがある方なら分かると思いますが、Webアプリを動かすために作成しなければならないVPC関連のリソースは結構多いです。

- VPC
- インターネットゲートウェイ
- ルートテーブル
- サブネット
- ルートテーブルとインターネットゲートウェイの関連付け
- ルートテーブルとサブネットの関連付け
- セキュリティグループ（本記事では説明しません）

パブリックに公開するなら最低限これくらいは必要ですが、管理コンソールでポチポチ作るのも大変ですし、Terraformの [AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) を使っても各リソースごとに設定を記述する必要があり、面倒ですしコード量も大きくなります。

そこで、[Terraform Registry](https://registry.terraform.io/) に登録されている __AWS VPC Terraform module__ を使うと、少しの記述で必要なリソースをすべて作成することができます。

https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest

```tf
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "example"
  cidr = "10.0.0.0/16"

  azs            = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  public_subnets = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
}
```

たったこれだけで、以下のリソースがすべて作成されます。便利ですね :rocket: 

- VPC
- インターネットゲートウェイ
- ルートテーブル
- サブネット
- ルートテーブルとインターネットゲートウェイの関連付け
- ルートテーブルとサブネットの関連付け

と、ここまでの内容は、すでに他で紹介されていたりしますw

https://dev.classmethod.jp/articles/terraform_registry_module/#toc-8

ゼロベースならこれでいいのですが、最初からTerraformによるIaC (Infrastructure as Code) を実践していて、AWS上のあらゆるリソースがちゃんとTerraform管理下にある現場というのはそうそうありません（私の観測範囲内では）。管理コンソールで作ったリソースをTerraformにインポートして管理下におくことになりますが、AWSモジュールを使いつつインポートする方法がまとまっていなかったので、実際にやってみます :runner: :dash: 

# 各リソースをインポートする

作業ディレクトリに `main.tf` というファイルを以下の内容で作成します。

```tf:main.tf
provider "aws" {
  region = "ap-northeast-1"
}

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

  name = "example"
  cidr = "10.0.0.0/16"

  azs            = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  public_subnets = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
}
```

インポートする際にどのリソースに対してインポートするかを指定しますが、Terraform Registryに登録されているモジュールにどんなリソースが含まれるかは、モジュールのページの「Resources」タブに書いてあります。

https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest?tab=resources

また、インポートする際にモジュールに含まれるリソースを指定するには、以下のように記述します。

```sh
$ terraform import module.vpc.xxx RESOURCE_ID
```

`module.vpc` の `vpc` は、モジュールに付けた名前です。

ここから各リソースをインポートしていきますが、IDが明示されているリソースについては、IDが記載されている場所のスクリーンショットとコマンドのみで簡単に説明します。なお、各リソースのIDは私の環境でのものなので、皆さんのものとは異なります。

## VPC

![スクリーンショット 2021-03-29 22.51.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/d8b7c75e-9421-586a-32d8-f8b98c2351a0.png)

```
$ terraform import module.vpc.aws_vpc.this vpc-03f02e915443e60c7
```

## インターネットゲートウェイ

![スクリーンショット 2021-03-29 22.55.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/deed62fd-0a87-a8c8-5e95-f23fc3f76dcb.png)

```
$ terraform import module.vpc.aws_internet_gateway.this igw-0b5c3992d5c6ccc91
```

## ルートテーブル

![スクリーンショット 2021-03-29 22.58.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/50fe5c35-4a4d-d769-a19a-f4d1e825e287.png)

```
$ terraform import module.vpc.aws_route_table.public rtb-0ab109683bef62f88
```


## サブネット

サブネットはアベイラビリティーゾーンの数と同じく3つ作っていたので、3つすべてインポートする必要があります。

![スクリーンショット 2021-03-29 23.06.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/fdc40fd1-a42f-ace9-1c48-2f32fceb3a89.png)

```sh
$ terraform import module.vpc.aws_subnet.public[0] subnet-021d6a31421d45dcd # ap-northeast-1a
$ terraform import module.vpc.aws_subnet.public[1] subnet-0a973037681ff7d9b # ap-northeast-1c
$ terraform import module.vpc.aws_subnet.public[2] subnet-057fefa3d5d6fe102 # ap-northeast-1d
```

## ルートテーブルとインターネットゲートウェイの関連付け

![スクリーンショット 2021-03-29 23.16.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/c83d682b-2718-7828-e33e-1f7510072dee.png)
管理コンソールにIDが記載されていませんね :thinking: こういう場合に何を指定してインポートすればいいのかは、各リソースのページの「Import」セクションに記載されています。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route#import

ルートテーブルのIDと送信先のCIDRをアンダースコア `_` で繋いで指定すればいいようです。

```
$ terraform import module.vpc.aws_route.public_internet_gateway rtb-0ab109683bef62f88_0.0.0.0/0
```

## ルートテーブルとサブネットの関連付け

![スクリーンショット 2021-03-29 23.24.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/fea21186-98a3-5f6b-1706-41c8c7f622a3.png)
こちらも、管理コンソールにIDが記載されていません。リソースのページのImportセクションを見てみましょう。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association#import

サブネットのIDとルートテーブルのIDをスラッシュ `/` で繋いで指定すればいいようです。サブネットの数と同じく3つインポートする必要があります。

```
$ terraform import module.vpc.aws_route_table_association.public[0] subnet-021d6a31421d45dcd/rtb-0ab109683bef62f88
$ terraform import module.vpc.aws_route_table_association.public[1] subnet-0a973037681ff7d9b/rtb-0ab109683bef62f88
$ terraform import module.vpc.aws_route_table_association.public[2] subnet-057fefa3d5d6fe102/rtb-0ab109683bef62f88
```

# TerraformのStateと実際のリソースが合っていることを確認する

```
$ terraform plan
```

```:実行結果（抜粋）
No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
```

ということで、無事にVPCをTerraformの管理下におくことができました :tada: 

このVPCはあくまで例なので削除してしまいますが、Terraformの管理下なら削除もコマンド一発です。

```
$ terraform destroy
```

# 最後に

VPCに限らず、最初からIaCを実践してあらゆるリソースをコードで管理するというのは、現実的には難しいと思います。私自身、リソースを操作するときには先に管理コンソールで操作して、ある程度落ち着いてから帳尻を合わせることが多いです。

インフラは一度構築すると変更する機会がアプリケーションほど多くないので、時間が経つとメンバーが増えたときにどんな構成なのか分かりにくかったり、構築した本人ですら忘れていることはよくあります。長期運用するなら、早い段階でIaCの仕組みを整えておきたいですね。