Terraform v0.7.9 でACMのデータソースが導入されました

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、中山です。

2016年11月4日にTerraformのv0.7.9がリリースされました。v0.7.8から3日しか経過していないという短期間でのリリースでした。時期リリースバージョンはv0.8.0という実質的なメジャーバージョンアップなので、多くの新機能を含めるためにv0.7.9は小出しにしたのだと思います。そのため、アップデート内容は比較的少ないです。ですが、個人的にはACMのデータソースが地味に便利そうだったので、早速使ってみました。

2016年11月10日追記
今回のリリースサイクルはパッチバージョンに二桁の数値を利用する方式のようです。具体的なバージョン番号はCHANGELOGをご覧ください。

ACM用データソース

aws_acm_certificateが今回のバージョンアップで加わったACM用データソースです。マネジメントコンソールなどで作成したACMの各種属性(ARNなど)を取得し、Terraformのコード内で参照することができます。以前まではACMのARNをハードコードする必要があったのですが、このデータソースによりその必要は無くなりました。

Terraformはその構造上、ACMなどのVerify処理が必要なAWSリソースとは相性がよくありません。ACMの場合であれば、申請したドメインの正当な持ち主かどうか確かめるために、メールを利用した検証プロセスが実施されます。こういったTerraformが管理できないプロセスが入るため、現状ACMやSNSトピックのサブスクリプションなどに対応できていないのです。

TerraformではCloudFormationを実行することもできるので、それを利用すれば全てTerraformで管理することもできなくはないです。詳細については以下のエントリをご覧ください。

ですが、CloudFormationの利用はある種「奥の手」感があるので、積極的に利用するものでもないのかなと個人的には思っています。いずれにせよ、今回のACM用データソースの導入はTerraformの可能性を広げるアップデートだと感じています。

話が長くなりました。早速使ってみましょう。

構成図

動作確認用に作成した構成は以下のとおりです。

tf-acm-data-source

ACMで取得した証明書をCloudFrontにひも付け、オリジンのS3で静的サイトを構築してみました。DNSにはRoute53を利用します。Hosted Zoneは新規に作成するのではなく、すでに作成済みのHosted Zoneに対して新しいレコードセットを登録させます。注意点として、現状(2016年11月7日)北部バージニアリージョンで取得した証明書しかCloudFrontにひも付けることができません(FAQの In what Regions is ACM available? 参照)。利用する際はご注意ください。

ACMを利用した証明書の取得方法については以下のエントリを参照してください。

コード

GitHubに上げておきました。ご自由にお使いください。

aws_acm_certificate データソースの解説

実際に利用してみる前に該当データソースの解説をします。とは言え、データソースなので簡単に利用可能です。

data "aws_acm_certificate" "acm" {
  provider = "aws.us-east-1"
  domain   = "*.${var.domain}"
}

domain に参照したい証明書のドメイン名を指定すれば基本的にOKです。今回はワイルドカード証明書を参照したいので、先頭にアスタリスクを追加しています。デフォルトでIssuedな証明書を検索してくれます。他の状態の証明書を指定したい場合は statuses を設定してください。リージョンの概念のあるAWSリソースを東京リージョンで作成しているので、Multiple Providerを利用して北部バージニアリージョンを明示的に指定しています。Multiple Providerについては以下のエントリを参照してください。

データソースで取得した証明書のARNは以下のように参照可能です。

<snip>
  viewer_certificate {
    acm_certificate_arn      = "${data.aws_acm_certificate.acm.arn}"
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1"
  }
}

使ってみる

使ってみる際は以下のステップを実施してください。もちろんGitHubからcloneをしていることを前提としています。

Hosted Zoneのインポート

まず、Terraform以外で作成したHosted Zoneのstateファイルをインポートします。 _HOSTED_ZONE_ID_ を自分の環境にあったものに変更して以下のコマンドを実行してください。余談ですが、こちらのPRでHosted Zone用データソースが追加されそうなので、v0.8.0あたりではわざわざインポートしなくてもOKになりそうです。

$ terraform import aws_route53_zone.dns <_HOSTED_ZONE_ID>
provider.aws.region
  The region where AWS operations will take place. Examples
  are us-east-1, us-west-2, etc.

  Default: us-east-1
  Enter a value:

aws_route53_zone.dns: Importing from ID "*************"...
aws_route53_zone.dns: Import complete!
  Imported aws_route53_zone (ID: *************)
aws_route53_zone.dns: Refreshing state... (ID: *************)

Import success! The resources imported are shown above. These are
now in your Terraform state. Import does not currently generate
configuration, so you must do this next. If you do not create configuration
for the above resources, then the next `terraform plan` will mark
them for destruction.

実行すると、以下のようにカレントディレクトリに terraform.tfstate が作成されます。

{
    "version": 3,
    "terraform_version": "0.7.9",
    "serial": 0,
    "lineage": "bd5ed1bc-e323-48f0-b9b0-c0654d2a4a29",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_route53_zone.dns": {
                    "type": "aws_route53_zone",
                    "depends_on": [],
                    "primary": {
                        "id": "*************",
                        "attributes": {
                            "comment": "****************",
                            "id": "*************",
                            "name": "***************************",
                            "name_servers.#": "4",
                            "name_servers.0": "*********************",
                            "name_servers.1": "***********************",
                            "name_servers.2": "********************",
                            "name_servers.3": "********************",
                            "tags.%": "0",
                            "zone_id": "*************"
                        },
                        "meta": {},
                        "tainted": false
                    },
                    "deposed": [],
                    "provider": "aws"
                }
            },
            "depends_on": []
        }
    ]
}

Terraformは現状(v0.7.9)、stateファイルの生成しか対応していません。そのため、このstateファイルに合致するtfファイルを作成する必要があります。例えば、上記のstateファイルに対応するものだったら以下のような感じになります。

resource "aws_route53_zone" "dns" {
  name    = "${var.domain}."
  comment = "${var.comment}"
}

tfファイル作成後、 plan コマンドで差異が出ないことを確認できたらインポートは完了です。ちなみに、ドメイン名などリポジトリに含めたくない情報は secrets.tfvars ファイルなどに記述しておき、 -var-file オプションで指定するのがおすすめです。

$ terraform plan \
  -target=aws_route53_zone.dns \
  -var-file=secrets.tfvars
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but
will not be persisted to local or remote state storage.

aws_route53_zone.dns: Refreshing state... (ID: *************)
data.aws_acm_certificate.acm: Refreshing state...

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

~ aws_route53_zone.dns
    force_destroy: "" => "false"


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

force_destroy についてはTerraform独自の設定なので単純にインポートしたのみではstateファイルに記述されません。デフォルト値をそのまま使っても問題ないのですが、気になる場合はstateファイルを直接編集して追記してください。

$ diff -u terraform.tfstate terraform.tfstate.orig
--- terraform.tfstate   2016-11-05 12:48:38.000000000 +0900
+++ terraform.tfstate.orig      2016-11-05 12:34:14.000000000 +0900
@@ -25,7 +25,6 @@
                             "name_servers.2": "********************",
                             "name_servers.3": "********************",
                             "tags.%": "0",
-                            "force_destroy": "false",
                             "zone_id": "*************"
                         },
                         "meta": {},

他のリソースレコードをHosted Zoneで指定している場合は注意してください。 force_destory を有効化しているとまるごと消してしまいます。テスト用に取得したHosted Zoneなど、消えても良いもので試してください。

残りのAWSリソースを作成

後はいつもどおり plan / apply してAWSリソースを作成するだけです。

$ terraform plan -var-file=secrets.tfvars
<snip>
Plan: 6 to add, 0 to change, 0 to destroy.

$ terraform apply  -var-file=secrets.tfvars
<snip>
Outputs:

cf_id = **************
fqdn = static.**************************

動作確認

CloudFrontのディストリビューションがDeployedになった後、実際にアクセスしてみます。

$ curl https://static.**************************
This is a index page.

正常にアクセスできました。最後に、CloudFrontにひも付いているACMのARNを確認してみます。

$ aws cloudfront get-distribution-config \
  --id <_DISTRIBUTION_ID_> \
  --query 'DistributionConfig.ViewerCertificate'
{
    "SSLSupportMethod": "sni-only",
    "ACMCertificateArn": "arn:aws:acm:us-east-1:************:certificate/************************************",
    "MinimumProtocolVersion": "TLSv1",
    "Certificate": "arn:aws:acm:us-east-1:************:certificate/************************************",
    "CertificateSource": "acm"
}

正常にひも付いているようです。

まとめ

いかがだったでしょうか。

ACM用データソースの導入により、ACMのARNをハードコートせずにTerraformから扱えることを確認しました。

余談ですが、最近データソースがかなり充実してきたので「基本はマネジメントコンソールで作成し、一部のAWSリソースのみTerraformで管理する」という使い方ができるようになってきたと思います。こちらについては別エントリでご紹介できればと思います。

本エントリがみなさんの参考になれば幸いです。