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

第2回レシピの書き方

前回はItamaeをインストールし、簡単なレシピを実行してみました。今回はさらに実践的なレシピの書き方について解説します。前回述べたようにItamaeはChefに強く影響を受けているため、概ねChefと同じように書けます。

リソースとは

レシピにはリソースと呼ばれる、サーバ上のコンポーネント(ソフトウェア、ファイル、コマンドなど)を抽象化したものを記述します。リソースにはアクションをはじめとする属性を指定でき、操作の種類を指定することができます。前回も使ったpackageのリソースを例にすると

package "nginx" do
  action :install # nginxパッケージをインストールする
  version "1.4.6-1ubuntu3.3" # 特定のバージョンをインストールする
end
package "nginx" do
  action :remove # nginxパッケージをアンインストールする
end

のようにpackageリソースをインストールしたり、アンインストールしたりすることができます。

packageリソースの場合、installアクションがデフォルトになっているため、省略することも可能です。

package "nginx" # 暗黙的にinstallアクションになる

どのようなアクションや属性が用意されているかはドキュメントを参照してください。

Itamaeには様々なリソースが用意されていますが、以下ではその中でよく使うであろうリソースを紹介します。

executeリソース

executeリソースは任意のコマンド実行を表します。

runアクション

execute "touch /tmp/file-created-by-itamae" do
  action :run # デフォルトなので省略可
end

任意のコマンドをリソースとして定義できるため、Itamaeのリソースとして用意されていない操作を実行することができます。

繰り返し似たようなコマンドを書く場合や抽象化出来る場合は、プラグイン機構を利用して独自のリソースを定義するほうがお勧めですが、手っ取り早く実行するには便利です。プラグイン機構については次回、解説します。

executeリソースを利用する際は、冪等性が保たれるように記述する必要があります。例えば、2度以上実行する必要がなかったり、2度実行すると失敗するようなコマンドの場合は、実行すべきかどうかを事前に確認するようにします。

execute "touch /tmp/file-created-by-itamae" do
  not_if "test -f /tmp/file-created-by-itamae"
  # または
  # only_if "test ! -f /tmp/file-created-by-itamae"
end

上記のようにonly_ifまたはnot_ifを使うと、特定のコマンドが成功または失敗した場合のみアクションを実行することができます。

fileリソース

fileリソースはサーバ上のファイルを表すリソースです。

createアクション

デフォルトはcreateアクションで、ファイルを作成します。なお、既にファイルが存在して、変更がある場合は上書きします。

file "/etc/nginx/conf.d/app.conf" do
  action :create # デフォルトなので省略可
  content "..."  # ファイルの内容
end

fileリソースをつかうと実行時にdiffが表示されます。--dry-runオプションをつけた場合にも表示されるので、実行前に変更される部分がわかります。

editアクション

editアクションをつかうと、ファイルの内容変更をRubyのスクリプトで書くことができます。

file "/etc/ssh/sshd_config" do
  action :edit
  block do |content|
    content.gsub!(/^Port .+$/, "Port 12322")
  end
end

editアクションの場合にもdiffが出力されます。

remote_fileリソース

createアクション

remote_fileはfileリソースの一種で、ローカルにあるファイルをサーバに置きます。

remote_file "/etc/my.cnf" do
  action :create  # デフォルトなので省略可
  source "my.cnf" # このファイルを/etc/my.cnfに配置する(レシピがあるディレクトリからの相対パス)
end

contentをファイルに書けること以外、fileリソースと同じですが、レシピと設定ファイルなどのsourceを分離でき管理しやすくなります。sourceは省略すると自動的に決定されます。sourceを省略した場合は、上記のmy.cnfの例だと以下のパスを上から順番に探索し、最初に見つかったものが使われます。

  • (レシピがあるディレクトリ)/files/etc/my.cnf
  • (レシピがあるディレクトリ)/files/my.cnf

templateリソース

createアクション

templateリソースはremote_fileとほぼ同じですが、sourceファイルとしてERBテンプレートを使える点が異なります。テンプレートを使って動的に設定ファイルを生成し、ホストごとに異なる設定を配布するときに有用です。

ホスト固有の値を渡したり、ホストの情報を取得するにはノード属性機能を利用します。ノード属性については次回解説します。

template "/etc/my.cnf" do
  action :create  # デフォルトなので省略可
  source "my.cnf.erb"
end

remote_fileと同様に、sourceは省略すると自動的に決定されます。sourceを省略した場合は、上記のmy.cnfの例だと以下のパスを上から順番に探索し、最初に見つかったものが使われます。

  • (レシピがあるディレクトリ)/templates/etc/my.cnf.erb
  • (レシピがあるディレクトリ)/templates/etc/my.cnf
  • (レシピがあるディレクトリ)/templates/my.cnf.erb
  • (レシピがあるディレクトリ)/templates/my.cnf

packageリソース

packageリソースはOS標準のパッケージシステムのパッケージを表します。Ubuntu, Debianの場合dpkg(apt⁠⁠、CentOS, RHELの場合rpm(yum⁠⁠、Mac OSの場合Homebrewを操作します。

installアクション

installアクションはパッケージのインストールを行います。すでにパッケージがインストールされていて、versionが指定されていない場合は何もしません。

package "nginx" do
  action :install # デフォルトなので省略可
  version "1.4.6-1ubuntu3.3"
end

removeアクション

removeアクションはパッケージのアンインストールを行います。

package "nginx" do
  action :remove
end

serviceリソース

serviceリソースはSysvinitやUpstartなどで管理されているサービスを表します。packageリソースと同様、OS・ディストリビューションに応じて自動的にコマンドが選択されます。

service "nginx" do
  action :reload
end

利用可能なアクションはstop, start, restart, reloadです。このリソースはデフォルトのアクションがnothingで、actionを指定しない限り何も起こりません。

local_ruby_blockリソース

runアクション

local_ruby_blockリソースはローカルでRubyのコード(ブロック)を実行するだけのリソースです。後述しますが、レシピに書かれたRubyのコードはリソースアクションが実行される前に評価されます。特定のリソースアクションのあとに実行したいコードがある場合、local_ruby_blockを利用します。

local_ruby_block "say hello" do
  action :run # デフォルトなので省略可
  block do
    puts "Hello"
  end
end

全リソース共通の属性

既にでてきたonly_ifnot_ifに加え、いくつかの属性は全リソースで使えるようになっています。

only_if, not_if

only_if, not_ifでリソースアクションを実行すべきかどうかを判定することができます。

  • only_if:指定されたコマンドが成功した場合(コマンドの終了コードが0)のみアクションを実行します。
  • not_if:指定されたコマンドが失敗した場合(コマンドの終了コードが0以外)のみアクションを実行します

user

userで指定したユーザとしてコマンドを実行します。

cwd

cwdで指定したディレクトリ内でコマンドを実行します。

リソースの実行順序

Itamaeのリソースは基本的に上から下に順番に実行されます。しかし、リソースの定義は実行前に行われるため注意が必要です。具体例を以下に示します。

puts "A"

local_ruby_block "local" do
  puts "B"
  block do
    puts "C"
  end
  puts "D"
end

puts "E"
vagrant@vagrant-ubuntu-trusty-64:/vagrant$ itamae local -l debug order.rb
 INFO : Starting Itamae...
A
B
D
E
 INFO : Recipe: /vagrant/order.rb

C

実行結果を見るとわかるように、DECより前に出力されています。これはリソースアクションCの前にリソース定義部分が評価されているためです。レシピを書く際はこの挙動にご注意ください。

リソース間の連携

例えば、特定のファイルが更新された際にサービスをリロードしたいなど、リソース間で連携を取りたい場合があると思います。notifiessubscribesで、リソースに変更があった場合に他のリソースのアクションを実行できます。

notifies

service "nginx" do
  action :nothing
end

template "/etc/nginx/conf.d/app.conf" do
  notifies :reload, "service[nginx]"
end

上記の例では、templateに更新があった場合(ファイルの内容に変更があった場合など)servicereloadアクションを実行するようになります。これにより設定ファイルが変更された時だけ、設定ファイルをリロードすることができます。

serviceに指定されてるnothingアクションはnotifiessubscribesのためのアクションで、すべてのリソースで利用可能です。nothingを指定すると、実際にはアクションが実行されずリソース定義だけが行われるので、notifiessubscribesでのみアクションが実行されます。

subscribes

subscribesnotifiesの逆で、あるリソースの変更を監視して、変更があった場合にアクションを実行することができます。

service "nginx" do
  action :nothing
  subscribes :reload, "template[/etc/nginx/conf.d/app.conf]"
end

template "/etc/nginx/conf.d/app.conf"

上記の例では先に出てきたnotifiesの例と同様、templateに変更があった場合にserviceのリロードを行います。

なお、notifiessubscribesは1つのリソースに複数指定することも可能です。

複数のリソースをまとめる

defineを使うと、複数のリソースをまとめて1つのリソースのように見せることができます。

define :install_and_enable_package, version: nil do
  package params[:name] do
    version params[:version] if params[:version]
    action :install
  end

  service params[:name] do
    action :enable
  end
end

このようにリソースを定義すると、定義以降install_and_enable_packageリソースが使えるようになります。

install_and_enable_package 'nginx' do
  version '1.4.6-1ubuntu3.3'
end

複数回同じようなリソース定義が出てくる場合はこのようにdefineを使って抽象化をすると見通しが良くなります。

他のレシピを読み込む

レシピを書いていくと、ファイルが大きくなって見通しが悪くなったり、再利用したくなる場合があります。その場合はinclude_recipeを使って他のレシピを読み込み、ファイルを分割することをお勧めします。

# cookbooks/nginx/default.rb
package "nginx"
template "/etc/nginx/conf.d/app.conf"
# cookbooks/nginx/templates/etc/nginx/conf.d/app.conf にテンプレートを置く
# cookbooks/ruby/default.rb
package "ruby" do
  version "2.2.2"
end
# roles/app.rb
include_recipe "../cookbooks/nginx/default.rb"
include_recipe "../cookbooks/ruby/default.rb"
# include_recipeにはレシピがあるディレクトリからの相対パスを指定する

このようにソフトウェアごとにディレクトリを作って、サーバの役割ごとのレシピからinclude_recipeでレシピを読み込む形を推奨しています

実際に実行する際は以下のようにroles以下のファイルを指定します。

$ itamae local roles/app.rb

まとめ

今回は比較的よく使うリソースの解説と、レシピを書きやすくする機能について紹介しました。

次回はホストごとに異なる値の渡し方・取得の仕方や、Itamaeプラグインの作り方について解説します。

おすすめ記事

記事・ニュース一覧