$ brew install terraform
$ terraform -v
Terraform v0.9.11
Terraformの設定要素
provider
IaaS(e.g. AWS)、PaaS(e.g. Heroku)、SaaS(e.g. CloudFlare)など。
AWS Providerはこんな感じ。 ここに直接access_keyやsecret_keyを書くこともできるが、誤って公開されてしまわないように環境変数か variableで渡す。
provider "aws" {
# access_key = "${var.access_key}"
# secret_key = "${var.secret_key}"
region = "us-east-1"
}
$ export AWS_ACCESS_KEY_ID="anaccesskey"
$ export AWS_SECRET_ACCESS_KEY="asecretkey"
varibale
CLIでオーバーライドできるパラメーター。typeにはstringのほかにmapやlistを渡すことができ、 何も渡さないとdefault値のものが、それもなければstringになる。
variable "key" {
type = "string"
default = "value"
description = "description"
}
値を渡す方法はTF_VAR_をprefixとする環境変数、-var、-var-fileがある。 また、moduleのinputとして渡されることもある。
$ export TF_VAR_somelist='["ami-abc123", "ami-bcd234"]'
$ terraform apply -var foo=bar -var foo=baz
$ terraform apply -var-file=foo.tfvars -var-file=bar.tfvars
$ cat foo.tfvars
foo = "bar"
xyz = "abc"
somelist = [
"one",
"two",
]
somemap = {
foo = "bar"
bax = "qux"
}
output
variableがinputなのに対して、こちらはoutput。
output "ip" {
value = "${aws_eip.ip.public_ip}"
}
実行した後に取得できる。
$ terraform apply
...
$ terraform output ip
50.17.232.209
resource
物理サーバーやVMのような低レベルのものからDNSレコードのような高レベルのものまで含むインフラのコンポーネント。
resource "aws_instance" "web" {
ami = "ami-408c7f28"
instance_type = "t1.micro"
}
provisioner
デフォルトでは作成されたときに実行されるコマンド。when = “destroy” で終了時に実行させることもできる。 on_failureで失敗したときの挙動を設定することができ、デフォルトはコマンド自体が失敗する"fail"になっている。
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo ${self.private_ip_address} > file.txt"
}
}
data
情報を取得する。Terraform以外で作られたリソースのものも取れる。
data "aws_ami" "web" {
filter {
name = "state"
values = ["available"]
}
filter {
name = "tag:Component"
values = ["web"]
}
most_recent = true
}
module
設定をまとめたもの。variableの値を渡すことができ、再利用することができる。 GitHubのurlをsourceに指定することもできる。最初に terraform get する必要がある。
module "assets_bucket" {
source = "./publish_bucket"
name = "assets"
}
module "media_bucket" {
source = "./publish_bucket"
name = "media"
}
# publish_bucket/bucket-and-cloudfront.tf
variable "name" {} # this is the input parameter of the module
...
backend
0.9.0から terraform remote の代わりに使われるようになったもの。 管理下のresourceと今の状態を表すtfstateファイルを各自のローカルではなくリモートで一元的に管理する。 オプションではあるが、applyしたあとにtfstateを上げるのを忘れたりするのを防ぐこともできるため 相当変わった用途でもない限り使わない理由がないと思う。最初に terraform init する必要がある。
S3に置く場合はこんな感じ。 DynamoDBでロックをかけられる。
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
dynamodb_table = "tflocktable"
}
}
VPCのmoduleを作る
コードはここ。
community-moduleにもVPCのモジュールがあるんだが、今回は自分で作ってみる。
variableはこんな感じ。同じファイルに書くこともできるが別に分けた方が見やすい。
variable "vpc_name" {
description = "vpc's name, e.g. main-vpc"
}
variable "vpc_cidr_block" {
description = "vpc's cidr block, e.g. 10.0.0.0/16"
}
variable "public_subnet_cidr_blocks" {
type = "list"
description = "public subnets' cidr blocks, e.g. [\"10.0.0.0/24\", \"10.0.1.0/24\"]"
}
variable "public_subnet_availability_zones" {
type = "list"
description = "public subnets' cidr blocks, e.g. [\"ap-northeast-1a\",\"ap-northeast-1c\"]"
}
variable "private_subnet_cidr_blocks" {
type = "list"
description = "private subnets' cidr blocks, e.g. [\"10.0.2.0/24\", \"10.0.3.0/24\"]"
}
variable "private_subnet_availability_zones" {
type = "list"
description = "public subnets' cidr blocks, e.g. [\"ap-northeast-1a\",\"ap-northeast-1c\"]"
}
まずVPCを作成する。
resource "aws_vpc" "vpc" {
cidr_block = "${var.vpc_cidr_block}"
enable_dns_hostnames = true
tags {
Name = "${var.vpc_name}"
}
}
次にVPCにpublicとprivate用のサブネットを、 それぞれcidr_block分作成する。
vpc_idでvpcのresourceを参照している。したがって、これを実行するためには既にvpcが作られている必要がある。 depends_onで明示的に依存関係を示すこともできるのだが、 大抵はそうする必要がなくて暗黙的な依存関係をterraformが解決してくれる。 これはterraform graphで確認できる。
AZで使われているelement(list, index) は要素数以上のindexを渡してもmodの要領で選ぶので数を合わせなくてもよい。
複数作ったものは aws_subnet.public-subnet.0 のように0から始まるindexで参照でき、aws_subnet.public-subnet.*.id のようにすると要素のリストを得られる。
resource "aws_subnet" "public-subnet" {
vpc_id = "${aws_vpc.vpc.id}"
count = "${length(var.public_subnet_cidr_blocks)}"
cidr_block = "${var.public_subnet_cidr_blocks[count.index]}"
availability_zone = "${element(var.public_subnet_availability_zones, count.index)}"
map_public_ip_on_launch = true
tags {
Name = "${var.vpc_name}-public-${element(var.public_subnet_availability_zones, count.index)}"
}
}
resource "aws_subnet" "private-subnet" {
vpc_id = "${aws_vpc.vpc.id}"
count = "${length(var.private_subnet_cidr_blocks)}"
cidr_block = "${var.private_subnet_cidr_blocks[count.index]}"
availability_zone = "${element(var.private_subnet_availability_zones, count.index)}"
tags {
Name = "${var.vpc_name}-private-${element(var.private_subnet_availability_zones, count.index)}"
}
}
Private IPアドレスとPublic IPアドレスを1:1でStatic NATするインターネットゲートウェイ をVPCにアタッチし、これを登録したカスタムルートテーブル をpublic用のサブネットに関連付ける。 これによりPublic IPを割り当てたインスタンスとインターネットが相互に接続できるようになる。
Public IPを割り当てるとコンソール上ではインスタンス自体にIPが紐づいているように見えるが、これはStatic NATのマッピング先で、 ifconfig 等には表示されない。
resource "aws_internet_gateway" "igw" {
vpc_id = "${aws_vpc.vpc.id}"
tags {
Name = "${var.vpc_name}-igw"
}
}
resource "aws_route_table" "public-route-table" {
vpc_id = "${aws_vpc.vpc.id}"
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.igw.id}"
}
tags {
Name = "${var.vpc_name}-public-route-table"
}
}
resource "aws_route_table_association" "route-table-association" {
count = "${length(var.public_subnet_cidr_blocks)}"
subnet_id = "${element(aws_subnet.public-subnet.*.id, count.index)}"
route_table_id = "${aws_route_table.public-route-table.id}"
}
privateのサブネットからはN:1のDynamic NATの後インターネットゲートウェイを通って外に出られるようにする。外から中には入れない。 publicなサブネットにそれ用のインスタンスを立ててもいいが、NATゲートウェイを使うと自分でメンテする必要がなくて楽。 料金は時間と通信量による。
ということで、EIPを割り当て、 適当なpublicのサブネットにNATゲートウェイを作成する。 ドキュメントに書いてある通り、明示的にigwを依存に入れている。
NATゲートウェイをメインルートテーブルに登録する。 これはAWSのドキュメントに書いてある通りの構成で、 明示的にルートテーブルと関連付けていないサブネットは メインルートテーブルに 関連付けられる。
resource "aws_eip" "nat" {
vpc = true
}
resource "aws_nat_gateway" "ngw" {
allocation_id = "${aws_eip.nat.id}"
subnet_id = "${aws_subnet.public-subnet.0.id}"
depends_on = ["aws_internet_gateway.igw"]
}
resource "aws_route_table" "main-route-table" {
vpc_id = "${aws_vpc.vpc.id}"
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = "${aws_nat_gateway.ngw.id}"
}
tags {
Name = "${var.vpc_name}-main-route-table"
}
}
resource "aws_main_route_table_association" "main-route-table-association" {
vpc_id = "${aws_vpc.vpc.id}"
route_table_id = "${aws_route_table.main-route-table.id}"
}
実行
terraform {
backend "s3" {
bucket = "terraform"
key = "terraform.tfstate"
region = "ap-northeast-1"
}
}
provider "aws" {
region = "ap-northeast-1"
}
module "test-vpc" {
source = "./vpc"
vpc_name = "test-vpc"
vpc_cidr_block = "10.0.0.0/16"
public_subnet_cidr_blocks = ["10.0.0.0/24", "10.0.1.0/24"]
public_subnet_availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
private_subnet_cidr_blocks = ["10.0.2.0/24", "10.0.3.0/24"]
private_subnet_availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
}
planして問題なければapplyする流れ。
$ terraform init
$ terraform get
$ terraform plan
+ module.test-vpc.aws_eip.nat
allocation_id: "<computed>"
association_id: "<computed>"
domain: "<computed>"
instance: "<computed>"
network_interface: "<computed>"
private_ip: "<computed>"
public_ip: "<computed>"
vpc: "true"
+ module.test-vpc.aws_internet_gateway.igw
tags.%: "1"
tags.Name: "test-vpc-igw"
vpc_id: "${aws_vpc.vpc.id}"
...
Plan: 13 to add, 0 to change, 0 to destroy.
$ terraform apply
...
Apply complete! Resources: 13 added, 0 changed, 0 destroyed.
applyするとresourceが作成・更新され、tfstateファイルがbackendまたはローカルに出力される。 次回以降はこのtfstateとの差分を取って変更されるので、このファイルがないとまた同じものが作成されてしまう。
{
"version": 3,
"terraform_version": "0.9.11",
"serial": 1,
"lineage": "f97ad997-5a19-4a3d-9921-b553c5f2532b",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {},
"depends_on": []
},
{
"path": [
"root",
"test-vpc"
],
"outputs": {},
"resources": {
"aws_eip.nat": {
"type": "aws_eip",
"depends_on": [],
"primary": {
"id": "eipalloc-3046f054",
"attributes": {
"association_id": "",
"domain": "vpc",
"id": "eipalloc-3046f054",
"instance": "",
"network_interface": "",
"private_ip": "",
"public_ip": "13.114.59.186",
"vpc": "true"
},
"meta": {},
"tainted": false
},
"deposed": [],
"provider": ""
},
"aws_internet_gateway.igw": {
"type": "aws_internet_gateway",
"depends_on": [
"aws_vpc.vpc"
],
"primary": {
"id": "igw-d2659bb6",
"attributes": {
"id": "igw-d2659bb6",
"tags.%": "1",
"tags.Name": "test-vpc-igw",
"vpc_id": "vpc-3cf6a358"
},
"meta": {},
"tainted": false
},
"deposed": [],
"provider": ""
},
...
},
"depends_on": []
}
]
}
planすると変更なしになっている。
$ terraform plan
...
No changes. Infrastructure is up-to-date.
terafform destroy で管理下のresourceを消すことができる。
$ terraform plan -destroy
...
Plan: 0 to add, 0 to change, 13 to destroy.
$ terraform destroy
...
Destroy complete! Resources: 13 destroyed.
$ terraform plan
...
Plan: 13 to add, 0 to change, 0 to destroy.