Terraformで複数台のEC2インスタンスを構築する場合のTIPS
EC2でサーバーを構築する場合、負荷分散や可用性を考慮して複数台構成とする場合が多いかと思います。 全く同じ構成のEC2インスタンスを複数台構築する、、今回はTerraformでをそれを行う場合のTIPSをいくつかご紹介いたします。
前提
以下のEC2のリソース定義をベースに、各種TIPSを適用していきます。サブネット、セキュリティグループは既存のものを利用する前提で話を進めます。
variable aws_access_key {} variable aws_secret_key {} provider "aws" { access_key = "${var.aws_access_key}" secret_key = "${var.aws_secret_key}" region = "ap-northeast-1" } resource "aws_instance" "web" { ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "subnet-a1b3bad6" tags { Name = "web" } }
単純な複数台構成
Terraformでは作成するリソース数をcount
パラメータで指定できます。EC2を2台構築したい場合はcount = 2
と指定します。
resource "aws_instance" "web" { count = 2 ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "subnet-a1b3bad6" tags { Name = "web" } }
タグの値をインスタンス毎に変更したい
count.index
で、リソースのインデックスが取得できます。インデックスは0から始まりインクリメントされていくので、count.index
の値は1台目は「0」、2台目は「1」、3台目は「2」、、となります。このインデックス値をタグの値に含めることで、インスタンス毎に異なるタグの値を設定することができます。
resource "aws_instance" "web" { count = 2 ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "subnet-a1b3bad6" tags { Name = "${format("web%02d", count.index + 1)}" } }
タグの値にインデックスを含めるには、format関数を利用します。format関数では標準的なsprintf
のフォーマットで文字列を整形することができます。(詳しい構文についてはこちらが参考になります。)上記の例では、タグの値は「web01」、「web02」、、となります。Terraformの${}
(式の埋め込み構文)の中では算術演算子が使えるというのもポイントです。
Terraformで同じ設定のリソースを複数作成する場合は、このcount.index
を駆使してあれこれやっていくのが基本となります。
EC2インスタンスを異なるAZに配置したい
EC2を複数台構成にする場合は、2つのAZに分散して配置する場合が多いかと思います。count.index
を利用して、4台のEC2を、1台目はAZ-aに、2台目はAZ-cに、3台目はAZ-aに、、という具合に、2つのAZに交互に配置してみます。
variable "subnets" { default = { "0" = "subnet-a1b3bad6" "1" = "subnet-2278597b" } } resource "aws_instance" "web" { count = 4 ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "${lookup(var.subnets, count.index%2)}" tags { Name = "${format("web%02d", count.index + 1)}" } }
map形式で、「"0" = "AZ-aのサブネット"」、「"1" =" AZ-cのサブネット"」という変数を定義します。EC2のリソース定義側ではlookup関数でこのmapの値を取得します。lookup関数に渡すkeyはcount.index%2
とします(%
は算術演算子です)。count.index%2
の実行結果は「0」または「1」となりますので、結果的にEC2が2つのAZに交互に配置される形となります。
変数をmapではなく"subnet-000e1111,subnet-000e2222"
のようにカンマ区切りの文字列で定義して、EC2のリソース定義側ではelement関数とsplit関数を使ってサブネットIDを取得する方法もあります。
variable subnets { default = "subnet-a1b3bad6,subnet-2278597b" } resource "aws_instance" "web" { count = 4 ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "${element(split(",", var.subnets), count.index%length(split(",", var.subnets)))}" tags { Name = "${format("web%02d", count.index + 1)}" } }
split関数で文字列をリストに変換し、element関数でリストから値を取り出します。length関数でリストの長さが取得できますので、count.index%4
はcount.index%length(element(split(",", var.subnets)))
と書き換えることができます。
EC2インスタンスにEIPを付与したい
作成したEC2インスタンスのIDのリストはaws_instance.web.*.id
で参照できますので、EIPリソース定義の中で${element(aws_instance.web.*.id, count.index)}
と書いてインスタンスのIDを一つずつ取り出してEIPを付与します。
variable subnets { default = "subnet-a1b3bad6,subnet-2278597b" } resource "aws_instance" "web" { count = 4 ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "${element(split(",", var.subnets), count.index%length(split(",", var.subnets)))}" tags { Name = "${format("web%02d", count.index + 1)}" } } resource "aws_eip" "web" { count = 4 instance = "${element(aws_instance.web.*.id, count.index)}" vpc = true }
この場合EC2とEIPのcount
の数は同一になるので、count
の値を変数で定義するのも良いかと思います。
variable subnets { default = "subnet-a1b3bad6,subnet-2278597b" } variable web_servers_count { default = 4 } resource "aws_instance" "web" { count = "${var.web_servers_count}" ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "${element(split(",", var.subnets), count.index%length(split(",", var.subnets)))}" tags { Name = "${format("web%02d", count.index + 1)}" } } resource "aws_eip" "web" { count = "${var.web_servers_count}" instance = "${element(aws_instance.web.*.id, count.index)}" vpc = true }
ちなみに、Terraformのv0.6.16ではaws_eip_association
リソースが追加され、既存EIPの割当が可能になっています。
EC2に対してプロビジョニングを実行したい
本題からは話が逸れますが、EC2に対してプロビジョニングを実行したい場合を考えてみます。EC2のリソース定義の中にプロビジョニングの定義を追加したいところですが、EC2インスタンス作成時にはEIPが付与されていないため(インターネット経由でEC2に接続できないため)、プロビジョニングに失敗します。ワークアラウンドとして、EIPリソース側にプロビジョニングの定義を追加する方法があります。
variable subnets { default = "subnet-a1b3bad6,subnet-2278597b" } variable web_servers_count { default = 4 } variable ssh_key_file { default = "~/.ssh/cm-yawata.yutaka.pem" } resource "aws_instance" "web" { count = "${var.web_servers_count}" ami = "ami-29160d47" instance_type = "t2.nano" key_name = "cm-yawata.yutaka" vpc_security_group_ids = [ "sg-761b9512", ] subnet_id = "${element(split(",", var.subnets), count.index%length(split(",", var.subnets)))}" tags { Name = "${format("web%02d", count.index + 1)}" } } resource "aws_eip" "web" { count = "${var.web_servers_count}" instance = "${element(aws_instance.web.*.id, count.index)}" vpc = true provisioner "remote-exec" { connection { host = "${self.public_ip}" type = "ssh" user = "ec2-user" key_file = "${var.ssh_key_file}" } inline = [ "sudo yum -y update", ] } }
リソース定義のブロック内で自リソースの属性を参照するにはself
を使います。上記の例ではself.public_ip
でEIP(グローバルIP)を参照しています。
remote-exec
を使用したプロビジョニングについては、以下ブログエントリも合わせてご参照ください。
上で「EC2インスタンス作成時にはEIPが付与されていないため(インターネット経由でEC2に接続できないため)」と書きましたが、正確にはEC2の起動時に自動的にパブリックIPを割り当てることが可能なので、この自動割り当て設定を有効にしておけばEC2リソース定義内にプロビジョニングの定義を書くことができます。パブリックIPは自動的に割り当てない&EIPを付与する、という場合には、このワークアラウンドをお試しください。
OutputでEC2にアタッチされたEIPを出力したい
output
定義ではcount
は使えません。aws_eip.web.1.public_ip
、aws_eip.web.2.public_ip
、、とインスタンス数分のoutput定義を書くのは面倒なので、ワークアラウンドとしてjoin関数を使います。
output "EIPs of Web Servers" { value = "${join(", ", aws_eip.web.*.public_ip)}" }
join
でリストを結合して文字列に変換します(上記の例ではカンマ区切りの文字列に変換しています)。出力結果は以下のようになります。
EIP for Web Servers = 52.xxx.xxx.xxx, 52.xxx.xxx.xxx, 52.xxx.xxx.xxx, 52.xxx.xxx.xxx
まとめ
今回ご紹介したTIPSはもちろんEC2以外のリソース作成にも使えますので、tfファイルの最適化の一助になれば幸いです。
また、Githubで公開されているterraform-community-modulesを見ると、Terraformの組み込み関数の使いどころなど参考になるところが多いので、一度目を通しておくことをお勧めします。
参考
- Configuring Resources - Terraform by HashiCorp
- Interpolation Syntax - Terraform by HashiCorp
- Output multiple IP of instances · Issue #3174 · hashicorp/terraform
- How to loop over variable using count? · Issue #2993 · hashicorp/terraform
- terraformで複数のvpc_subnetを一つにまとめる - Qiita
- Elastic IP assignment · Issue #557 · hashicorp/terraform
- terraform-community-modules