Terraform

Terraformのリファクタリング: リモートのアカウントID参照

Terraformのリファクタリング楽しいですね。ついついいろんなハードコードを変数化するのに熱中してしまいます。

AWSをTerraformで構築・運用するときAWSのアカウントIDが必要になることがあります。 特にVPC Peering、Private Link, SNS, S3のBucket Policyなど、クロスアカウントで利用できるリソースなどは別アカウントのIDが欲しくなる事があります。 一つならまだしも2つ以上アカウントIDと連携したりすると混乱するので、ここにハードコードしたIDではなくわかりやすい変数名、しかもリモートステートで別アカウントのIDが参照できれば良いなと思いました。

自アカウントであれば、aws_caller_identityを利用すれば簡単に取得することができます。

1
data "aws_caller_identity" "self" { }

どこかに上記のような記述をすると、下記のような形でアカウントIDを参照できます。

1
"${data.aws_caller_identity.self.account_id}"

これにリモートステートの仕組みをプラスすることで、別アカウントのIDを変数化できました。 参照される側のアカウントをA, 参照する側のアカウントをBとしています。

参照される側のアカウントの設定

まずは参照されるアカウントAの設定です。 別アカウントBに変数を公開するにはoutputとして登録する必要があります。 今回は変数名をaccount_idとして、AWSのアカウントIDを公開しています。

1
2
3
4
5
data "aws_caller_identity" "self" {}

output "account_id" {
  value = "${data.aws_caller_identity.self.account_id}"
}

また、S3のバケットポリシーを編集して、TerraformのStateをアカウントBが閲覧できるように設定をする必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ExampleStatement1",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::[アカウントBのID]:root",
                ]
            },
            "Action": [
                "s3:GetBucketLocation",
                "s3:ListBucket",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::[アカウントAのStateが入っているBucket]",
                "arn:aws:s3:::[アカウントAのStateが入っているBucket]"/*"
            ]
        }
    ]
}

参照される側のアカウントの設定

参照する側のアカウントBでは参照するアカウントAのStateを参照する設定を追加します。

1
2
3
4
5
6
7
8
9
data "terraform_remote_state" "account_a" {
  backend = "s3"

  config {
    bucket = "[アカウントAのStateが入っているBucket]"
    key    = "state"
    region = "ap-northeast-1"
  }
}

例えばアカウントAとBのVPC Peeringの設定とかで利用できます。

1
2
3
4
5
6

resource "aws_vpc_peering_connection" "acount_a_b" {
  peer_owner_id = "${data.terraform_remote_state.account_a.account_id}"
  peer_vpc_id   = "[アカウントAのVPC ID]"
  vpc_id        = "[アカウントBのVPC ID]"
}

アカウントAのVPC IDもリモートステートで取得する手もありますが、今回は割愛しました。

まとめ

Terraformを書いていていろんなアカウントと連携する設定を書くときに、アカウントID部分が数字の羅列だと混乱するので変数化してみました。 クロスアカウントの機能は充実していますし機能拡張もされ続けています。 何かと使い所はあるのではないでしょうか。

TerraformでSnapshotからAMI作って立ち上げる

独自のAMIを作ってたら、どうもうまく行かず。Try and Errorの様相を呈してきた。 何度もSnapshot作ってAMIを作ってLaunchってやり始めたので、Terraformでこの辺をやるようにした。

ただ、EBSボリュームからSnapshotを作る部分は見つけられなかったので、Snapshotを作るところは手動です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
resource "aws_ami" "origin-base" {
    name = "origin-base-20161124"
    virtualization_type = "hvm"
    root_device_name = "/dev/sda1"

    ebs_block_device {
        device_name = "/dev/sda1"
        snapshot_id = "snap-60559eee"
        volume_size = 10
    }
}

resource "aws_instance" "gside-origin" {
    ami                         = "${aws_ami.origin-base.id}"
    availability_zone           = "${aws_subnet.main.availability_zone}"
    ebs_optimized               = false
    associate_public_ip_address = false
    instance_type               = "t2.nano"
    monitoring                  = false
    key_name                    = "${aws_key_pair.gside-key.key_name}"
    vpc_security_group_ids      = ["${aws_security_group.basic.id}"]
    associate_public_ip_address = true
    disable_api_termination     = "true"
    source_dest_check           = "false"
    subnet_id 			= "${aws_subnet.main.id}"

    root_block_device {
        volume_type           = "gp2"
        volume_size           = 10
        delete_on_termination = true
    }

    tags {
        "Name" = "gside-origin"
    }
}

Terraformで追加のEBSをインスタンスにAttacheする

とあるインスタンスに追加でEBS VolumeをAttacheしたかったのですが、 現在のTerraform(Ver 0.7.9)では新規にインスタンスを作成し直さないとできないようです。 残念。

ebs_block_deviceの箇所で追加のEBSについて記載しています。 サイズとマウントポイントを記載するだけ。 簡単なんだけどやはりインスタンス作りなおしなのは惜しいですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
resource "aws_instance" "gside" {
    ami                         = "ami-0c11b26d"
    availability_zone           = "${aws_subnet.main.availability_zone}"
    ebs_optimized               = false
    associate_public_ip_address = false
    instance_type               = "t2.nano"
    monitoring                  = false
    key_name                    = "${aws_key_pair.gside-key.key_name}"
    vpc_security_group_ids      = ["${aws_security_group.basic.id}"]
    associate_public_ip_address = true
    private_ip                  = "10.0.1.10"
    disable_api_termination     = "true"
    source_dest_check           = "false"
    subnet_id 			= "${aws_subnet.main.id}"

    root_block_device {
        volume_type           = "gp2"
        volume_size           = 10
        delete_on_termination = true
    }

    ebs_block_device {
	device_name = "/dev/xvdb"
        volume_size           = 10
    }

    tags {
        "Name" = "gside"
    }
}

インスタンスにログインしてディスクの状況を確認します。

1
2
3
4
5
[ec2-user@ip-10-0-1-10 ~]$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  10G  0 disk
└─xvda1 202:1    0  10G  0 part /
xvdb    202:16   0  10G  0 disk

sshの鍵をTerraformで扱う

AMIを作る時に必要なSSHのキーですが、 Terraformでは鍵のインポートのみサポートしています。

確かに秘密鍵をダウンロードするより、公開鍵をアップロードするほうが健全ですね。

鍵の作成

手元のPCで秘密鍵と公開鍵のペアを作成します。

1
2
3
4
5
6
7
8
$ ssh-keygen -t rsa -b 2048
Generating public/private rsa key pair.
Enter file in which to save the key
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in
Your public key has been saved in
The key fingerprint is:

Terraformで公開鍵をアップロード

リソースにaws_key_pairを使って、公開鍵をアップロードします。 key_nameの参照には${aws_key_pair.gside-key.key_name}を使います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
resource "aws_key_pair" "gside-key" {
  key_name = "gside-key"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvGSftV0pe4Pu4AA6CIwZ5QwUnVmO1YZ6LnkUuY1oti0OBuwNhvKE2gJ7eELwUmXLixq5OsccItAeUyIstp8u86AJqaO4DeZBE6gHwaBlrKKG+0b0jFsNCtfFu/jFsmnTuED5I/MpggUk0NKV4BFveqX9Wi7fxaOt5XEsx4XR9mJD+RrtrVAuzSoSK3y3jJgLKpBku1TqcaPZutE6fHIE6OalPRY0JCrN9WzQmFWXL+whTe9KaPMKs6PdHeFG+KBpnY9VjQxxc+lPmMfcID1t/xAuYpi5TZQbtB+YH5Qrn4uz+v1FyL9N/GYmcX8dVz9d9HMgXDUgzgEZU3JpWd6uf gside"
}

resource "aws_instance" "gside" {
    ami                         = "ami-0c11b26d"
    availability_zone           = "${aws_subnet.main.availability_zone}"
    ebs_optimized               = false
    associate_public_ip_address = false
    instance_type               = "t2.nano"
    monitoring                  = false
    key_name                    = "${aws_key_pair.gside-key.key_name}"
    vpc_security_group_ids      = ["${aws_security_group.basic.id}"]
    associate_public_ip_address = true
    private_ip                  = "10.0.1.10"
    disable_api_termination     = "true"
    source_dest_check           = "false"
    subnet_id 			= "${aws_subnet.main.id}"

    root_block_device {
        volume_type           = "gp2"
        volume_size           = 10
        delete_on_termination = true
    }

    tags {
        "Name" = "gside"
    }
}

まとめ

これで秘密鍵をダウンロードする気持ち悪さからもおさらばです。

Route53のHealthをTerraformで設定してみる

先日入門したTerraform、Route53のURL監視も入れてみました。 ポイントとしては、Route53のCloudWatchアラームはN.Virginiaのリージョンで作る必要がある点です。 それ以外のリージョンは現在はサポートされていません。

Route53のヘルスチェックを作成する

Route53のヘルスチェックを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
resource "aws_route53_health_check" "gside" {
  fqdn = "gside.org"
  port = 80
  type = "HTTP"
  resource_path = "/blowg/b"
  failure_threshold = "3"
  request_interval = "30"

  tags = {
    Name = "gside"
   }
}

Cloudwatchアラームを作成する

前述したように、N.VirginiaのリージョンにClouwdWatchアラームを作成します。 dimensionsには先程作成したRoute53のヘルスチェックのIDを指定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
provider "aws" {
    region = "us-east-1"
    alias = "virginia"
}

resource "aws_cloudwatch_metric_alarm" "gside-healthcheck" {
    provider      = "aws.virginia"
    alarm_name = "gside-healthcheck"
    comparison_operator = "GreaterThanThreshold"
    evaluation_periods = "1"
    metric_name = "HealthCheckStatus"
    namespace = "AWS/Route53"
    period = "60"
    statistic = "Minimum"
    threshold = "1"
    alarm_description = "This metric monitor gside url healthcheck"
    dimensions {
        HealthCheckId="${aws_route53_health_check.gside.id}"
	}
    alarm_actions = ["arn:aws:sns:xxxxxxx"]
}

まとめ

ClouwdWatchアラームをN.Virgnia以外で作成して、なかなかRoute53ヘルスチェックと関連づかずハマりましたが、 それ以外は問題なく作成できる内容でした。 ちなみにアラート時のEmail送信用SNSを作るところもTerraform化しようとしましたが、 Emailは送信者認証が入るところがTerraformのモデルに合わず未サポートだそうです。