Terraformを使う機会が会ったので、概要をまとめてみました。
TerraformはCloudFormationよりも定義ファイルが簡潔で、驚くほど習得しやすいツールです。
また、Chefやシェルによるプロビジョニングの仕組みも用意されているためミドルウェアまでささっと構築するようなユースケースにも使えそうです。
CloudFormationと比較した特徴については、「HashiCorpの新オーケストレーションツールTerraformを試してみた」の「CloudFormationの弱いところはカバー済み」が参考になります。
以下は、ほぼGETTING STARTEDを確認した内容になります
また、各リソースはデフォルトVPCに構築しています。
セットアップ
INSTALL TERRAFORMの手順に従ってインストール
バイナリパッケージがあるので、DOWNLOAD TERRAFORMよりダウンロードして適当なディレクトリに解凍して、パスを通すだけ
Mac OS Xの場合は、.bash_profileあたりに設定しておく
以下 /opt/terraform_0.6.6に解凍した場合
# .bash_profile
export PATH=$PATH:/opt/terraform_0.6.6
ターミナルでterraform
コマンドを実行できればセットアップ完了
$ terraform
usage: terraform [--version] [--help] <command> [<args>]
Available commands are:
apply Builds or changes infrastructure
destroy Destroy Terraform-managed infrastructure
get Download and install modules for the configuration
graph Create a visual graph of Terraform resources
init Initializes Terraform configuration from a module
output Read an output from a state file
plan Generate and show an execution plan
push Upload this Terraform module to Atlas to run
refresh Update local state file against real resources
remote Configure remote state storage
show Inspect Terraform state or plan
taint Manually mark a resource for recreation
version Prints the Terraform version
インフラを構築してみる
BUILD INFRASTRUCTURE
の手順にそって、AWSにEC2を起動してみます
# example.tf
provider "aws" {
access_key = "ACCESS_KEY_HERE"
secret_key = "SECRET_KEY_HERE"
region = "ap-northeast-1"
}
resource "aws_instance" "example" {
ami = "ami-383c1956"
instance_type = "t2.micro"
subnet_id = "subnet-7d83150a"
}
amiは東京リージョンのAmazon Linuxを指定
instance_typeはt2.microを指定しているので、subnet_idが必須になります。
ここでは、デフォルトVPCのサブネットの1つを指定しています
terraform plan
コマンドで実行計画を確認できる
$ terraform plan
Refreshing Terraform state prior to plan...
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.
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_instance.example
ami: "" => "ami-383c1956"
availability_zone: "" => "<computed>"
ebs_block_device.#: "" => "<computed>"
ephemeral_block_device.#: "" => "<computed>"
instance_type: "" => "t2.micro"
key_name: "" => "<computed>"
placement_group: "" => "<computed>"
private_dns: "" => "<computed>"
private_ip: "" => "<computed>"
public_dns: "" => "<computed>"
public_ip: "" => "<computed>"
root_block_device.#: "" => "<computed>"
security_groups.#: "" => "<computed>"
source_dest_check: "" => "1"
subnet_id: "" => "subnet-7d83150a"
tenancy: "" => "<computed>"
vpc_security_group_ids.#: "" => "<computed>"
Plan: 1 to add, 0 to change, 0 to destroy.
<computed>
はリソースが作成されてみないとわからない項目です
apply
コマンドで適用
$ terraform apply
aws_instance.example: Creating...
ami: "" => "ami-383c1956"
availability_zone: "" => "<computed>"
ebs_block_device.#: "" => "<computed>"
ephemeral_block_device.#: "" => "<computed>"
instance_type: "" => "t2.micro"
key_name: "" => "<computed>"
placement_group: "" => "<computed>"
private_dns: "" => "<computed>"
private_ip: "" => "<computed>"
public_dns: "" => "<computed>"
public_ip: "" => "<computed>"
root_block_device.#: "" => "<computed>"
security_groups.#: "" => "<computed>"
source_dest_check: "" => "1"
subnet_id: "" => "subnet-7d83150a"
tenancy: "" => "<computed>"
vpc_security_group_ids.#: "" => "<computed>"
aws_instance.example: Creation complete
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
リソース名の”example”は、terraformの中で管理されるもので、Tag Nameにはなりません。
インフラの変更
$ terraform plan
Refreshing Terraform state prior to plan...
aws_instance.example: Refreshing state... (ID: i-4ba71cee)
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.
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_instance.example
tags.Name: "" => "Terraform example"
Plan: 0 to add, 1 to change, 0 to destroy.
$ terraform apply
aws_instance.example: Refreshing state... (ID: i-4ba71cee)
aws_instance.example: Modifying...
tags.Name: "" => "Terraform example"
aws_instance.example: Modifications complete
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
tags.Nameに指定した値が適用されました。
インフラの削除
$ terraform plan -destroy
Refreshing Terraform state prior to plan...
aws_instance.example: Refreshing state... (ID: i-4ba71cee)
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.
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_instance.example
Plan: 0 to add, 0 to change, 1 to destroy.
Enter a value: でyesと入力すると実行される
$ terraform destroy
Do you really want to destroy?
Terraform will delete all your managed infrastructure.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_instance.example: Refreshing state... (ID: i-4ba71cee)
aws_instance.example: Destroying...
aws_instance.example: Destruction complete
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
リソースの依存について
RESOURCE DEPENDENCIES
ここまで、ドキュメントにしたがって、1つのaws_instanceの作成について確認してきましたが、複数のリソースを指定した場合は、適用順序を制御する必要があります。
EC2インスタンスにElastic IPリソースを追加するようにexample.tf
にaws_eip
リソースを追加してみます。
# example.tf" highlight="7-10"]
provider "aws" {
access_key = "ACCESS_KEY_HERE"
secret_key = "SECRET_KEY_HERE"
region = "ap-northeast-1"
}
resource "aws_eip" "ip" {
instance = "${aws_instance.example.id}"
vpc = true
}
resource "aws_instance" "example" {
ami = "ami-383c1956"
instance_type = "t2.micro"
subnet_id = "subnet-7d83150a"
tags {
Name = "Terraform example"
}
}
aws_eip
リソースの${aws_instance.example.id}
は、後のaws_instance
リソースaws_instance.example
を参照します。
実行計画を確認
$ terraform plan
Refreshing Terraform state prior to plan...
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.
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_eip.ip
allocation_id: "" => "<computed>"
association_id: "" => "<computed>"
domain: "" => "<computed>"
instance: "" => "${aws_instance.example.id}"
network_interface: "" => "<computed>"
private_ip: "" => "<computed>"
public_ip: "" => "<computed>"
+ aws_instance.example
ami: "" => "ami-383c1956"
availability_zone: "" => "<computed>"
ebs_block_device.#: "" => "<computed>"
ephemeral_block_device.#: "" => "<computed>"
instance_type: "" => "t2.micro"
key_name: "" => "<computed>"
placement_group: "" => "<computed>"
private_dns: "" => "<computed>"
private_ip: "" => "<computed>"
public_dns: "" => "<computed>"
public_ip: "" => "<computed>"
root_block_device.#: "" => "<computed>"
security_groups.#: "" => "<computed>"
source_dest_check: "" => "1"
subnet_id: "" => "subnet-7d83150a"
tags.#: "" => "1"
tags.Name: "" => "Terraform example"
tenancy: "" => "<computed>"
vpc_security_group_ids.#: "" => "<computed>"
Plan: 2 to add, 0 to change, 0 to destroy.
適用時には、Elastic IPリソースに指定されているEC2インスタンスIDへの参照を検出して、暗黙的にEC2インスタンスを先に作成します。
$ terraform apply
aws_instance.example: Creating...
ami: "" => "ami-383c1956"
availability_zone: "" => "<computed>"
ebs_block_device.#: "" => "<computed>"
ephemeral_block_device.#: "" => "<computed>"
instance_type: "" => "t2.micro"
key_name: "" => "<computed>"
placement_group: "" => "<computed>"
private_dns: "" => "<computed>"
private_ip: "" => "<computed>"
public_dns: "" => "<computed>"
public_ip: "" => "<computed>"
root_block_device.#: "" => "<computed>"
security_groups.#: "" => "<computed>"
source_dest_check: "" => "1"
subnet_id: "" => "subnet-7d83150a"
tags.#: "" => "1"
tags.Name: "" => "Terraform example"
tenancy: "" => "<computed>"
vpc_security_group_ids.#: "" => "<computed>"
aws_instance.example: Creation complete
aws_eip.ip: Creating...
allocation_id: "" => "<computed>"
association_id: "" => "<computed>"
domain: "" => "<computed>"
instance: "" => "i-2445c181"
network_interface: "" => "<computed>"
private_ip: "" => "<computed>"
public_ip: "" => "<computed>"
vpc: "" => "1"
aws_eip.ip: Creation complete
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
depends_on
パラメータを利用して明示的に依存するリソースを指定することもできます。
プロビジョニング
remote-execプロビジョナーを使って、Amazon Linuxに、Nginxをインストールして起動してみます。
PCでTerraformを実行しているので、インスタンス作成後にsshログインして、コマンドを実行できるようにconnect
を設定します。
また、sshとWebアクセス用にインバウンド22, 80ポート、yum実行用にアウトバウンドポート全てを空けたセキュリティグループを作成しています。
# example.tf" highlight="7-29,35,40-51"]
provider "aws" {
access_key = "ACCESS_KEY_HERE"
secret_key = "SECRET_KEY_HERE"
region = "ap-northeast-1"
}
resource "aws_security_group" "web-server" {
name = "web-server"
description = "Allow HTTP and SSH inbound traffic"
vpc_id = "vpc-bdd851d8"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
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" "example" {
ami = "ami-383c1956"
instance_type = "t2.micro"
subnet_id = "subnet-cda6c5ba"
vpc_security_group_ids = ["${aws_security_group.web-server.id}"]
key_name = "co-meeting"
tags {
Name = "Terraform example"
}
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ec2-user"
key_file = "~/.ssh/my-key.pem"
}
inline = [
"sudo yum -y install nginx",
"sudo service nginx start",
"sudo chkconfig nginx on"
]
}
}
適用します
$ terraform apply
aws_security_group.web-server: Refreshing state... (ID: sg-448f7044)
aws_security_group.web-server: Destroying...
aws_security_group.web-server: Destruction complete
aws_security_group.web-server: Creating...
description: "" => "Allow HTTP and SSH inbound traffic"
egress.#: "" => "1"
egress.482069346.cidr_blocks.#: "" => "1"
egress.482069346.cidr_blocks.0: "" => "0.0.0.0/0"
egress.482069346.from_port: "" => "0"
egress.482069346.protocol: "" => "-1"
egress.482069346.security_groups.#: "" => "0"
egress.482069346.self: "" => "0"
egress.482069346.to_port: "" => "0"
ingress.#: "" => "2"
ingress.2214680975.cidr_blocks.#: "" => "1"
ingress.2214680975.cidr_blocks.0: "" => "0.0.0.0/0"
ingress.2214680975.from_port: "" => "80"
ingress.2214680975.protocol: "" => "tcp"
ingress.2214680975.security_groups.#: "" => "0"
ingress.2214680975.self: "" => "0"
ingress.2214680975.to_port: "" => "80"
ingress.2541437006.cidr_blocks.#: "" => "1"
ingress.2541437006.cidr_blocks.0: "" => "0.0.0.0/0"
ingress.2541437006.from_port: "" => "22"
ingress.2541437006.protocol: "" => "tcp"
ingress.2541437006.security_groups.#: "" => "0"
ingress.2541437006.self: "" => "0"
ingress.2541437006.to_port: "" => "22"
name: "" => "web-server"
owner_id: "" => "<computed>"
vpc_id: "" => "vpc-bdd851d8"
aws_security_group.web-server: Creation complete
aws_instance.example: Creating...
ami: "" => "ami-383c1956"
availability_zone: "" => "<computed>"
ebs_block_device.#: "" => "<computed>"
ephemeral_block_device.#: "" => "<computed>"
instance_type: "" => "t2.micro"
key_name: "" => "co-meeting"
placement_group: "" => "<computed>"
private_dns: "" => "<computed>"
private_ip: "" => "<computed>"
public_dns: "" => "<computed>"
public_ip: "" => "<computed>"
root_block_device.#: "" => "<computed>"
security_groups.#: "" => "<computed>"
source_dest_check: "" => "1"
subnet_id: "" => "subnet-cda6c5ba"
tags.#: "" => "1"
tags.Name: "" => "Terraform example"
tenancy: "" => "<computed>"
vpc_security_group_ids.#: "" => "1"
vpc_security_group_ids.618137044: "" => "sg-98c2eafd"
aws_instance.example: Provisioning with 'remote-exec'...
aws_instance.example (remote-exec): Connecting to remote host via SSH...
aws_instance.example (remote-exec): Host: 52.192.72.204
aws_instance.example (remote-exec): User: ec2-user
aws_instance.example (remote-exec): Password: false
aws_instance.example (remote-exec): Private key: true
aws_instance.example (remote-exec): SSH Agent: true
...
aws_instance.example (remote-exec): Connecting to remote host via SSH...
aws_instance.example (remote-exec): Host: 52.192.72.204
aws_instance.example (remote-exec): User: ec2-user
aws_instance.example (remote-exec): Password: false
aws_instance.example (remote-exec): Private key: true
aws_instance.example (remote-exec): SSH Agent: true
aws_instance.example (remote-exec): Connected!
aws_instance.example (remote-exec): Loaded plugins: priorities, update-motd,
aws_instance.example (remote-exec): : upgrade-helper
aws_instance.example (remote-exec): Resolving Dependencies
...
aws_instance.example (remote-exec): Dependencies Resolved
aws_instance.example (remote-exec): ========================================
aws_instance.example (remote-exec): Package Arch Version
aws_instance.example (remote-exec): Repository Size
aws_instance.example (remote-exec): ========================================
aws_instance.example (remote-exec): Installing:
aws_instance.example (remote-exec): nginx x86_64 1:1.8.0-10.25.amzn1
aws_instance.example (remote-exec): amzn-main 555 k
aws_instance.example (remote-exec): Installing for dependencies:
aws_instance.example (remote-exec): GeoIP x86_64 1.4.8-1.5.amzn1
aws_instance.example (remote-exec): amzn-main 783 k
aws_instance.example (remote-exec): gd x86_64 2.0.35-11.10.amzn1
aws_instance.example (remote-exec): amzn-main 155 k
aws_instance.example (remote-exec): gperftools-libs
aws_instance.example (remote-exec): x86_64 2.0-11.5.amzn1
aws_instance.example (remote-exec): amzn-main 570 k
aws_instance.example (remote-exec): libXpm x86_64 3.5.10-2.9.amzn1
aws_instance.example (remote-exec): amzn-main 54 k
aws_instance.example (remote-exec): libunwind x86_64 1.1-10.8.amzn1
aws_instance.example (remote-exec): amzn-updates 72 k
aws_instance.example (remote-exec): Transaction Summary
aws_instance.example (remote-exec): ========================================
aws_instance.example (remote-exec): Install 1 Package (+5 Dependent packages)
...
aws_instance.example (remote-exec): Complete!
aws_instance.example (remote-exec): Starting nginx:
aws_instance.example (remote-exec): [ OK ]
aws_instance.example: Creation complete
Apply complete! Resources: 2 added, 0 changed, 1 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
割り当てられたPublic IPにブラウザからアクセスするとNginxのデフォルトページが表示されます。
プロビジョニング参考
【Terraform】remote-execを使ったリモートサーバーのプロビジョニング
How To Use Terraform with DigitalOcean
TerraformだけでAWS環境にWordPressを構築する
変数の入力について
INPUT VARIABLES
上記までの例では、AWSのアクセスキーとシークレットキーなどをexample.tfに直接設定していましたが、このような環境設定については、別ファイルやコマンド実行時の変数、環境変数から渡すことができます。
新たにvariables.tf
を作成し、variable
で変数を定義します
# variables.tf"]
variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "ap-northeast-1"
}
regionはデフォルト値を設定しているので、入力がなければap-northeast-1
が使われます
変数に対応したAWS providerの定義は以下のようになります
# example.tf"]
variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "ap-northeast-1"
}
変数の渡し方はいくつかあります
コマンドラインオプション
$ terraform apply -var 'access_key=ACCESS_KEY_HERE' -var 'secret_key=SECRET_KEY_HERE
変数ファイル terraform.tfvars
terraform.tfvarsという名前のファイルで変数をセットすることもできます。
# terraform.tfvars
access_key = "ACCESS_KEY_HERE"
secret_key = "SECRET_KEY_HERE"
terraformコマンド実行時に、カレントディレクトリのterraform.tfvarsファイルは、自動で読み込まれるので特にオプションは必要ありません。
他の名前を使いたい場合は、-var-file
オプションで指定できます。
環境変数
TF_VAR_<変数名>の環境変数は、変数として参照してくれます。
access_keyの場合は、TF_VAR_access_keyと指定します。
また、AWS ProviderのAWS_ACCESS_KEY_ID
のようにプロバイダーによっては、特定の環境変数を読み込むものもあります。