Terraformを使ってみる

概要

役割と期待すること

TerrafomとはHashicorpが開発したソフトウェアで、役割はインフラの構築・変更を定義ファイルベースで行うことです。
構築作業を定義ファイルベースで行うことで、次のことが期待できます。

自動化構築作業の自動化と複数回の実行
可視化構築定義・変更定義の可視化
版管理各種定義の版管理

定義を作成により実行を自動化するという流れや、それによって受けられる恩恵が単体テストの自動化と似ていますね。また実行に関しても最適化されており依存性のない作業同士は並列で実行されるという非機能面での恩恵もあります。

対応する環境

2016年10月現在では次のサービスに対応しています。色々とあり過ぎておなかが減ってきますね。今回は公式サイトのGetting Startedの流れに倣ってAWS環境を利用してゆきます。

Archive Atlas AWS Bitbucket Bitbucket
Chef CenturyLinkCloud CloudFlare CloudStack Cobbler
Consul Datadog DigitalOcean DNSMadeEasy DNSimple
Docker Google Cloud Dyn GitHub Fastly
Grafana Heroku InfluxDB Librato Logentries
Mailgun Microsoft Azure Microsoft Azure(Legacy ASM)
MySQL OpenStack Packet PostgreSQL PowerDNS
RabbitMQ Random Rundeck StatusCake SoftLayer
Scaleway Template Terraform TLS Triton
UltraDNS VMware vCloud Director VMware vSphere

公式サイト

こちらが公式サイトになります。詳細情報や関連ソフトウェアの情報が掲載されていますので確認してみてください。
Terraform by HashiCorp

インストール

さて実際にインストールから実行してゆきます。公式サイトより適切なパッケージ(下記のOSに対応)をダウンロードしてください。現時点での最新版は0.7.7となります。

www.terraform.io

ダウンロードが完了したら環境に応じた方法でPATHを通してください。(僕は面倒なので /usr/local/sbinに実行ファイルを入れました) 終わったら正常にインストール出来たかterraform -vで確認してみましょう。

$ terraform -v
Terraform v0.7.7

定義ファイルの作成

環境が出来たら定義ファイルを作成してゆきます。正式名称は Terraform Configurationとなります。

定義ファイルの形式

形式は独自の Terraform Format(*.tf)または JSON(*.tf.json) の何れかを任意で選択します。公式では人間の可視性の観点から前者の利用を推奨しています。後者はプログラム等による出力を想定して用意したものの様です。個人的にはJSONでも充分見やすいと思います。

ファイルの定義

では実際に定義ファイルを作成します。定義ファイルの内容は、対応するサービス種別(provider)と、内部資源(resource)の 2部で構成されます。なお今回はサンプルなので providerの認証情報も内部定義していますが、版管理の観点から外部ファイルに切り出すことも可能です。

provider "aws" {
  access_key = "(自身のAWS環境のアクセスキーを設定)"
  secret_key = "(自身のAWS環境のシークレットキーを設定)"
  region     = "us-east-1"
}

resource "aws_instance" "example" {
  ami           = "ami-0d729a60"
  instance_type = "t2.micro"
}

計画

terraform planで実行計画を確認することができます。実環境は変更しない点で後述のterraform applyとは異なります。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but
will not be persisted to local or remote state storage.


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. Cyan entries are data sources to be read.

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-0d729a60"
    availability_zone:        "<computed>"
    ebs_block_device.#:       "<computed>"
    ephemeral_block_device.#: "<computed>"
    instance_state:           "<computed>"
    instance_type:            "t2.micro"
    key_name:                 "<computed>"
    network_interface_id:     "<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:        "true"
    subnet_id:                "<computed>"
    tenancy:                  "<computed>"
    vpc_security_group_ids.#: "<computed>"


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

実行

terraform applyで実行をかけます

$ terraform apply
aws_instance.example: Creating...
  ami:                      "" => "ami-0d729a60"
  availability_zone:        "" => "<computed>"
  ebs_block_device.#:       "" => "<computed>"
  ephemeral_block_device.#: "" => "<computed>"
  instance_state:           "" => "<computed>"
  instance_type:            "" => "t2.micro"
  key_name:                 "" => "<computed>"
  network_interface_id:     "" => "<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:        "" => "true"
  subnet_id:                "" => "<computed>"
  tenancy:                  "" => "<computed>"
  vpc_security_group_ids.#: "" => "<computed>"
aws_instance.example: Still creating... (10s elapsed)
aws_instance.example: Still creating... (20s elapsed)
aws_instance.example: Still creating... (30s elapsed)
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

実際に作成されたかをAWSの管理コンソールで確認してみます。リージョンを"バージニア北部"に設定し、サービスからEC2を選択します。
どうやら無事動いている様です。
f:id:kooyoo:20161023075313p:plain:w700

尚 AccessKey/SecretAccessKey のオーナーとなるAWS上のIAMユーザにEC2へのアクセス権が無いと以下の様にエラーとなります。

Error applying plan:

1 error(s) occurred:

* aws_instance.example: Error launching source instance: UnauthorizedOperation: 
  You are not authorized to perform this operation. 
  Encoded authorization failure message: DyxG53T.....
	status code: 403, request id: 81414ade-9598-403f-af9a-5d264bff26a3

Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.

この場合、AWSのマネージメントコンソール上で サービスからIAM (Identity and Management Service)を選択し、該当ユーザに適切な権限を与えて下さい。(下の画面では暫定でEC2のフルアクセスを与えています)
f:id:kooyoo:20161023080746p:plain:w500

結果の確認

先にAWSの管理コンソールから結果を確認しましたが、実は Terraformのコマンドからも結果の確認が可能です。
terraform showで以下の様な表示が出ます。

aws_instance.example:
  id = i-xxxxxxxx
  ami = ami-xxxxxxxx
  availability_zone = us-east-1b
  disable_api_termination = false
  ebs_block_device.# = 0
  ebs_optimized = false
  ephemeral_block_device.# = 0
  iam_instance_profile = 
  instance_state = running
  instance_type = t2.micro
  key_name = 
  monitoring = false
  network_interface_id = eni-xxxxxxxx
  private_dns = ip-xxx-xx-xx-xxx.ec2.internal
  private_ip = xxx.xxx.xxx.xxx
  public_dns = ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com
  public_ip = xxx.xxx.xxxx.xxx
  root_block_device.# = 1
  root_block_device.0.delete_on_termination = true
  root_block_device.0.iops = 0
  root_block_device.0.volume_size = 8
  root_block_device.0.volume_type = standard
  security_groups.# = 0
  source_dest_check = true
  subnet_id = subnet-xxxxxxxx
  tags.% = 0
  tenancy = default
  vpc_security_group_ids.# = 1
  vpc_security_group_ids.xxxxxxxxxx = sg-xxxxxxxx

尚、この情報は実行時に作成された terraform.tfstateというファイルが入力元となっています。

$ ls
example.tf	terraform.tfstate	terraform.tfstate.backup