2026年6月8日

2026年6月8日

Route 53 を Terraform で管理する方法|既存ゾーンの取り込み、レコードの書き方、消してはいけないゾーンの守り方

Route 53 を Terraform で管理するときは、「既存のホストゾーンとレコードを import で取り込んでから管理を始める」「ホストゾーンは prevent_destroy で削除から守る」「同じ名前と種類のレコードは1つのリソースにまとめる」の三つを押さえておくと、事故の多くを防げます。この記事では、プロバイダーと state の準備、既存環境の取り込み、WordPress サイトでよく使うレコードの書き方、plan の読み方と運用のルールまでを順に説明します。

Terraform と AWS プロバイダーは更新が早く、引数や推奨される設定が変わることがあります(2026年10月現在の情報です)。コードは例として、手元のバージョンのドキュメントと照らし合わせて使ってください。本番の DNS に適用する前に、検証用のドメインで一通り試し、現在のレコードを aws route53 list-resource-record-sets で書き出してバックアップしておきます。

管理する範囲を先に決める

Route 53 まわりには、Terraform で扱える対象が三つあります。

  • ホストゾーン(aws_route53_zone):ゾーンそのもの。作ると NS と SOA が自動で用意されます。
  • レコード(aws_route53_record):A・CNAME・MX・TXT などのレコードセット。
  • 登録ドメインのネームサーバー設定:ドメインを Route 53 Domains で登録している場合、AWS プロバイダーにはその設定を扱うリソースもあります。ただしドメインの登録情報は影響が大きいため、最初は管理の対象に含めず、画面や CLI で扱うほうが安全です。

最初の段階では「ホストゾーンとレコード」を対象にし、ゾーンが複数あるならドメインごとに state を分けると、plan の差分が読みやすく、誤った変更の影響も1つのドメインに収まります。

プロバイダーと state の準備

DNS の state には、どのレコードをどのリソースで管理しているかが記録されます。手元のファイルに置くと、作業者が複数いるときに食い違いが起きるため、S3 などのリモートに置き、同時実行を防ぐロックを有効にします。

terraform {
  required_version = ">= 1.10"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0" # 手元で使う主要バージョンに合わせる
    }
  }

  backend "s3" {
    bucket       = "your-tfstate-bucket"
    key          = "dns/example.com.tfstate"
    region       = "ap-northeast-1"
    encrypt      = true
    use_lockfile = true # S3 のロックファイルでロックする(新しい Terraform で使える方式)
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

Route 53 はリージョンを持たないグローバルなサービスですが、プロバイダーにはリージョンの指定が要ります。ロックの方式は Terraform のバージョンによって推奨が変わってきたため(以前は DynamoDB のテーブルを使う方式が一般的でした)、使うバージョンのドキュメントで確認してください。state のバケットはバージョニングを有効にし、読み書きできる人を絞っておきます。

既存のゾーンとレコードを取り込む

すでに Route 53 で運用しているゾーンを Terraform で「作り直す」と、新しいゾーンには別のネームサーバーが割り当てられ、レジストラの設定と食い違ってドメインが引けなくなります。既存のものは import で state に取り込みます。Terraform 1.5 以降は、import ブロックをコードに書いて plan で確認できます。

import {
  to = aws_route53_zone.main
  id = "Z0123456789EXAMPLE"                        # ホストゾーンID
}

import {
  to = aws_route53_record.cname["www"]             # 後述の for_each の例に合わせた宛先
  id = "Z0123456789EXAMPLE_www.example.com_CNAME"  # ゾーンID_名前_種類
}

レコードの ID は「ゾーンID_名前_種類」の形で、加重などのルーティングポリシーを使っているレコードは末尾に「_識別子」が付きます。terraform plan -generate-config-out=generated.tf を実行すると、対応するリソースの定義がまだないものについて、取り込む内容から設定のひな形を書き出せます。生成されたコードはそのまま使わず、不要な引数を整理し、名前を読みやすく付け直してから本来のファイルへ移します。

取り込んだ後の plan で「変更なし」になることを確認してから、次の変更に進みます。ここで差分が出る場合は、コードと実際の値が食い違っているので、適用せずにコード側を直します。

レコードの書き方

ゾーンと基本のレコード

resource "aws_route53_zone" "main" {
  name    = "example.com"
  comment = "managed by terraform"

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_route53_record" "apex" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "example.com"
  type    = "A"
  ttl     = 300
  records = ["203.0.113.10"]
}

resource "aws_route53_record" "mx" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "example.com"
  type    = "MX"
  ttl     = 3600
  records = ["10 mail.example.net"]
}

# 同じ名前の TXT は1つのリソースにまとめる
resource "aws_route53_record" "apex_txt" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "example.com"
  type    = "TXT"
  ttl     = 3600
  records = [
    "v=spf1 include:_spf.example.net ~all",
    "google-site-verification=your-verification-code",
  ]
}

output "name_servers" {
  value = aws_route53_zone.main.name_servers
}

Route 53 では「名前と種類」の組み合わせが1つのレコードセットです。SPF と所有権確認の TXT を別々のリソースで書くと、互いの値を上書きし合ったり、作成時に既存のレコードとぶつかったりします。同じ名前・同じ種類の値は、1つのリソースの records にまとめます。

255 文字を超える TXT(DKIM の公開鍵など)は、文字列の途中に \"\" を入れて区切ります(例:"v=DKIM1; k=rsa; p=前半部分\"\"後半部分")。Route 53 は1つの文字列を 255 文字までしか扱えないためです。

サブドメインをまとめて書く

同じ形のレコードが多い場合は、変数と for_each を使うと、追加や削除が一覧の編集だけで済みます。

variable "cname_records" {
  type = map(string)
  default = {
    "www"  = "example.com"
    "shop" = "shops.example-ec.net"
  }
}

resource "aws_route53_record" "cname" {
  for_each = var.cname_records

  zone_id = aws_route53_zone.main.zone_id
  name    = "${each.key}.example.com"
  type    = "CNAME"
  ttl     = 300
  records = [each.value]
}

CloudFront などへの ALIAS

ゾーン頂点を CloudFront やロードバランサーへ向けるときは、alias ブロックを使います。ALIAS のレコードには ttl と records を書きません。上の apex の A レコードの代わりに使う例です。

resource "aws_route53_record" "apex_alias" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "example.com"
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.site.domain_name
    zone_id                = aws_cloudfront_distribution.site.hosted_zone_id
    evaluate_target_health = false
  }
}

接続先の zone_id は、ID を手で書かず、リソースの属性から参照すると書き間違いを防げます。

ゾーンを削除から守る

ホストゾーンを削除して作り直すと、ネームサーバーが変わります。レジストラの NS を更新するまでドメインが引けなくなるため、DNS の Terraform では最も避けたい事故です。

  • prevent_destroy:上の例のように lifecycle に書いておくと、ゾーンを削除・置き換えする plan はエラーで止まります。
  • force_destroy:レコードが残っていてもゾーンを削除できるようにする引数です。本番のゾーンでは有効にしません。
  • 再利用可能な委任セット:複数のゾーンで同じネームサーバーを使いたい、作り直してもネームサーバーを変えたくない、という場合は、aws_route53_delegation_set を作って zone の delegation_set_id に指定する方法があります。既存のゾーンに後から付けることはできないため、新しくゾーンを作るときに検討します。

plan の読み方と運用のルール

plan では、記号で変更の種類を見分けます。~(update in-place)は TTL や値の変更で、通常はそのまま反映されます。-/+(replace)はリソースの作り直しで、レコードの名前や種類を変えたときに出ます。作り直しでは、古いレコードの削除と新しいレコードの作成の間に、短時間レコードがない状態になりうるため、アクセスの少ない時間に適用します。ゾーンに -/+ や - が出たら、適用せずに原因を調べます。

運用では、次のルールを決めておきます。

  • 画面からの変更を禁止する:コンソールで直した値は、次の apply で Terraform の値に戻されます。緊急で画面から直した場合は、すぐにコードへ反映します。食い違いは terraform plan -refresh-only で確認できます。
  • 既存レコードの上書きに頼らない:aws_route53_record には、既存のレコードを上書きして作成する allow_overwrite という引数があります。便利ですが、取り込み漏れに気付けなくなるため、既存のものは import で取り込みます。
  • 権限を絞る:Terraform を実行する IAM ロールには、対象のホストゾーンに対するレコードの参照と変更(route53:ListResourceRecordSets、route53:ChangeResourceRecordSets、route53:GetChange など)を中心に付け、ゾーンの削除権限は通常の実行ロールから外すことを検討します。必要な権限の一覧は、使うリソースによって変わるため公式ドキュメントで確認してください。
  • 変更はレビューを通す:plan の結果をプルリクエストに貼り、別の人が確認してから apply します。

まとめ

  • 既存の Route 53 は作り直さず、import ブロックで取り込み、plan が「変更なし」になってから管理を始めます。
  • state は S3 などに置いてロックを有効にし、ドメインごとに分けると差分が読みやすくなります。
  • 同じ名前と種類の値は1つの aws_route53_record にまとめ、長い TXT は区切り、ALIAS には ttl と records を書きません。
  • ホストゾーンには prevent_destroy を付け、force_destroy は使いません。作り直すとネームサーバーが変わります。
  • plan の -/+ を見落とさず、画面からの変更の禁止、権限の絞り込み、レビューを運用のルールにします。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲