🌝

Terraformのimportブロックを使ってRoute53のレコードをインポートする

2024/03/14に公開

はじめに

AWSコンソールから手動でRoute53のレコードを作成した後に、Terraformコード管理にしたくなりましたので、 import ブロックを使ったやり方を調査、検証してみました。

import ブロックとは

Terraformにはv1.5.0から import ブロックというものが実装されておりまして、Terraform以外で作成されたリソースをTerraform管理化に移行するための機能です。それより以前のバージョンではterraform import コマンドを使ってStateを更新する必要がありましたが、 import ブロックを使えば terraform import コマンドの実行は不要になります。
また、v1.6.0からは import ブロックの中で静的な値以外に、変数や他の値を参照する式が使えるようになったようで、より使いやすくなりました。

事前準備

検証のための事前準備としてAWSコンソールから直接、以下のように hoge.com というホストゾーンと、TXTレコードを1つ作成しておきました。

作成したTXTレコード

key value
レコード名 _dmarc.hoge.com
タイプ TXT
"v=DMARC1;p=none"

※ホストゾーン自体はTerraform管理になっていない前提で、ゾーンIDは Zxxxxxxxxxxxxxx として進めます。今回インポートしたい対象はTXTレコードのみとします。

コードを書く

インポートしたいTXTレコードができたので、次はコードを書きます。Terraformのベストプラクティスにならって、以下のようなディレクトリ構造でファイルを配置しました。(dev環境という想定です。)

.
├── environments
│   └── dev
│       ├── import.tf      //importブロックを記述
│       ├── main.tf        //mail_senderモジュールを使用
│       ├── terraform.tf   //terraform設定を記述
│       └── variables.tf   //ルートモジュールに必要な入力値
└── modules
    └── mail_sender        //mail_senderモジュール
        ├── main.tf        //Route53レコードのリソースを記述
        └── variables.tf   //mail_senderモジュールに必要な入力値

environments/dev がルートモジュールであり、そこで mail_sender モジュールを呼び出す構成です。今回のTXTレコードはメール送信者としての認証のための設定という想定であるため、このようなモジュール名にしています。
ファイルの中身は以下のように記述しました。

今回のポイントではないファイルたち
environments/dev/terraform.tf
terraform {

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40.0"
    }
  }

  required_version = ">= 1.7.4"
}
environments/dev/variables.tf
variable "hostzone_name" {
  type    = string
  default = "hoge.com"
}
variable "hostzone_id" {
  type    = string
  default = "Zxxxxxxxxxxxxxx"
}
environments/dev/main.tf
provider "aws" {
  region = "ap-northeast-1"
}
module "mail_sender" {
  source = "../../modules/mail_sender"

  hostzone_name = var.hostzone_name
  hostzone_id   = var.hostzone_id
}
modules/mail_sender/variables.tf
variable "hostzone_name" { type = string }
variable "hostzone_id" { type = string }
modules/mail_sender/main.tf
resource "aws_route53_record" "dmarc" {
  zone_id = var.hostzone_id
  name    = "_dmarc.${var.hostzone_name}"
  type    = "TXT"
  ttl     = 300
  records = ["v=DMARC1;p=none"]
}

↑↑↑
インポート先となる module.mail_sender.aws_route53_record.dmarc を記述しています。この例では、インポートした内容と同じ設定になるように、あらかじめ値を指定しています。

environments/dev/import.tf
import {
  to = module.mail_sender.aws_route53_record.dmarc
  id = "${var.hostzone_id}__dmarc.${var.hostzone_name}_TXT"
}

↑↑↑
import ブロックを記述できる場所はルートモジュールのみだそうで、子モジュールに書くとエラーになります。そのため import ブロックの to 属性で、インポート先のモジュールを指定する形にしました。

terraform plan を確認する

コードが書き終わったら terraform plan を実行してみます。

インポート後の差分がないパターン

以下は terraform plan の結果を抜粋したものになります。

$ terraform plan
...(省略)
Terraform will perform the following actions:

  # module.mail_sender.aws_route53_record.dmarc will be imported
    resource "aws_route53_record" "dmarc" {
        fqdn                             = "_dmarc.hoge.com"
        id                               = "Zxxxxxxxxxxxxxx__dmarc.hoge.com_TXT"
        multivalue_answer_routing_policy = false
        name                             = "_dmarc.hoge.com"
        records                          = [
            "v=DMARC1;p=none",
        ]
        ttl                              = 300
        type                             = "TXT"
        zone_id                          = "Zxxxxxxxxxxxxxx"
    }

Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

このパターンではインポートしたリソースと、記述したmodule.mail_sender.aws_route53_record.dmarc が同じ内容であったため 1 to import だけになりました。

インポート後の差分があるパターン

module.mail_sender.aws_route53_record.dmarc の設定を以下のように変更してみます。
さきほど p=none と記述していた値の一部を p=reject に変更しています。

resource "aws_route53_record" "dmarc" {
  zone_id = var.hostzone_id
  name    = "_dmarc.${var.hostzone_name}"
  type    = "TXT"
  ttl     = 300
  records = ["v=DMARC1;p=reject"]
}

以下は terraform plan の結果を抜粋したものになります。

$ terraform plan
...(省略)
Terraform will perform the following actions:

  # module.mail_sender.aws_route53_record.dmarc will be updated in-place
  # (imported from "Zxxxxxxxxxxxxxx__dmarc.hoge.com_TXT")
  ~ resource "aws_route53_record" "dmarc" {
        fqdn                             = "_dmarc.hoge.com"
        id                               = "Zxxxxxxxxxxxxxx__dmarc.hoge.com_TXT"
        multivalue_answer_routing_policy = false
        name                             = "_dmarc.hoge.com"
      ~ records                          = [
          - "v=DMARC1;p=none",
          + "v=DMARC1;p=reject",
        ]
        ttl                              = 300
        type                             = "TXT"
        zone_id                          = "Zxxxxxxxxxxxxxx"
    }

Plan: 1 to import, 0 to add, 1 to change, 0 to destroy.

このパターンでは、インポートもして、その後の差分も表示してくれています。
1 to import1 to change という結果になりました。

terraform plan の結果を元にコードを修正

今回はあえて差分を出してみましたが、本来であれば差分がでなくなるまでコードを修正していく流れになると思います。 terraform plan の結果を見れば、コードをどのように修正すればいいかわかり、差分がでなくなれば、 terraform apply できる合図になりますね。

terraform apply

コードが完成したら、 terraform apply を実行すれば完了です。

Terraform Cloudでの実行

今回私はTerraform Cloudを使って terraform apply を実行しましたので結果はスクショを貼りますね。またCLIによる実行結果の見え方の違いとして terraform plan のスクショも貼っておきます。(ゾーンIDはマスクしています)

Stateの確認

以下は terraform apply 後のStateの抜粋です。

{
  "version": 4,
  "terraform_version": "1.7.4",
  ...(省略)
  "resources": [
    {
      "module": "module.mail_sender",
      "mode": "managed",
      "type": "aws_route53_record",
      "name": "dmarc",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 2,
          "attributes": {
            "fqdn": "_dmarc.hoge.com",
            "name": "_dmarc.hoge.com",
            "records": ["v=DMARC1;p=reject"],
            "type": "TXT",
            "ttl": 300,
            ...(省略)
          }
        }
      ]
    }
  ],
  "check_results": null
}

ちゃんと modemanaged になっており、Terraformで管理されていることが確認できました。

インポート後の import ブロックの削除

インポートしてTerraform管理になったあとは import ブロックは削除することができます。
environments/dev/import.tf ファイルごと削除してTerraform Cloudで terraform plan を実行した結果が以下になります。 No changes となり削除しても何も影響がないことが確認できました。

おわりに

一度試してみると、Terraformの便利さを改めて実感しますね。最近では、AWSのCDKもインポートができるようになったようですが、以前はできなかったためやり辛さを感じていました。
インポートが容易になると、「とりあえずポチポチして作って、あとでインポートすればいっか。」という選択が取れるようになるので、今後も活用していきたいと思います。

レスキューナウテックブログ

Discussion