HCLコードの自動生成を利用してRoute53管理のドメインをterraform管理にimportする
terraformにRoute53管理のドメインをimportする
terraformはAWSマネジメントコンソール等で作成した既存リソースも、importを使うことでterraform管理に取り込むことができます。terraformでIaCするのが大好きな私は、プライベートで持っている大昔に取ったドメインもterraformにimportしてみます。
大体の流れ
- import内容を記述した
.tfファイルを用意する terraform plan -generate-config-outでHCLコード(.tfファイル)を生成する- 生成したコードを微調整する
 terraform applyでimport完了
generate-config-outオプション
generate-config-outオプションを使うことで取り込むAWSリソースの.tfファイルを生成することができます。
これを使わなくてもAWSリソースのimportはできますが、importはあくまでterraformの状態管理ファイルである.tfstateに取り込むのみなので、.tfファイルは作成されません。.tfファイルは別途用意する必要があります。generate-config-outはこれを生成してくれるということですね。
公式ドキュメントを見てみます。

terraformバージョン1.5から導入された機能ですが、いまだに試験的な機能の扱いのようです🤔
やってみる
terraform管理のリポジトリにdomainディレクトリを掘ってその中にファイルを配置することにします。
作ったファイルは3つだけ。
% tree ./domain
./domain
├── import.tf
├── locals.tf
└── terraform.tf
import.tf
import {
  to = aws_route53domains_registered_domain.my_domain_com
  id = local.my_domain
}
import {
  to = aws_route53_zone.my_domain_com
  id = local.zone_id
}
import {
  to = aws_route53_record.my_domain_com_ns
  id = "${local.zone_id}_${local.my_domain}_NS"
}
import {
  to = aws_route53_record.my_domain_com_soa
  id = "${local.zone_id}_${local.my_domain}_SOA"
}
import {
  to = aws_route53_record.my_domain_com_a
  id = "${local.zone_id}_www.${local.my_domain}_A"
}
AWSリソースをterraformのtfstateに取り入れるimportブロックです。
aws_route53_recordの書き方がちょっとめんどくさくて、idの形式が"
これに限らず、importブロックの書き方はterraformの各リソースのドキュメントに書いてあるので見ましょう。
ゾーンIDはAWSマネージメントコンソールから確認しましょう。
locals.tf
locals {
    my_domain = "私のドメイン"
    zone_id   = "コンソール上で確認したゾーンID"
}
ドメインとゾーンIDを別ファイルのlocalsに。
terraform.tf
terraform {
  required_version = "~> 1.13.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
  backend "s3" {
    bucket = <バケット名>
    region = "ap-northeast-1"
    key = "tfstate/dev/domain.tfstate"
  }
}
provider "aws" {
  region = "ap-northeast-1"
}
バージョンやtfstateの置き場所、providerの定義です。このへんのファイル分割はプロジェクトにより色々ありますが、私は理由がなければterraform.tfにまとめちゃうことが多いです。
ではterraform plan -generate-config-outを使って、HCLコードを作成してみます。
terraform init
terraform plan -generate-config-out="route53.tf"
-generate-config-outの引数にはHCLコードを出力するファイル名を指定します。既存のファイルパスはNGです。
以下のようにroute53.tfが出力されました。
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "my_domain_com"
resource "aws_route53domains_registered_domain" "my_domain_com" {
  admin_privacy      = true
  auto_renew         = true
  billing_privacy    = true
  domain_name        = "example-company.com"
  registrant_privacy = true
  tags               = {}
  tags_all           = {}
  tech_privacy       = true
  transfer_lock      = true
  admin_contact {
    address_line_1    = "1-1-1 Shibuya"
    address_line_2    = null
    city              = "Shibuya-ku"
    contact_type      = "PERSON"
    country_code      = "JP"
    email             = "admin@example-company.com"
    extra_params      = {}
    fax               = null
    first_name        = "Taro"
    last_name         = "Yamada"
    organization_name = null
    phone_number      = "+81.312345678"
    state             = null
    zip_code          = "150-0002"
  }
  billing_contact {
    address_line_1    = "1-1-1 Shibuya"
    address_line_2    = null
    city              = "Shibuya-ku"
    contact_type      = "PERSON"
    country_code      = "JP"
    email             = "billing@example-company.com"
    extra_params      = {}
    fax               = null
    first_name        = "Taro"
    last_name         = "Yamada"
    organization_name = null
    phone_number      = "+81.312345678"
    state             = null
    zip_code          = "150-0002"
  }
  name_server {
    glue_ips = []
    name     = "ns-170.awsdns-21.com"
  }
  name_server {
    glue_ips = []
    name     = "ns-1554.awsdns-02.co.uk"
  }
  name_server {
    glue_ips = []
    name     = "ns-575.awsdns-07.net"
  }
  name_server {
    glue_ips = []
    name     = "ns-1364.awsdns-42.org"
  }
  registrant_contact {
    address_line_1    = "1-1-1 Shibuya"
    address_line_2    = null
    city              = "Shibuya-ku"
    contact_type      = "PERSON"
    country_code      = "JP"
    email             = "registrant@example-company.com"
    extra_params      = {}
    fax               = null
    first_name        = "Taro"
    last_name         = "Yamada"
    organization_name = null
    phone_number      = "+81.312345678"
    state             = null
    zip_code          = "150-0002"
  }
  tech_contact {
    address_line_1    = "1-1-1 Shibuya"
    address_line_2    = null
    city              = "Shibuya-ku"
    contact_type      = "PERSON"
    country_code      = "JP"
    email             = "tech@example-company.com"
    extra_params      = {}
    fax               = null
    first_name        = "Taro"
    last_name         = "Yamada"
    organization_name = null
    phone_number      = "+81.312345678"
    state             = null
    zip_code          = "150-0002"
  }
}
# __generated__ by Terraform from "Z01921833I123456789"
resource "aws_route53_zone" "my_domain_com" {
  comment           = null
  delegation_set_id = null
  force_destroy     = null
  name              = "example-company.com"
  tags              = {}
  tags_all          = {}
}
# __generated__ by Terraform
resource "aws_route53_record" "my_domain_com_soa" {
  allow_overwrite                  = null
  health_check_id                  = null
  multivalue_answer_routing_policy = false
  name                             = "example-company.com"
  records                          = ["ns-170.awsdns-21.com. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400"]
  set_identifier                   = null
  ttl                              = 900
  type                             = "SOA"
  zone_id                          = "Z01921833I123456789"
}
# __generated__ by Terraform
resource "aws_route53_record" "my_domain_com_ns" {
  allow_overwrite                  = null
  health_check_id                  = null
  multivalue_answer_routing_policy = false
  name                             = "example-company.com"
  records                          = ["ns-1364.awsdns-42.org.", "ns-1554.awsdns-02.co.uk.", "ns-170.awsdns-21.com.", "ns-575.awsdns-07.net."]
  set_identifier                   = null
  ttl                              = 172800
  type                             = "NS"
  zone_id                          = "Z01921833I123456789"
}
# __generated__ by Terraform
resource "aws_route53_record" "my_domain_com_a" {
  allow_overwrite                  = null
  health_check_id                  = null
  multivalue_answer_routing_policy = false
  name                             = "www.example-company.com"
  records                          = ["111.111.222.222"]
  set_identifier                   = null
  ttl                              = 300
  type                             = "A"
  zone_id                          = "Z01921833I123456789"
}
確かに出力されましたが、aws_route53domains_registered_domainを見るとわかる通り、個人情報モリモリです。このままでは例えプライベートリポジトリでもコミットしたくないです(上記コードの個人情報は記事用に適当なものに変えてます)。
個人情報含め、コードに書いておく必要のないものは消します。
特に登録ドメインのaws_route53domains_registered_domainはほとんどの引数がオプショナルです。
削除してplanを取ってみます。-generate-config-outはもう必要ありません。
terraform plan
...
│ Error: Missing required argument
│
│   with aws_route53_record.my_domain_com_soa,
│   on domain.tf line 3:
│   (source code not available)
│
│ "multivalue_answer_routing_policy": all of `multivalue_answer_routing_policy,set_identifier` must be specified
╵
エラーが出ました。
ルーティングポリシーの問題のようです。シンプルルーティングポリシーならmultivalue_answer_routing_policy, set_identifierは必須でないので削除します。
修正してplanが通り、他にも多少整理して、こんな感じになりました。
resource "aws_route53domains_registered_domain" "my_domain_com" {
  admin_privacy      = true
  auto_renew         = true
  billing_privacy    = true
  domain_name        = "example-company.com"
  registrant_privacy = true
  tech_privacy       = true
  transfer_lock      = true
  name_server {
    glue_ips = []
    name     = "ns-170.awsdns-21.com"
  }
  name_server {
    glue_ips = []
    name     = "ns-1554.awsdns-02.co.uk"
  }
  name_server {
    glue_ips = []
    name     = "ns-575.awsdns-07.net"
  }
  name_server {
    glue_ips = []
    name     = "ns-1364.awsdns-42.org"
  }
}
resource "aws_route53_zone" "my_domain_com" {
  delegation_set_id = null
  force_destroy     = null
  name              = "example-company.com"
}
resource "aws_route53_record" "my_domain_com_soa" {
  allow_overwrite                  = null
  health_check_id                  = null
  name                             = "example-company.com"
  records                          = ["ns-170.awsdns-21.com. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400"]
  ttl                              = 900
  type                             = "SOA"
  zone_id                          = "Z01921833I123456789"
}
resource "aws_route53_record" "my_domain_com_ns" {
  allow_overwrite                  = null
  health_check_id                  = null
  name                             = "example-company.com"
  records                          = ["ns-1364.awsdns-42.org.", "ns-1554.awsdns-02.co.uk.", "ns-170.awsdns-21.com.", "ns-575.awsdns-07.net."]
  ttl                              = 172800
  type                             = "NS"
  zone_id                          = "Z01921833I123456789"
}
resource "aws_route53_record" "my_domain_com_a" {
  allow_overwrite                  = null
  health_check_id                  = null
  name                             = "www.example-company.com"
  records                          = ["111.111.222.222"]
  ttl                              = 300
  type                             = "A"
  zone_id                          = "Z01921833I123456789"
}
他にも必要ない項目があって冗長だし、リテラルじゃなくて参照に変えたほうがいいとか色々あるけど(zone_idとか)、まぁ見通せるレベルなんで一旦OKです。
applyしてみます。
terraform apply
...
Apply complete! Resources: 5 imported, 0 added, 1 changed, 0 destroyed.
成功しました。1 changedが出ているのはmanaged by terraformのコメントがついただけみたいなので問題ないです。
うーんでもちゃんとterraformに取り込めたかな?動作確認としてホストゾーンにAレコードを新しく作ってみます。以下コードを追加します。
# Aに割り当てるElasticIPを取る
resource "aws_eip" "sample_eip" {
  domain = "vpc"
}
resource "aws_route53_record" "sample_eip_record_a" {
  name                             = "sample.my-domain.com"
  records                          = ["${aws_eip.sample_eip.public_ip}"]
  ttl                              = 300
  type                             = "A"
  # 取り込んだゾーンを参照で指定
  zone_id                          = aws_route53_zone.my_domain_com.id
}
applyします。
terraform apply
...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
成功したようです。AWSコンソールで確認してみましょう。

ちゃんとできてます!
まとめ
冗長だったり、DRYじゃなかったりで多少の手直しは必要ですが、それでもterraform plan -generate-config-outによるHCLコードの自動生成は楽ですね。手で書くと変数を必須か否かドキュメント見ながらプランプランすることになるので。






