Terraform v0.6.16 リリース / AWS関連項目まとめ

2016.05.12

はじめに

こんにちは、中山です。

Terraformのv0.6.16がリリースされました。本エントリではAWS関連の更新内容についてCHANGELOGをもとにまとめたいと思います。また、私が気になった部分をいくつかピックアップしてご紹介します。

新規追加Resource

リソース名 主な機能 Issue 参考URL
aws_api_gateway_account API Gateway Accountを操作する #6321 Specify a Stage's Settings in API Gateway
aws_api_gateway_authorizer API Gateway Authorizerを設定する #6320 Enable Amazon API Gateway Custom Authorization
aws_db_event_subscription RDSのイベントを取得する #6367 【AWS】RDS入門/イベントをメールで通知してみよう
aws_db_option_group RDSのオプショングループを設定する #4401 Amazon RDS MySQL 5.6と新機能を試してみた
aws_eip_association 既存EIPをAWSリソースに関連付ける #6552 N/A

既存Resource/Provider/Backend改善点

リソース名 改善点 Issue
aws_opswork_stack agent_version オプションの追加 #6493
api_gateway_method
api_gateway_integration
request_parameters_in_json オプションの追加 #6501
api_gateway_integration_response
api_gateway_integration_response
response_parameters_in_json オプションの追加 #6344
aws_cloudfront_dirstribution s3_origin_configcustom_origin_config が指定されていない場合、エラーとならないように origin_access_identity を空にする処理を追加 #6487
aws_iam_server_certificate エラー処理の改善 #6442
aws allowed_account_ids または forbidden_account_ids オプションを利用する際、アカウント情報を取得する方法に sts:GetCallerIdentity を追加 #6385
aws_redshift_cluster automated_snapshot_retention_period オプションのデフォルト値を1であると明示的に指定して、 内部的にエラーが発生する可能性を低減させる処理を追加 #6537
aws_cloudfront_dirstribution hosted_zone_id の追加 #6530
S3 Backend S3 Backend利用時にAWSクレデンシャルを取得する処理がaws providerと同じになった #5270

ピックアップ

aws_cloudfront_dirstribution のattributeに hosted_zone_id が追加

aws_s3_bucketaws_elb リソースにはALIASレコードを設定する際に利用する hosted_zone_id というattributeがあります。今回のアップデートで aws_cloudfront_dirstribution にもこのattributeが追加されました。今まででもCloudFrontのHosted Zone IdをハードコードしてあげればALIASレコードの作成はできたのですが、attributeとして追加されることでより簡単に利用することができるようになりました。

以下にCloudFrontのドメインに対してALIASレコードを設定するサンプルコードを記載します。ドメインは適当なものに読み替えてください。

variable "name" {
  default = "ap-northeast-1-cloudfront-hosted-zone-id-demo"
}

variable "region" {
  default = "ap-northeast-1"
}

variable "site_domain" {
  default = "*********.**."
}

provider "aws" {
  region = "${var.region}"
}

resource "template_file" "s3" {
  template = <<EOT
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjets",
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::${bucket_name}/*"]
    }
  ]
}
EOT

  vars {
    bucket_name = "${var.name}"
  }
}

resource "aws_s3_bucket" "s3" {
  bucket        = "${var.name}"
  acl           = "public-read"
  force_destroy = true
  policy        = "${template_file.s3.rendered}"

  website {
    index_document = "index.html"
  }
}

resource "aws_s3_bucket_object" "s3" {
  bucket       = "${aws_s3_bucket.s3.bucket}"
  key          = "index.html"
  source       = "${concat(path.module, "/", "index.html")}"
  content_type = "text/html"
}

resource "aws_cloudfront_distribution" "cf" {
  enabled             = true
  comment             = "${var.name}"
  default_root_object = "index.html"
  price_class         = "PriceClass_200"
  retain_on_delete    = true

  origin {
    domain_name = "${concat(aws_s3_bucket.s3.id, ".s3.amazonaws.com")}"
    origin_id   = "${var.name}"
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "${aws_s3_bucket.s3.id}"

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "allow-all"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

resource "aws_route53_zone" "dns" {
  name = "${var.site_domain}"
}

resource "aws_route53_record" "dns" {
  zone_id = "${aws_route53_zone.dns.zone_id}"
  name    = "${concat("cf.", var.site_domain)}"
  type    = "A"

  alias {
    name                   = "${aws_cloudfront_distribution.cf.domain_name}"
    zone_id                = "${aws_cloudfront_distribution.cf.hosted_zone_id}"
    evaluate_target_health = true
  }
}

output "website_endpoint" {
  value = "${aws_s3_bucket.s3.website_endpoint}"
}

output "cloudfront_domain_name" {
  value = "${aws_cloudfront_distribution.cf.domain_name}"
}

output "cloudfront_hosted_zone_id" {
  value = "${aws_cloudfront_distribution.cf.hosted_zone_id}"
}

output "alias_record_fqdn" {
  value = "${aws_route53_record.dns.fqdn}"
}

まずはテスト用にCloudFrontでキャッシュさせる index.html を作成しておきます。

$ echo 'Hello, World!' > index.html

続いてTerraformを実行します。

$ terraform apply
<snip>

Outputs:

  alias_record_fqdn         = cf.*********.**
  cloudfront_domain_name    = d2t6keum07cvba.cloudfront.net
  cloudfront_hosted_zone_id = Z2FDTNDATAQYW2
  website_endpoint          = ap-northeast-1-cloudfront-hosted-zone-id-demo.s3-website-ap-northeast-1.amazonaws.com

今回の例ではCloudFrontのドメイン名が d2t6keum07cvba.cloudfront.net です。レコードセットの内容を確認してみましょう。

$ aws route53 list-resource-record-sets \
  --hosted-zone-id "$(aws route53 list-hosted-zones --query 'HostedZones[].Id' --output text | cut -d/ -f3)" \
  --query 'ResourceRecordSets[2]'
{
    "AliasTarget": {
        "HostedZoneId": "Z2FDTNDATAQYW2",
        "EvaluateTargetHealth": true,
        "DNSName": "d2t6keum07cvba.cloudfront.net."
    },
    "Type": "A",
    "Name": "cf.*********.**."
}

正常に登録されているようですね。続いて実際にアクセスしてみましょう。

$ curl cf.*********.**.
Hello, World!

上記のように「Hello, World!」と表示されたら成功です。わざわざCloudFrontのHosted Zone IDを指定せずに済むのは地味に便利ですね。

aws_eip_association により既存EIPの割当が可能になった

今まではTerraformでEIPを利用する場合 aws_eip を使用し、新規にEIPを作成してからAWSリソースへ関連付けるという作業が必要でした。つまり、すでに作成済みのEIPを割り当てられないという問題がありました。しかし、 aws_eip_association リソースの追加により既存EIPをAWSリソースに対して割り当てられるようになりました。実際に試してみましょう。

まずはテスト用のEIPを作成します。

$ aws ec2 allocate-address
{
    "PublicIp": "52.69.11.29",
    "Domain": "vpc",
    "AllocationId": "eipalloc-b9bfdbdc"
}

一時的に利用しているのでEIPの情報出してますが、割り当てられたパブリックIPが 52.69.11.29 で、 AllocationIdeipalloc-b9bfdbdc です。このEIPをEC2インスタンスに割り当ててみたいと思います。

サンプルコードが以下になります。関連する部分をハイライトしています。

variable "name" {
  default = "test"
}

variable "region" {
  default = "ap-northeast-1"
}

variable "vpc_cidr" {
  default = "172.16.0.0/16"
}

variable "az" {
  default = "ap-northeast-1a"
}

variable "public_subnet" {
  default = "172.16.0.0/24"
}

variable "web_instance_type" {
  default = "t2.micro"
}

variable "web_instance_ami_id" {
  default = "ami-29160d47"
}

variable "allocation_id" {
  default = "eipalloc-b9bfdbdc"
}

variable "key_name" {
  default = "*****"
}

provider "aws" {
  region = "${var.region}"
}

resource "aws_vpc" "vpc" {
  cidr_block           = "${var.vpc_cidr}"
  enable_dns_support   = true
  enable_dns_hostnames = true
}

resource "aws_internet_gateway" "public" {
  vpc_id = "${aws_vpc.vpc.id}"
}

resource "aws_subnet" "public" {
  vpc_id                  = "${aws_vpc.vpc.id}"
  cidr_block              = "${var.public_subnet}"
  availability_zone       = "${var.az}"
  map_public_ip_on_launch = true
}

resource "aws_route_table" "public" {
  vpc_id = "${aws_vpc.vpc.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.public.id}"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = "${aws_subnet.public.id}"
  route_table_id = "${aws_route_table.public.id}"
}

resource "aws_network_acl" "acl" {
  vpc_id     = "${aws_vpc.vpc.id}"
  subnet_ids = ["${aws_subnet.public.id}"]

  ingress {
    protocol   = "-1"
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  egress {
    protocol   = "-1"
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }
}

resource "aws_security_group" "web" {
  name        = "${var.name}-web"
  vpc_id      = "${aws_vpc.vpc.id}"
  description = "${var.name}-SG"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "web" {
  ami                         = "${var.web_instance_ami_id}"
  instance_type               = "${var.web_instance_type}"
  vpc_security_group_ids      = ["${aws_security_group.web.id}"]
  subnet_id                   = "${aws_subnet.public.id}"
  key_name                    = "${var.key_name}"
  associate_public_ip_address = true

  root_block_device {
    volume_type = "gp2"
    volume_size = 8
  }
}

resource "aws_eip_association" "eip" {
  instance_id   = "${aws_instance.web.id}"
  allocation_id = "${var.allocation_id}"
}

output "web_public_ip" {
  value = "${aws_eip_association.eip.public_ip}"
}

実行してみましょう。

$ terraform apply
<snip>
Outputs:

  web_public_ip = 52.69.11.29

該当のIPがEC2インスタンスに割り当てられたようです。SSHしてみましょう。

$ ssh -i path/to/keypair ec2-user@52.69.11.29
<snip>

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2016.03-release-notes/
[ec2-user@ip-172-16-0-66 ~]$

ちゃんとEIPが関連付けられたようですね。

まとめ

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

Terraformはズッ友だよ。

本エントリがみなさんの参考になったら幸いに思います。