AWSでTerraformに入門

Terraform
198件のシェア(すこし話題の記事)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

OSやミドルウェアの機能検証を実施した場合など、オンデマンドで一時的な検証環境を構築できるのもクラウドサービスの醍醐味です。

検証対象のOSやミドルウェアは異なれど、検証に必要な環境はある程度共通であることが少なくなく、また費用節約のためにも検証が終わった後はきれいさっぱりとその環境を削除したいものです。

AWSでそんな使い捨ての環境を構築する場合の方法として、ファーストチョイスとなるのはAWS CloudFormationかと思います。検証環境をテンプレート化しておくことができ、またマネージメントコンソールやAWS CLIを使って簡単に環境の構築/削除ができる、使い捨て環境の構築にはぴったりのサービスです。あるいはAWS CLIAWS SDKを使って自前の環境構築スクリプトを作成するのもよいかもしれません。

AWS公式のツールを使う以外では、Terraformが有力な選択肢の1つになるかと思います。

今回は主にこれからTerraformを使ってみようという方向けに、AWS上での環境構築をネタにTerraformの基本的な使い方をご紹介したいと思います。

改めてTerraformとは

HashiCorpが手がける、インフラ構築や設定をコード(テンプレートファイル)を使って自動化するためのツールです。今回取り上げるAWSのほか、複数のプロバイダー(クラウドサービスやツール)に対応しているのが特徴の1つです。

  • Terraformが対応しているプロバイダー(※本ブログエントリ執筆時点)
    • Atlas
    • AWS
    • Azure
    • CloudFlare
    • CloudStack
    • Consul
    • DigitalOcean
    • DNSMadeEasy
    • DNSimple
    • Docker
    • Google Cloud
    • Heroku
    • Mailgun
    • OpenStack

2014年7月のリリースから約1年が経過し、本ブログエントリ執筆時点の最新バージョンはv0.62となっています。

AWSの対応状況に目を向けると、対応するAWSリソースも順調に拡充されていて、Amazon DynamoDB AWS LambdaAmazon EC2 Container Service (ECS)などへの対応が最近のリリースで追加されています。

Terraformの開発は非常に活発で、毎月、あるいはそれ以上のペースで新バージョンがリリースされていますので、今後ますますの発展に期待したいところです。

今回のゴール

今回はEC2インスタンス×1台のシンプルな環境を構築します。具体的に作成するAWSリソースは以下の通りです。

  • VPC
  • Internet Gateway
  • Subnet
  • Route Table
  • Security Group
  • EC2

前提とする環境

  • OS : OS X Yosemite 10.10.4
  • Terraform : v0.62

Terraformのインストールは、バイナリファイルを任意のディレクトリに配置するだけです。以下を参考にインストールを実施下さい。

Mac環境であればHomebrew Caskでもインストールできます。

$ brew cask install terraform

$ terraform version                                                                                                                                                                                                                                                                                                
Terraform v0.6.2

テンプレートファイルの作成

Terraformのインストールが完了したら、早速お題であるAWSの環境構築にとりかかりましょう。

まずはじめに適当なディレクトリを作成して、その中にTerraformのテンプレートファイルを作成します。テンプレートファイルの名前は任意ですが、拡張子は*.tfとします。(Terraformはこの*.tfファイルを自動的にテンプレートとして認識してくれます)。

今回テンプレートファイルはmain.tfという名前で作成します。

$ mkdir terraform-test
$ cd terraform-test
$ touch main.tf

テンプレートはHCL (HashiCorp Configuration Language)というHashiCorp製の独自言語で記述します。独自言語とはいえあくまでも設定ファイル記述用のDSLでJSONとも互換性があるので、習得コストは極めて低いと思います。警戒せずに臨みましょう(※純粋なJSONフォーマットもサポートされていますが、HCLの方が可読性が高いため基本的にはHCLを使った方がよいかと思います)。

プロバイダーの設定

テンプレートでまず必要となるのではプロバイダーの設定です。上述の通りTerraformは複数のプロバイダーに対応していますので、まず「どのプロバイダーを使うのか?」を宣言するところから始めます。

provider "aws" {
    access_key = "ACCESS_KEY_HERE"
    secret_key = "SECRET_KEY_HERE"
    region = "ap-northeast-1"
}

Terrafromのテンプレートファイルではブロック単位で設定を追加していきます。AWSを使う場合はproviderブロックでawsを指定し、ブロックの中にAWSのクレデンシャル情報(access_key, secret_key)とリージョン(region)の設定を記述します。

クレデンシャル情報の切り出し

テンプレートファイルをgit等でバージョン管理し、チームでメンテナンスする場合を想定すると、テンプレートファイルに直接クレデンシャル情報を書き込んでしまうのは危険です(git pushした瞬間にクレデンシャル情報が世界中に晒される、、などという事態に。。)。

クレデンシャル情報は環境変数、またはTerraformの変数を使ってテンプレートファイルの本体から切り出しておきましょう。

環境変数の利用

後でも触れますが、テンプレートファイルの内容に基づき実際にリソースを作成するにはterraformコマンドを実行します。以下の環境変数を設定しておくと、terraformコマンド実行時にクレデンシャル情報とリージョンが環境変数から自動的に読み込まれます。

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

この場合、providerブロックにはaccess_keysecret_keyの設定は不要となります。

provider "aws" {
    region = "ap-northeast-1"
}

なお、リージョンも環境変数AWS_DEFAULT_REGIONで設定することができます。

複数のAWSアカウントを扱う場合など、複数のクレデンシャル情報を切り替えて使いたい場合はdirenvなどでディレクトリ単位で環境変数をセットするのがよいかと思います。

Terraformの変数の利用

変数の定義

Terraformでは、変数はvariableブロックで定義します。variable "<変数名>" {}のように、空のブロックを与えて変数を宣言します。(変数への値の代入方法は後述します。)

変数の値の参照

変数の値はvar.<変数名>で参照することができます。また${}を使うと、文字列に変数の値を埋め込むことができます(変数展開)。まとめると、プロバイダーの設定は以下のように書くことができます。

variable "access_key" {}
variable "secret_key" {}

provider "aws" {
    access_key = "${var.access_key}"
    secret_key = "${var.secret_key}"
    region = "ap-northeast-1"
}

${}はTerraformでは頻出のシンタックスですので、覚えておきましょう。

変数への値の代入

宣言した変数には必ず値を代入しないといけません(宣言だけして値を代入しないと、terraformコマンド実行時にエラーとなります。)。

変数への値の代入方法は3通りあります。

(1) Terraformコマンドのオプションで値を渡す

terraformコマンドのオプションで-var '<変数名>=<値>'と指定することで変数に値を代入することができます。

$ terraform apply \
-var 'access_key=AKIAXXXXXXXXXXXXXXXXXX' \
-var 'secret_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

お手軽な方法ですね。

(2) 環境変数で値を渡す

TF_VAR_<変数名>という名前の環境変数を設定しておくと、terraformコマンド実行時に自動的に設定した環境変数が読みこまれ、変数に値が代入されます。

$ export TF_VAR_access_key="AKIAXXXXXXXXXXXXXXXXXX"
$ export TF_VAR_secret_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

(3) ファイルで値を渡す

terraform.tfvarsという名前でファイルを作成しその中に<変数名> = "<値>"を書いておく方法もあります。環境変数の場合と同様に、terraformコマンド実行時に自動でこのファイルが読みこまれ、変数に値が代入されます。

aws_access_key = "AKIAXXXXXXXXXXXXXXXXXX"
aws_secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

terraform.tfvars以外のファイル名を使うこともできます。その場合はterraformコマンドのオプション-var-fileでファイル名を指定します。

Terraformの公式ドキュメントでは、このterraform.tfvarsを使う方法が推奨となっています(terraform.tfvarsにクレデンシャル情報を書く場合は、同ファイルをバージョン管理の対象外としておきましょう。)。

変数のデフォルト値

変数にはデフォルト値を設定することができます。例としてリージョンの設定を変数化してみます。

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

provider "aws" {
    access_key = "${var.aws_access_key}"
    secret_key = "${var.aws_secret_key}"
    region = "${var.region}"
}

AWSプロバイダーにはaccess_keysecret_keyregion以外にも設定可能な項目がいくつかあります。詳しくは以下を参照下さい。

長くなりましたが、ここまででプロバイダーの設定は完了です。続いてAWSリソースの設定を追加していきましょう。

リソースの設定

リソースはresourceブロックで設定します。resource "<リースの種類>" "<リソース名>" {}という構文です。

リソースの種類は、プロバイダーがAWSの場合はaws_*という名前でTerraformで予め定義されています。VPCであればaws_vpc、EC2であればaws_instanceです。

リソース名は任意の名前を設定可能です。各リソースの用途などを識別できるような分かりやすい名前を付けておきましょう。

ブロックの中には各AWSリソースの設定値を設定項目名 = 設定値の形式で記述します。具体例として、VPCの設定を見てみましょう。

resource "aws_vpc" "myVPC" {
    cidr_block = "10.1.0.0/16"
    instance_tenancy = "default"
    enable_dns_support = "true"
    enable_dns_hostnames = "false"
    tags {
      Name = "myVPC"
    }
}

VPCなので、リソースの種類はaws_vpcを指定します。リソース名はmyVPCという名前を付けています。

ブロックの中の設定項目は、リソースがVPCなのでcidr_blockinstance_tenancy等を記述しています。これがEC2の場合は、amiinstance_typeといった設定項目を記述する形となります。

各AWSリソースで設定が必要(または設定が可能)な項目については、以下より各リソース毎のArgument Referenceをご参照下さい。

他リソースの属性の参照

VPCに続いて、Internet Gatewayの設定を追加します。

Internet Gatewayの属性で設定が必須なのはvpc_idです。Internet Gatewayを既存のVPCにアタッチする場合は、vpc-92xxxxxxのように実際のVPC IDを指定すればOKですが、今回は同じテンプレートの中でVPCも新規に作成するので、そうもいきません(VPCが作成されていないので、当然VPC IDも生成されていません。)。

そんな時は、Terraformにはテンプレート内の他リソースの属性を参照する方法がありますので、これを使います。具体的には、<リソースの種類>.<リソース名>.<属性名>で他リソースの属性を参照することができます。

resource "aws_vpc" "myVPC" {
    cidr_block = "10.1.0.0/16"
    instance_tenancy = "default"
    enable_dns_support = "true"
    enable_dns_hostnames = "false"
    tags {
      Name = "myVPC"
    }
}

resource "aws_internet_gateway" "myGW" {
    vpc_id = "${aws_vpc.myVPC.id}" # myVPCのid属性を参照
}

vpc_id = "${aws_vpc.myVPC.id}"の部分に注目です。

"aws_vpc.myVPC.idで、myVPCid属性を参照しています。これを"${}"で変数展開した上でvpc_idに設定しています。

各AWSリソースで参照が可能な属性については、以下より各リソース毎のAttributes Referenceをご参照下さい。

リソース間の依存関係

他リソースの属性を参照する場合、参照先のリソースが参照元のリソースより先に作成されている必要があります。Terrafromではこのようなリソース間の依存関係を自動的に解決してくれるので、基本的には依存関係を明示する必要はありません。

ですが、depends_onを使って依存関係を明示することも可能です。

先ほどのVPCとInternet Gatewayの間に依存関係を設定してみましょう。

resource "aws_vpc" "myVPC" {
    cidr_block = "10.1.0.0/16"
    instance_tenancy = "default"
    enable_dns_support = "true"
    enable_dns_hostnames = "false"
    tags {
      Name = "myVPC"
    }
}

resource "aws_internet_gateway" "myGW" {
    vpc_id = "${aws_vpc.myVPC.id}"
    depends_on = "${aws_vpc.myVPC}" # myVPCに依存することを明示。
}

Mapの利用

上で変数のデフォルト値を設定する方法について触れましたが、デフォルト値は単一の値ではなくMapを指定することも可能です。Terraformの公式ドキュメントでは、このMapの典型的なユースケースとしてリージョンとAMIをマッピングする例が挙げられています。

variable "images" {
    default = {
        us-east-1 = "ami-1ecae776"
        us-west-2 = "ami-e7527ed7"
        us-west-1 = "ami-d114f295"
        eu-west-1 = "ami-a10897d6"
        eu-central-1 = "ami-a8221fb5"
        ap-southeast-1 = "ami-68d8e93a"
        ap-southeast-2 = "ami-fd9cecc7"
        ap-northeast-1 = "ami-cbf90ecb"
        sa-east-1 = "ami-b52890a8"
    }
}

このように定義した変数の値は、var.images.ap-northeast-1のようにして参照することが出来ます。

アウトプット

EC2インスタンスのパブリックIPなど、環境を構築した結果リソースに割り当てられた属性値を知りたい場合があります。そんな時に役立つのがoutputです。

output "<アウトプットする属性の説明>" { value = "<アウトプットする属性値>"}のように書いておくと、terraformコマンド実行時に指定した属性値がコンソール上に出力されます。

output "public ip of cm-test" {
  value = "${aws_instance.cm-test.public_ip}"
}

実際の出力結果については後述します。

ここまでのまとめ

ここまでの内容で、基本的なテンプレートの作成は可能かと思います。VPCとInternet Gatewayは設定済みですので、残りのリソース(Subnet、Route Table、Security Group、EC2)を一気に設定しましょう。

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

variable "images" {
    default = {
        us-east-1 = "ami-1ecae776"
        us-west-2 = "ami-e7527ed7"
        us-west-1 = "ami-d114f295"
        eu-west-1 = "ami-a10897d6"
        eu-central-1 = "ami-a8221fb5"
        ap-southeast-1 = "ami-68d8e93a"
        ap-southeast-2 = "ami-fd9cecc7"
        ap-northeast-1 = "ami-cbf90ecb"
        sa-east-1 = "ami-b52890a8"
    }
}

provider "aws" {
    access_key = "${var.aws_access_key}"
    secret_key = "${var.aws_secret_key}"
    region = "${var.region}"
}

resource "aws_vpc" "myVPC" {
    cidr_block = "10.1.0.0/16"
    instance_tenancy = "default"
    enable_dns_support = "true"
    enable_dns_hostnames = "false"
    tags {
      Name = "myVPC"
    }
}

resource "aws_internet_gateway" "myGW" {
    vpc_id = "${aws_vpc.myVPC.id}"
}

resource "aws_subnet" "public-a" {
    vpc_id = "${aws_vpc.myVPC.id}"
    cidr_block = "10.1.1.0/24"
    availability_zone = "ap-northeast-1a"
}

resource "aws_route_table" "public-route" {
    vpc_id = "${aws_vpc.myVPC.id}"
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.myGW.id}"
    }
}

resource "aws_route_table_association" "puclic-a" {
    subnet_id = "${aws_subnet.public-a.id}"
    route_table_id = "${aws_route_table.public-route.id}"
}

resource "aws_security_group" "admin" {
    name = "admin"
    description = "Allow SSH inbound traffic"
    vpc_id = "${aws_vpc.myVPC.id}"
    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" "cm-test" {
    ami = "${var.images.ap-northeast-1}"
    instance_type = "t2.micro"
    key_name = "cm-yawata.yutaka"
    vpc_security_group_ids = [
      "${aws_security_group.admin.id}"
    ]
    subnet_id = "${aws_subnet.public-a.id}"
    associate_public_ip_address = "true"
    root_block_device = {
      volume_type = "gp2"
      volume_size = "20"
    }
    ebs_block_device = {
      device_name = "/dev/sdf"
      volume_type = "gp2"
      volume_size = "100"
    }
    tags {
        Name = "cm-test"
    }
}

output "public ip of cm-test" {
  value = "${aws_instance.cm-test.public_ip}"
}

Dry-Run

テンプレートがひと通り書き上がったので、実際にAWS上に環境を構築してみましょう。がその前に、作成したテンプレートに誤りが無いかと、テンプレートを適用した結果意図したリソースが作成されるか(実行計画)を確認してみましょう。実行計画を確認するコマンドはterraform planです。

$ terraform plan
Refreshing Terraform state prior to plan...

...

+ aws_instance.cm-test
    ami:                                               "" => "ami-cbf90ecb"
    associate_public_ip_address:                       "" => "1"
    availability_zone:                                 "" => "<computed>"
    ebs_block_device.#:                                "" => "1"
    ebs_block_device.2659407853.delete_on_termination: "" => "1"
    ebs_block_device.2659407853.device_name:           "" => "/dev/sdf"
    ebs_block_device.2659407853.encrypted:             "" => "<computed>"
    ebs_block_device.2659407853.iops:                  "" => "<computed>"
    ebs_block_device.2659407853.snapshot_id:           "" => "<computed>"
    ebs_block_device.2659407853.volume_size:           "" => "100"
    ebs_block_device.2659407853.volume_type:           "" => "gp2"
    ephemeral_block_device.#:                          "" => "<computed>"
    instance_type:                                     "" => "t2.micro"
    key_name:                                          "" => "cm-yawata.yutaka"

...

+ aws_internet_gateway.myGW
    vpc_id: "" => "${aws_vpc.myVPC.id}"

...

Plan: 7 to add, 0 to change, 0 to destroy.

リソースと属性の一覧が表示されます。「+」マークが付いているのが新規に作成されるリソースです。また、最終行に作成(add)、変更(change)、削除(destroy)されるリソース数が表示されます。この時点ではまだリソースを作成していないので、7 to addと表示され、chnagedestroy0になっています。

terraform planでは構文エラーや、ブロックに設定したパラメータ誤りについてはチェックしてくれますが、パラメータの値の正しさまではチェックしてくれません。例えば、存在しないVPC IDや、AMIに対して選択不可なインスタンスタイプを指定してもterraform plan上はエラーにならず、後述するterraform apply実行時に始めてエラーと分かるので注意が必要です。

terraform planを実行すると、リソースの状態を表すterraform.tfstateという名前のJSONファイルが生成されます(リソース作成前なので、ファイルの内容はほぼ空の状態です。)。

{
    "version": 1,
    "serial": 0,
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {}
        }
    ]
}

リソースの作成(テンプレートの適用)

それでは実際にテンプレートを適用してAWS上にリソースを作成してみましょう。実行するコマンドはterraform applyです。

$ terraform apply
aws_vpc.myVPC: Creating...
  cidr_block:                "" => "10.1.0.0/16"
  default_network_acl_id:    "" => "<computed>"
  default_security_group_id: "" => "<computed>"
  dhcp_options_id:           "" => "<computed>"

...

Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

...

Outputs:

  public ip of cm-test = 52.69.227.18

Apply complete!と表示されれば成功です。実行結果の最後にoutputで設定した内容(EC2インスタンスのPublic IP)が表示されています。

terraform applyを実行したので、terraform.tfstateの内容も更新されます。ファイルを直接参照してもよいですが、terraform showコマンドを実行するとファイルの内容を見やすい形に整形して表示してくれます。

$ terraform show
aws_instance.cm-test:
  id = i-e9860e1b
  ami = ami-cbf90ecb
  associate_public_ip_address = true
  availability_zone = ap-northeast-1a

...

Outputs:

public ip of cm-test = 52.68.24.133

また、特定のoutputのみを参照したい場合はterraform outputコマンドを使います。

terraform output "public ip of cm-test"
52.68.24.133

リソースの変更

作成したリソースの属性を変更してみましょう。Security GroupのInbound通信でHTTP(80)の許可を追加し、EC2のインスタンスタイプをm3.mediumに変更してみます。

...

resource "aws_security_group" "admin" {
  name = "admin"
  description = "Allow SSH inbound traffic"
  vpc_id = "${aws_vpc.myVPC.id}"
  ingress {
      from_port = 22
      to_port = 22
      protocol = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
  }

  # HTTP(80)のInboud通信を許可
  ingress {
      from_port = 80
      to_port = 80
      protocol = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
  }

...

resource "aws_instance" "cm-test" {
    ami = "${var.images.ap-northeast-1}"
    # インスタンスタイプをm3.mediumに変更
    instance_type = "m3.medium"

...

terraform planで実行計画を確認します。

$ terraform plan
-/+ aws_instance.cm-test
    ami:                                               "ami-cbf90ecb" => "ami-cbf90ecb"
    associate_public_ip_address:                       "true" => "1"
    availability_zone:                                 "ap-northeast-1a" => "<computed>"
    ebs_block_device.#:                                "1" => "1"
    ebs_block_device.2659407853.delete_on_termination: "true" => "1"
    ebs_block_device.2659407853.device_name:           "/dev/sdf" => "/dev/sdf"
    ebs_block_device.2659407853.encrypted:             "false" => "<computed>"
    ebs_block_device.2659407853.iops:                  "300" => "<computed>"
    ebs_block_device.2659407853.snapshot_id:           "" => "<computed>"
    ebs_block_device.2659407853.volume_size:           "100" => "100"
    ebs_block_device.2659407853.volume_type:           "gp2" => "gp2"
    ephemeral_block_device.#:                          "0" => "<computed>"
    instance_type:                                     "t2.micro" => "m3.medium" (forces new resource)

 ...

~ aws_security_group.admin
    ingress.#:                            "1" => "2"
    ingress.2214680975.cidr_blocks.#:     "0" => "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" => "0"
    ingress.2214680975.self:              "" => "0"
    ingress.2214680975.to_port:           "" => "80"

...

変更されるリソースはテンプレートファイルと前述のterraform.tfstateとの差分からが割り出されます。

EC2(aws_instance.cm-test)には「-/+」マークが付いています。これは属性の変更にともない、リソースが削除&再作成されることを示しています。

一方でSecurity Group(aws_security_group.admin)には「~」マークが付いています。これは削除&再作成ではなく、純粋にリソースの設定変更(in-place update)が行われることを示しています。

リソースの変更を行う場合は必ずterraform planで事前に実行計画を確認しておきましょう。リソースの変更を行うコマンドは、作成と同じくterraform applyです。

リソースの削除

terraform destroyでテンプレートにあるリソース一式を削除することができます。リソース削除の実行計画はterraform plan -destroyで知ることができます。

$ terraform plan -destroy

...

- aws_instance.cm-test

- aws_internet_gateway.myGW

- aws_route_table.public-route

- aws_route_table_association.puclic-a

- aws_security_group.admin

- aws_subnet.public-a

- aws_vpc.myVPC


Plan: 0 to add, 0 to change, 7 to destroy.

テンプレートにある全てのリソースに、削除を示す「-」マークが付いています。では、実際に削除を実行してみましょう。

$ 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

...

Apply complete! Resources: 0 added, 0 changed, 7 destroyed.

実行確認のメッセージが表示されるので、yesを返すと削除が実行されます。

テンプレートファイルの分割

これまで各種設定は1つのテンプレートファイル(main.tf)にまとめて書いてきましたが、冒頭で触れた通りTerraformは拡張子が*.tfのファイルを自動的にテンプレートとして認識してくれるので、テンプレートファイルを複数に分割することも可能です。

Basic Two-Tier AWS Architectureの例を見ると、main.tfから変数の設定をvariables.tf、アウトプットの設定をoutputs.tfに切り出していることが伺えます。これに倣うと今回作成したテンプレートは以下のように3ファイルに分割することが出来そうです。

# main.tf
provider "aws" {
    access_key = "${var.aws_access_key}"
    secret_key = "${var.aws_secret_key}"
    region = "${var.region}"
}

## リソースの定義を記述
resource "aws_vpc" "myVPC" {
    cidr_block = "10.1.0.0/16"
    instance_tenancy = "default"
    enable_dns_support = "true"
    enable_dns_hostnames = "false"
    tags {
      Name = "myVPC"
    }
}

...
# variables.tf
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "region" {
    default = "ap-northeast-1"
}

variable "images" {
    default = {
        us-east-1 = "ami-1ecae776"
        us-west-2 = "ami-e7527ed7"
        us-west-1 = "ami-d114f295"
        eu-west-1 = "ami-a10897d6"
        eu-central-1 = "ami-a8221fb5"
        ap-southeast-1 = "ami-68d8e93a"
        ap-southeast-2 = "ami-fd9cecc7"
        ap-northeast-1 = "ami-cbf90ecb"
        sa-east-1 = "ami-b52890a8"
    }
}
# outputs.tf
output "public ip of cm-test" {
  value = "${aws_instance.cm-test.public_ip}"
}

その他、リソース数が多い場合はリソースの種類毎にテンプレートファイルを分割するといったやり方も効果的かもしれません。

まとめ

コマンド一撃でインフラ環境の構築ができるのは気持ちがいいものです。そして作成したリソースを残さず削除できる安心感。使い捨ての検証環境を構築する場面では積極的にTerraformを使っていきたいと思っています。

AWS観点では、対応するAWSリソースという意味ではCloudFormationに見劣りしますが、Dry-Runが出来るなどCloudFormationには無い機能を備えています。使用しているAWSリソースがTerraformによってカバーされているのであれば、CloudFormationの代替としても十分に機能するのではないでしょうか(と、入門したての今は思っています。細かくリソース毎の出来る/出来ないを確認していくと見解が変わるかもしれません。)。

Terraformには今回ご紹介した基本的な機能の他にも、各種組み込みの関数やChef等のプロビジョニングツールの連携などの便利機能が備わっていますので、それらはまた別の機会にご紹介したいと思っています。