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

Vagrantを触ってみる

Vagrantと言えば Mitchell Hashimotoさんが開発した仮想マシン環境構築ソフトです。Mitchell Hashimoto さんと言えば HashiCorpの設立者で、Packer, Serf, Consul等、色々な素晴らしいソフトを作っていますよね。

さて、その第一弾となった Vagrantですが、今まであまり使う機会が無かったので触れておきたいなと思いました。今更感は半端ないですが、それ考えると先進まないので思考停止して行ってみよー!

公式サイト

何を始めるにしても公式サイトを見るのが楽だよね。
というわけで、この記事でも公式サイトの流れを追う感じで書いていこうと思います。
www.vagrantup.com

役割

仮想マシンをファイルベースで作成

  1. 設定をコード化できる
  2. 設定を再現出来る
  3. 設定を持ち運べる

手順

1. Vagrantのインストール (公式サイト参照)

環境に応じたインストール手順でインストールしてね
僕は homebrewを使った様な気がする (覚えてない)
www.vagrantup.com

2. Vagrantファイルの作成

vagrant initVagrantファイルのテンプレートを作成してくれます

$ vagrant init hashicorp/precise64
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

終わると Vagrantファイルが作成されているのが分かります

$ ls
Vagrantfile

中身はこんな感じです

$ less Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://atlas.hashicorp.com/search.
  config.vm.box = "hashicorp/precise64"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
  # such as FTP and Heroku are also available. See the documentation at
  # https://docs.vagrantup.com/v2/push/atlas.html for more information.
  # config.push.define "atlas" do |push|
  #   push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
  # end

  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
end

3. 仮想マシンプロバイダ上にマシンの配置

vagrant upにて実施

ログを見ると色々やっているのが分かりますね

  • 指定仮想マシンのインポート
  • NATやネットワーク周りの設定
  • ポートフォワーディングの設定
  • Boxの更新確認
  • VMの起動
  • 共有フォルダのマウント
$ vagrant up
==> default: Importing base box 'hashicorp/precise64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'hashicorp/precise64' is up to date...
==> default: Setting the name of the VM: new_default_1476494705030_32930
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Remote connection disconnect. Retrying...
    default: 
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default: 
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default: 
    default: Guest Additions Version: 4.2.0
    default: VirtualBox Version: 5.0
==> default: Mounting shared folders...
    default: /vagrant => /Users/xxxx/work/study/vagrant/new

4. ログイン
vagrant sshで完成した仮想マシンにログインできます

$ vagrant ssh
Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
New release '14.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Welcome to your Vagrant-built virtual machine.
Last login: Fri Sep 14 06:23:18 2012 from 10.0.2.2
vagrant@precise64:~$ ls
postinstall.sh
vagrant@precise64:~$ pwd
/home/vagrant
vagrant@precise64:~$ exit
logout

5. SSHクライアントからログイン
sshコマンドや putty/Teratermの様なクライアントからのログインも可能です

先ずはvagrant ssh-configで接続設定を確認します

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile 
  /Users/xxx/work/study/vagrant/new/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

続けて確認した設定に基づいて Host, Port, ユーザ, 秘密鍵を指定すればログインできます

ホスト 127.0.0.1
ポート 2222
ユーザ vagrant
秘密鍵 ~/work/study/vagrant/new/.vagrant/machines/default/virtualbox/private_key
$ ssh vagrant@127.0.0.1 -p 2222 \
  -i ~/work/study/vagrant/new/.vagrant/machines/default/virtualbox/private_key
The authenticity of host '[127.0.0.1]:2222 ([127.0.0.1]:2222)' can't be established.
ECDSA key fingerprint is SHA256:+zgKqxyYlTBAs0xtTVGxxxxxS9Zr71wQGvnG/k2igw.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[127.0.0.1]:2222' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
New release '14.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Welcome to your Vagrant-built virtual machine.
Last login: Sat Oct 15 02:12:55 2016 from 10.0.2.2