Itamaeが構成管理を仕込みます! ~新進気鋭の国産・構成管理ツール~

第5回(最終回) 中規模環境でのItamae(複数ホストに対する実行)

ここまで4回、Itamaeのレシピの書き方やプラグインの作り方などを説明してきました。最終回となる今回は複数サーバに対してItamaeを利用する場合の活用方法を3つ紹介します。

Capistranoを使ってItamaeを複数ホストに対して実行する

CapistranoはRubyで書かれたデプロイツールで、アプリケーションのデプロイによく使われています。Capistranoを使ってSSH経由で複数台にItamaeを実行することで、手軽に並列でItamaeを実行することができます。ここではCapistranoのデプロイ機能を使ってレシピなどをデプロイし、デプロイ後にItamaeを実行する例を紹介します。

まず、Bundlerを使ってCapistranoをインストールし、cap installで初期ファイルを生成します。

$ cd itamae-repo
$ bundle init # Gemfileがない場合
$ echo 'gem "capistrano"' >> Gemfile
$ bundle install
$ bundle exec cap install STAGES=production

config/deploy.rbを以下のように修正します。itamae:applyタスクでitamae localが実行されるようにしています。また、itamae:applyの前にdeployを実行するように指定しているので、実行前に/tmp/itamae-repoにコードがデプロイされます。

set :application, itamae
set :repo_url, '[email protected]:your/itamae-repo.git'
set :deploy_to, '/tmp/itamae-repo'

# この部分はAWS EC2のAPIを呼ぶなどして、動的に定義すると良いかもしれません
role :app, [‘app-1’]

namespace :itamae do
  # apply前にdeployを実行します
  task :apply => [:deploy] do
    # bootstrap.rbを実行します
    recipe = File.join(fetch(:release_path), 'bootstrap.rb')
    on roles(ENV['ROLES'].split(',')) do
      # 対象ホスト側にItamaeがインストールされている必要があります
      execute "itamae", "local", recipe
    end
  end
end

itamae:applyタスクでItamaeを実行できます。

$ bundle exec cap production itamae:apply ROLES=app
(中略)
DEBUG [ed621171] Command: /usr/bin/env itamae local /tmp/itamae-repo/releases/20150927150652/bootstrap.rb
DEBUG [ed621171]        [0m INFO : Starting Itamae...
DEBUG [ed621171]        [0m
DEBUG [ed621171]        [0m INFO : Recipe: /tmp/itamae-repo/releases/20150927150652/bootstrap.rb
DEBUG [ed621171]        [0m[32m INFO :   execute[echo Hello] executed will change from 'false' to 'true'
DEBUG [ed621171]        [0m
INFO [ed621171] Finished in 0.317 seconds with exit status 0 (successful).

このように非常に簡単に複数台へのItamaeの実行が実現できます。上記の例ではitamae sshを使わずitamae localを利用しています。itamae sshitamae localに比べ速度が遅いため、ある程度の規模になったらlocalを使うのがお勧めです。itamae localは対象ホストにitamaeがインストールされている必要があるので、パッケージなどでインストールしましょう。

もちろん、itamae sshを使うことも可能です。その場合はrun_locally内でItamaeを実行しましょう。詳しくはCapistranoのドキュメントを参照してください。

PackerとItamaeを使ってAWS EC2用のイメージを作成する

PackerとItamaeを使ってAWS EC2のイメージ(AMI)を作る例を紹介します。Packerは各種VMのマシンイメージをつくるためのツールで、EC2やDigitalOceanなどのIaaS用のイメージ作成やVirtualBoxやVMwareなどの仮想マシンイメージの作成に対応しています。Packerでは任意のコマンドを実行しイメージを作成することができるので、その機能を使ってItamaeを実行します。

まず、Packerをダウンロードします。

$ wget https://dl.bintray.com/mitchellh/packer/packer_0.8.6_darwin_amd64.zip
$ unzip packer_0.8.6_darwin_amd64.zip

Packerの設定ファイルを書きます。以下をubuntu.jsonとして保存しますvpc_idsubnet_id/path/to/itamae-repoは環境に合わせて書き換えます⁠⁠。

{
  "builders": [{
    "type": "amazon-ebs",
    "region": "ap-northeast-1",
    "source_ami": "ami-46990446",
    "instance_type": "t2.micro",
    "ssh_username": "ubuntu",
    "ssh_timeout": "5m",
    "ami_name": "ubuntu-trusty-hvm",
    "vpc_id": "vpc-abcdef",
    "availability_zone": "ap-northeast-1c",
    "subnet_id": "subnet-abcdef",
    "associate_public_ip_address": true,
    "run_tags": {
      "Name": "packer-tmp-ubuntu-trusty-hvm"
    }
  }],
  "provisioners": [
    {
      "type": "file",
      "source": "/path/to/itamae-repo",
      "destination": "/tmp/itamae-repo"
    },
    {
      "type": "shell",
      "inline": [
        "curl -s https://packagecloud.io/install/repositories/ryotarai/itamae/script.deb.sh | sudo bash",
        "sudo apt-get install itamae",
        "sudo /opt/itamae/embedded/bin/itamae local /tmp/itamae-repo/bootstrap.rb",
        "sudo rm -rf /tmp/itamae-repo"
      ]
    }
  ]
}

Packerの設定ファイルにはbuildersprovisionerを指定する必要があり、ここではBuilderにamazon-ebs(AMI)を、ProvisionerにItamaeの実行コマンドを指定します。file Provisionerを使って、Itamaeのレシピなどを転送し(以下では/path/to/itamae-repoを転送しています⁠⁠、itamaeコマンドを実行しています。

あとはpacker buildコマンドに上記の設定ファイルを与えれば、全自動でインスタンスを作成し、Itamaeを実行し、イメージを作成してくれます。

$ AWS_ACCESS_KEY_ID=foo AWS_SECRET_ACCESS_KEY=bar ./packer build ubuntu.json
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name...
==> amazon-ebs: Inspecting the source AMI...
(中略)
==> amazon-ebs: Provisioning with shell script: /var/folders/tj/0g3ny7c94mq9s_2dtbzk0hbr0000gn/T/packer-shell612616353
(中略)
    amazon-ebs:  INFO : Starting Itamae...
    amazon-ebs:  INFO : Recipe: /tmp/itamae-repo/bootstrap.rb
    amazon-ebs:  INFO :   execute[echo Hello] executed will change from 'false' to 'true'
    amazon-ebs:
==> amazon-ebs: Stopping the source instance...
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: ubuntu-trusty-hvm
    amazon-ebs: AMI: ami-abcdef
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: Deleting temporary security group...
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

ap-northeast-1: ami-abcdef

ConsulのWatch機能を使ってItamaeを実行する

Capistranoを使った方法は手軽である反面、サーバ台数が増えるとSSHのオーバヘッドが気になったり、接続が切れたり、ストレスを感じることがあります。そのような問題を解決するため、ConsulのEvent機能を利用して、Itamaeを実行する例を紹介します。Consulはサービスディスカバリ、障害検知、KVSなどの機能を提供するソフトウェアですが、その中の機能の一つとしてEventの発火と監視があります。Itamae実行用のEventを監視し、Eventを受けてItamaeを実行することができます。

ここでは説明を簡略化するため、1台構成で解説しますが、実際に利用する場合はConsulクラスタを構築する必要があります。詳しくはConsulのドキュメントを参照してください。

まず、Consulをダウンロードします。

$ wget https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip
$ unzip 0.5.2_linux_amd64.zip

eventを受け取ったときに実行されるスクリプトを書きます。細かい仕様などについてはドキュメントを参照してください。以下のスクリプトをitamae-apply-handlerとして保存します。

#!/usr/bin/env ruby
require 'pathname'
require 'json'
require 'base64'
require 'tmpdir'

# 同じeventを2度以上処理しないように最後のeventのindexを保存しておく
index = ENV['CONSUL_INDEX']
index_file = Pathname.new('/tmp/itamae-apply-handler-last-index')
if index_file.exist? && index_file.read == index
  puts "This is not a new event"
  exit
end
index_file.write(index)

input = JSON.parse($stdin.read).last
payload = JSON.parse(Base64.decode64(input['Payload']))

Dir.mktmpdir do |tmpdir|
  Dir.chdir(tmpdir) do
    # RecipeURLで指定されたgzipped tarballをダウンロードし実行する
    system("wget", "-O", "recipe.tar.gz", payload['RecipeURL']) || raise
    system("tar", "xf", "recipe.tar.gz") || raise
    system("/opt/itamae/embedded/bin/itamae", "local", "bootstrap.rb") || raise
  end
end

上記のスクリプトでやっていることは非常にシンプルでeventで指定されたURLからレシピをダウンロードし、itamae localを実行しています。

このスクリプトを実行可能にします。

$ chmod +x itamae-apply-handler

スクリプト内でItamaeを実行しているので、事前にインストールしておく必要があります。以下ではDebianパッケージを使ってインストールしていますが、Rubygemでインストールすることも可能です。

$ curl -s https://packagecloud.io/install/repositories/ryotarai/itamae/script.deb.sh | sudo bash
$ sudo apt-get install itamae

最後にevent名とhandlerのひも付ける設定ファイルを書きます。以下をconsul.jsonとして保存します。

{
  "watches": [{
    "type": "event",
    "name": "itamae-apply",
    "handler": "/path/to/itamae-apply-handler"
  }]
}

そして、Consulをサーバモードで起動します。

$ ./consul agent -data-dir=/tmp/consul -server -bootstrap -config-file=consul.json
(中略)
    2015/09/28 08:25:15 [INFO] consul: New leader elected: vagrant-ubuntu-trusty-64
    2015/09/28 08:25:15 [INFO] raft: Disabling EnableSingleNode (bootstrap)
    2015/09/28 08:25:15 [INFO] consul: member 'vagrant-ubuntu-trusty-64' joined, marking health alive
    2015/09/28 08:25:16 [INFO] agent: Synced service 'consul'

この状態でeventを発行すると、Itamaeが自動的に実行されます。

$ ./consul event -name itamae-apply '{"RecipeURL": "https://gist.github.com/ryotarai/b5e6a356145faeac55da/raw/recipes.tar.gz"}'

consul monitorコマンドでログが見られます。

$ ./consul monitor -log-level=debug
2015/09/28 09:16:05 [DEBUG] consul: user event: itamae-apply
2015/09/28 09:16:05 [DEBUG] agent: new event: itamae-apply (b99e3de8-a0f7-a74a-e8b9-4b2d48b97ef2)
2015/09/28 09:16:05 [DEBUG] http: Request /v1/event/list?index=9906610952174680195&name=itamae-apply (5.003298219s)
2015/09/28 09:16:06 [DEBUG] agent: watch handler '/home/vagrant/itamae-apply-handler' output: --2015-09-28 09:16:05--  https://gist.github.com/ryotarai/b5e6a356145faeac55da/raw/recipes.tar.gz
Resolving gist.github.com (gist.github.com)... 192.30.252.143
Connecting to gist.github.com (gist.github.com)|192.30.252.143|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://gist.githubusercontent.com/ryotarai/b5e6a356145faeac55da/raw/recipes.tar.gz [following]
--2015-09-28 09:16:06--  https://gist.githubusercontent.com/ryotarai/b5e6a356145faeac55da/raw/recipes.tar.gz
Resolving gist.githubusercontent.com (gist.githubusercontent.com)... 103.245.222.133
Connecting to gist.githubusercontent.com (gist.githubusercontent.com)|103.245.222.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 167 [application/octet-stream]
Saving to: recipe.tar.gz

     0K                                                       100% 32.2M=0s

2015-09-28 09:16:06 (32.2 MB/s) - recipe.tar.gz saved [167/167]

 INFO : Starting Itamae...
 INFO : Recipe: /tmp/d20150928-2975-1a7tza3/bootstrap.rb
 INFO :   execute[echo Hello Itamae] executed will change from 'false' to 'true'

このように非常に簡単にスケーラブルなItamae実行環境が用意できます。この例はあくまで大枠を解説したものなので、実際に運用する際には考慮すべき点がいくつかあります。いくつか挙げると、

Abort機能

途中で中止できるほうが心の平穏が保たれそうです。

同時実行数の制限

全台で同時に実行されると困る場合や、リスクが大きい場合、同時に実行できる数を制限すると良いかもしれません。

consul lockで同時に実行できる数を制限することもできます。

ログの集約

いちいちサーバにログインする必要があるのでログを集約する必要があります。

eventの発行元の検証

誰でもeventを発行出来てしまうので、例えばHMACをPayloadに付加すると良いかもしれません。

レシピをCIなどでアップロードする

Gitなどで管理しているレシピをどこかにアップロードする必要があるので、Jenkinsなどでアップロードする必要があります。

上記の例ではHTTPのURLを指定してダウンロードしていましたが、AWSであればS3からダウンロードするのもよいでしょう。

といったところでしょうか。規模が大きい場合このような方法も試してみてはいかがでしょうか。

まとめ

今回紹介した方法以外にもItamaeを使う手段はいろいろあります。Itamaeは非常にシンプルなツールなので環境に合わせてカスタマイズして使っていただけるかと思います。

これまで全5回、Itamaeについて紹介してきましたが、Itamaeの機能や使い方などすべて紹介しました。Itamaeは全5回の連載で全貌を解説できるぐらい、軽量でシンプルだということをおわかりいただけたでしょうか。Itamaeをコンポーネントの一つとして様々なツールと組み合わせ、Infrastructure as Codeを推し進めていただければ幸いです。

おすすめ記事

記事・ニュース一覧