作りながら覚えるTerraform入門(5) - Route53 + ACM編

作りながら覚えるTerraform入門シリーズの第5回です。
今回は独自ドメインでHTTPS接続するための準備として、Route53とACMを作成していきます。
独自ドメインはお名前.comで取得しているものを使います。

##### 作りながら覚えるTerraform入門シリーズ

1. [インストールと初期設定](https://qiita.com/m-oka-system/items/6103bbb9f103db1fea0e)
1. [基本編](https://qiita.com/m-oka-system/items/983653f611fe305820c8)
1. [VPC編](https://qiita.com/m-oka-system/items/bba8d32d57142a870ce5)
1. [EC2編](https://qiita.com/m-oka-system/items/8280f748f88ac2e28ad7)
1. **Route53 + ACM編** => 今回はコチラ
1. [ELB編](https://qiita.com/m-oka-system/items/8882431042a1d01d0b7c)
1. [RDS編](https://qiita.com/m-oka-system/items/a45867495f95461b0a2c)

今回の学習ポイントは以下です。が、具体的に解説はできていません^^;

- lifecycle
- メタ引数
- for文

# ホストゾーンの作成

まず、Route53のホストゾーンを作成します。
`route53.tf`を作成し、以下のコードを貼り付けます。

```terraform:route53.tf
################################
# Route 53
################################
resource "aws_route53_zone" "public" {
  name = var.my_domain
}

output "name_servers" {
  description = "A list of name servers in associated (or default) delegation set."
  value       = aws_route53_zone.public.name_servers
}
```

`output`ブロックではRoute53のネームサーバを出力させるようにしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/7483fda9-5430-dfca-0b86-81e2b6fadaf7.png)



`variables.tf`に独自ドメインの変数を追加します。

```terraform:variables.tf
# Route53
variable "my_domain" {}
```

ドメイン名は`terraform.tfvars`から読み込ませるようにしておきます（以下のドメインは適当です）

```terraform:terraform.tfvars
prefix    = "cloud02"
my_domain = "example.com"
```

`terraform apply`を実行してホストゾーンを作成後、Outputsで出力されるネームサーバをお名前.com側に登録します。（ネームサーバはコンソール画面から確認でもOK）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/a3cc8fcf-aa72-f1bd-60a4-bd789350ea34.png)

ネームサーバの変更が反映されるには少し時間がかかりますので気長に待ちましょう。`dig`コマンドなどでNSレコードの反映が確認できたら次の手順へ進みます。

```bash
# ネームサーバ変更前
dig example.com ns +short
dns1.onamae.com.
dns2.onamae.com.

# ネームサーバ変更後
dig example.com ns +short
ns-1596.awsdns-07.co.uk.
ns-450.awsdns-56.com.
ns-656.awsdns-18.net.
ns-1045.awsdns-02.org.
```

# ACMでパブリック証明書を作成

続いて、ACMでパブリック証明書（SSL証明書)を作成します。
`route53.tf`に以下のコードを追加します。

```terraform:route53.tf
################################
# ACM
################################
resource "aws_acm_certificate" "public" {
  domain_name               = aws_route53_zone.public.name
  subject_alternative_names = ["*.${aws_route53_zone.public.name}"]
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}
```

`domain_name`には独自ドメインの名前を指定し、`subject_alternative_names`には頭に`*.`を付け加えてサブドメインにも対応したワイルドカード証明書を作成するようにしています。こうすることで、ネイキッドドメイン、サブドメインどちらとも保護することができます。たとえば、ドメイン名が`example.com`の場合、以下のようなドメインが保護されます。

- example.com (ネイキッドドメイン)
- www.example.com
- corp.example.com

`validation_method`ではDNS検証を指定しています。`lifecycle`は後述します。
`terraform apply`を実行して、パブリック証明書が作成されることを確認しましょう。

# メタ引数

ACMの [lifecycle](https://www.terraform.io/docs/language/meta-arguments/lifecycle.html) ブロックでは証明書再作成時の挙動を指定しています。デフォルトではリソースの再作成が必要な場合、「既存のリソースを削除してから新しいリソースを作成する」という挙動になります。lifecycleブロックで`create_before_destroy = true`を指定することで「新しいリソースを作成してから、古いリソースを削除する」という動作になりますので、SSL証明書再作成時の影響を最小限にすることができます。

lifecycleのようにTerraformの挙動をコントロールするオプションを「**メタ引数（Meta-Argument）**」と呼び、ACMのみならずすべてのリソースで指定することができます。メタ引数は他にもあり、まとめると次のようになります。

| 種類 | 説明 |
| ---- | ---- |
| depends_on 　| 依存関係を定義する |
| count      　| 指定回数ループする |
| for_each  　 | マップや文字列を元にループする　　　 |
| provider  　 | プロバイダーを上書きする |
| lifecycle 　 | ライフサイクルの挙動を定義する |

`depends_on`はリソース間の依存関係を定義します。基本的にはTerraformによって自動的に依存関係を考慮しながら作成・更新・削除されるのですが、一部リソースなど、依存関係を明示的に指定してあげないとうまく動作しない場合に利用します。

`count`と`for_each`はループ処理で利用します。前回の[EC2編](https://qiita.com/m-oka-system/items/8280f748f88ac2e28ad7)のENIやEC2の作成のように、同じようなリソースを複数作成したい場合に便利です。

`provider.tf`にはプロバイダーにAWSを利用することを宣言し、デフォルトリージョンに`ap-northeast-1`を指定していますが、Resourceブロックに`provider`を指定することで上書きすることができます。例えば、別のリージョンに作成したい場合などでしょうか。追加のプロバイダーにはエイリアスを定義しておいて、Resourceブロックではエイリアスの名前を記述します。詳細は [The Resource provider Meta-Argument](https://www.terraform.io/docs/language/meta-arguments/resource-provider.html) を参照。

# ACMのドメイン検証

ドメインの所有者を検証するために「DNS検証」を指定しましたので、Route53に検証用のCNAMEレコードを登録します。今はまだ証明書を作成しただけなので「検証保留中」となっている状態です。コンソール画面であれば「Route53でのレコードの作成」のボタンを押すだけなので非常に簡単なのですがこれをTerraformで行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/0a738242-5899-6fff-1681-44ea3491df22.png)

`route53.tf`に以下のコードを追加します。

```terraform:route53.tf
resource "aws_route53_record" "public_dns_verify" {
  for_each = {
    for dvo in aws_acm_certificate.public.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }
  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = aws_route53_zone.public.id
}

resource "aws_acm_certificate_validation" "public" {
  certificate_arn         = aws_acm_certificate.public.arn
  validation_record_fqdns = [for record in aws_route53_record.public_dns_verify : record.fqdn]
}
```

まず、`aws_route53_record`でRoute53にCNAMEレコードを登録します。
コンソール画面で見たCNAMEレコードの名前と値のペアを取得したいのですが、これは先に作成したACMのリソース`aws_acm_certificate.public`から`domain_validation_options`を参照することで取得できます。`terraform console`で確認するとわかりやすいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/7302913a-c366-3ab6-45cb-483b3473d14a.png)

`for_each` と `for` を使ってるので少しわかりづらいかもしれませんが、以下の記事を参考にしました。
[Terraform で AWS Certificate Manager 無料証明書を発行する（AWS Provider 3.0.0 以降の場合）](https://dev.classmethod.jp/articles/terraform-aws-certificate-validation/) 

`aws_acm_certificate_validation`は検証が成功するまで待機するためのものです。新たにリソースが作成されることはありません。

CNAMEレコードの登録、DNS検証検証の待機とも [for文](https://www.terraform.io/docs/language/expressions/for.html) が使われていますが、これはリストやマップを変換したり、フィルタする時に使います。こちらも`terraform console`で確認してみるとやっていることは理解できますが、書けと言われるとまだ書けないですね。。`terraform console`で動きを確認しながら慣れていきたいと思います。

`terraform apply`すると10分くらいでDNS検証が成功しました。

```bash
:
aws_acm_certificate_validation.public: Still creating... [9m50s elapsed]
aws_acm_certificate_validation.public: Still creating... [10m0s elapsed]
aws_acm_certificate_validation.public: Creation complete after 10m8s [id=2021-06-09 00:29:59 +0000 UTC]
```
コンソール画面で確認しても「発行済み」に変わっていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/ffc33a34-ade5-7701-f537-ba67439a1e32.png)

今回は以上です。次回はELB編ということで、ロードバランサを作成して独自ドメインでHTTPS接続できるようにしてみたいと思います！

# 参考リンク

- [The lifecycle Meta-Argument](https://www.terraform.io/docs/language/meta-arguments/lifecycle.html)
- [The Resource provider Meta-Argument](https://www.terraform.io/docs/language/meta-arguments/resource-provider.html)
- [Terraform で AWS Certificate Manager 無料証明書を発行する（AWS Provider 3.0.0 以降の場合）](https://dev.classmethod.jp/articles/terraform-aws-certificate-validation/) 
- [for Expressions](https://www.terraform.io/docs/language/expressions/for.html)