既存AWS構成をTerraformでIaC化（VPC,API Gateway）

## はじめに
現在の以下の構成をTerraformでIaC化していきたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/9aeb7811-c7ff-e0dd-7c96-1f0a5e7d7350.png)


内容としては
・VPC関連(VPC,Subnet,InternetGateway,RouteTable,SecurityGroup) 
・API Gateway
の部分をまずはやっていきたいと思います。その他コンポーネントについては順次記事を追加投稿していきたいと思います。（長くなってしまうので）

※Subnetの種類としてPublic SubnetとPrivate Subnetを用意していますが、前提構成ではPublic Subnetしか利用していないので基本はPublic Subnetの設定を実施していきます。（Private Subnetは一部の検証を実施する際に利用した。詳細は以下記事）

https://qiita.com/kennyQiita/items/0bfa3a6d0d81595454d8

## IaC化
### 基本の流れ
まずは全てのコンポーネントに対して適用される基本の流れを確認。
1. Terraformの[公式Doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)でリソースタイプや個別パラメータなどを確認する。（各リソースページの一番下には`Import`セクションが記載されているので、`terafform import`する際のコマンドも確認できる。）
1. リソースを管理する`.tf`ファイル（例：aws_vpc.tf）に必要なリソースセクションを用意する。
1. `terraform import`を実行。
1. `terraform state show`で既存構成を確認。
1. 既存構成のうち、必要なパラメータや設定値を`.tf`ファイルに記載する。
1. `terraform fmt`や`terraform validate`で内容を整備・確認。
1. `terraform plan`で既存構成と差分がないかを確認（追加変更する場合は差分があってOK）。
1. もし意図しない差分があったら、再度4~7を実施して内容確認。意図した差分があるなら`terraform apply`で変更を環境に反映する。

## VPC関連サービス
### VPC
最初のリソースなので基本の流れの内容を順を追って残しておく。
まずは[公式Doc](!https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc)からリソースタイプなどを確認。VPCは以下のようなブロックとなる。
```terraform:aws_pubsub_vpc.tf
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}
```
今回は既存環境をIaC化するので、リソースタイプはそのままにしてVPC名は既存のVPC名に書き換え、cidr_blockなども記載なしにした.tfファイルを以下のように用意する。
```terraform:aws_pubsub_vpc.tf
resource "aws_vpc" "pubsub-vpc" {
 
}
```
公式Docには`terraform import`する際の例も以下のように記載されているので確認。
>Using terraform import, import VPCs using the VPC id. For example:
`% terraform import aws_vpc.test_vpc vpc-a01106c2`

上記のコマンド例に沿って自分のIaC化実施環境でもコマンドを打つ
```
terraform import aws_vpc.pubsub-vpc [既存のVPCIDをコンソールから確認して入力]
```
Importされた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/9261d833-e2b6-a90c-69b8-61f2c17fa968.png)

次に`terraform state show`で既存構成を確認する。
```
terraform state show aws_vpc.pubsub-vpc
```
出力された構成情報のうち、デフォルト値を除いた必須項目などをtfファイルに記載していく。
```terraform:aws_pubsub_vpc.tf
#----------------------------------------
# Create VPC
#----------------------------------------
resource "aws_vpc" "pubsub-vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = {
    Name = "pubsub-vpc"
  }
}
```
フォーマットの整備や、（念のため）構文エラーなどもないかを確認。
```
terraform fmt 
```
```
terraform validate
```
上記OKであれば、`terraform plan`で記載内容と既存構成内容の差分を確認。
```
terraform plan
```
差分がなければ以下のように表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/19249bdd-617f-74ca-a8b6-792e02686bd6.png)

ここまでで既存構成のIaC化は完了。既存構成に対して構成差分がある場合は`terraform apply`で内容を反映する。

### Subnet
以降のリソースはIaC化された後の状態のtfファイルのみ残しておく。VPC関連のサービスは同じファイルに記載して管理する。
```terraform:aws_pubsub_vpc.tf
#----------------------------------------
# Create Public Subnet
#----------------------------------------
resource "aws_subnet" "pubsub-subnet-public1-ap-northeast-1a" {
  vpc_id            = aws_vpc.pubsub-vpc.id
  availability_zone = "ap-northeast-1a"
  cidr_block        = "10.0.0.0/20"
  tags = {
    Name = "pubsub-subnet-public1-ap-northeast-1a"
  }
}

resource "aws_subnet" "pubsub-subnet-public2-ap-northeast-1c" {
  vpc_id            = aws_vpc.pubsub-vpc.id
  availability_zone = "ap-northeast-1c"
  cidr_block        = "10.0.16.0/20"
  tags = {
    Name = "pubsub-subnet-public2-ap-northeast-1c"
  }
}

#----------------------------------------
# Create Private Subnet
#----------------------------------------
resource "aws_subnet" "pubsub-subnet-private1-ap-northeast-1a" {
  vpc_id            = aws_vpc.pubsub-vpc.id
  availability_zone = "ap-northeast-1a"
  cidr_block        = "10.0.128.0/20"
  tags = {
    Name = "pubsub-subnet-private1-ap-northeast-1a"
  }
}

resource "aws_subnet" "pubsub-subnet-private2-ap-northeast-1c" {
  vpc_id            = aws_vpc.pubsub-vpc.id
  availability_zone = "ap-northeast-1c"
  cidr_block        = "10.0.144.0/20"
  tags = {
    Name = "pubsub-subnet-private2-ap-northeast-1c"
  }
}
```
### Internet Gateway
```terraform:aws_pubsub_vpc.tf
#----------------------------------------
# Create Internet Gateway
#----------------------------------------
resource "aws_internet_gateway" "pubsub-igw" {
  vpc_id = aws_vpc.pubsub-vpc.id
  tags = {
    Name = "pubsub-igw"
  }
}
```
### Route Table
```terraform:aws_pubsub_vpc.tf
#----------------------------------------
# Create RouteTable
#----------------------------------------
# RouteTable for Public Subnet
resource "aws_route_table" "pubsub-rtb-public" {
  vpc_id = aws_vpc.pubsub-vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.pubsub-igw.id
  }
  tags = {
    Name = "pubsub-rtb-public"
  }
}

# RouteTable for Private Subnet
resource "aws_route_table" "pubsub-rtb-private" {
  vpc_id = aws_vpc.pubsub-vpc.id
  tags = {
    Name = "pubsub-rtb-private"
  }
}

#----------------------------------------
# Attach RouteTable to Subnet
#----------------------------------------
resource "aws_route_table_association" "public_rt_assoc" {
  subnet_id      = aws_subnet.pubsub-subnet-public1-ap-northeast-1a.id
  route_table_id = aws_route_table.pubsub-rtb-public.id
}

resource "aws_route_table_association" "public_rt_assoc2" {
  subnet_id      = aws_subnet.pubsub-subnet-public2-ap-northeast-1c.id
  route_table_id = aws_route_table.pubsub-rtb-public.id
}

resource "aws_route_table_association" "private1_rt_assoc" {
  subnet_id      = aws_subnet.pubsub-subnet-private1-ap-northeast-1a.id
  route_table_id = aws_route_table.pubsub-rtb-private.id
}

resource "aws_route_table_association" "private2_rt_assoc" {
  subnet_id      = aws_subnet.pubsub-subnet-private2-ap-northeast-1c.id
  route_table_id = aws_route_table.pubsub-rtb-private.id
}
```
### Security Group
```terraform:aws_pubsub_vpc.tf
#----------------------------------------
# Create Security Group
#----------------------------------------
resource "aws_security_group" "pubsub-public-sg" {
  name   = "pubsub-public-sg"
  vpc_id = aws_vpc.pubsub-vpc.id
  description = "public security group"

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    self        = true
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "pubsub-public-sg"
  }
}
```
## API Gateway
次にAPI Gatewayの構成部分をTerraformでIaC化していく。
```terraform:aws_pubsub_apigw.tf
# #----------------------------------------
# # Create API Gateway
# #----------------------------------------
resource "aws_api_gateway_rest_api" "pubsubApi" {
  name        = "pubsubApi"
  description = "Api to call publisher lambda"
  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

resource "aws_api_gateway_resource" "pubsubApi" {
  rest_api_id = aws_api_gateway_rest_api.pubsubApi.id
  parent_id   = aws_api_gateway_rest_api.pubsubApi.root_resource_id
  path_part   = "publishtopic"
}

resource "aws_api_gateway_authorizer" "pubsubApi" {
  name        = "PubSubLambdaAuthorizer"
  rest_api_id = aws_api_gateway_rest_api.pubsubApi.id
  authorizer_uri                   = data.aws_lambda_function.AuthForPubSub.invoke_arn
  authorizer_result_ttl_in_seconds = 0
  type                             = "REQUEST"
}

resource "aws_api_gateway_method" "pubsubApi" {
  http_method   = "POST"
  authorization = "CUSTOM"
  authorizer_id = aws_api_gateway_authorizer.pubsubApi.id
  resource_id   = aws_api_gateway_resource.pubsubApi.id
  rest_api_id   = aws_api_gateway_rest_api.pubsubApi.id
}

resource "aws_api_gateway_deployment" "pubsubApi" {
  rest_api_id = aws_api_gateway_rest_api.pubsubApi.id
}

resource "aws_api_gateway_stage" "pubsubApi" {
  rest_api_id   = aws_api_gateway_rest_api.pubsubApi.id
  stage_name    = "dev"
  deployment_id = aws_api_gateway_deployment.pubsubApi.id
  description   = "dev stage for pubsub api"
}

resource "aws_api_gateway_integration" "pubsubApi" {
  rest_api_id             = aws_api_gateway_rest_api.pubsubApi.id
  resource_id             = aws_api_gateway_resource.pubsubApi.id
  http_method             = "POST"
  type                    = "AWS_PROXY"
  uri                     = data.aws_lambda_function.deliverTopicToSNS_Test01.invoke_arn
  content_handling        = "CONVERT_TO_TEXT"
  integration_http_method = "POST"
}
```
上記で参照している`data.aws_lambda_function.AuthForPubSub.invoke_arn`や`data.aws_lambda_function.deliverTopicToSNS_Test01.invoke_arn`の部分については別ファイル`aws_pubsub_lambda.tf`で記述している。実際のLambdaの中身自体も同様にTerraformで管理できるようにしたいが、その内容についてはLambdaのIaC化セクションで行うこととし、現状では既存のコード（コンソール上にある）を参照できるところまでにしておく。
```terraform:aws_pubsub_lambda.tf
data "aws_lambda_function" "AuthForPubSub" {
  function_name = "AuthForPubSub"
}

data "aws_lambda_function" "deliverTopicToSNS_Test01" {
  function_name = "deliverTopicToSNS_Test01"
}
```
一旦ここまででVPC回りとAPI Gateway回りのIaCはできている（はず）。次回は既存のLambdaの部分をTerraformでスムーズに管理できるところを実施していきたい。

## おわりに
一旦ここまで実施するのにも割と時間がかかりました。既存の構成をimportして差分が無いようにIaC化するのには労力かかる面もあるので、やはり最初からIaCを念頭に構築できればベストだと思います。途中からIaC化する場合は、IaC化することによるメリットや費用対効果なども考慮しながら検討する必要がありそうです。次回以降はLambda部分やその他の既存構成部分をTerraformでIaC化していきたいと思います。

## 参考サイト
今後のTerraform活動でも何回か参照しそうなので載せておく。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_function

https://tech.toreta.in/entry/2020/12/06/000000

https://github.com/manaty226/lambda-authorizer-spicedb/tree/main