前々回、
ノード属性(node attributes)
Itamaeのレシピはできるだけ汎用的にし、
レシピを汎用的なものにするためには、
Itamaeの実行時のオプションに--node-json
を渡すと、
templates/etc/nginx/nginx.conf.erb
:$ cat node.json { "nginx": { "version": "1.9.3-1+trusty0" } } $ itamae local --node-json node.json recipe.rb
--node-json
でJSONを指定した場合、node
を使って参照できます。
package "nginx" do
version node['nginx']['version']
# また、node[:nginx][:version] や node.nginx.version でも参照できる
end
このようにJSONでnginxのバージョンを指定できるようにしておくと、
また、
$ cat node.yaml nginx: version: "1.9.3-1+trusty0" $ itamae local --node-yaml node.yaml recipe.rb
テンプレートからの利用
node
はレシピだけではなくテンプレート内からも参照することができます。テンプレートの使い方については前回の記事を参照してください。設定ファイルを動的に生成する場合に有用です。
recipe.rb
:node.reverse_merge!({
'nginx' => {
'worker_processes' => 'auto',
}
})
template "/etc/nginx/nginx.conf" do
owner 'root'
group 'root'
mode '644'
end
templates/etc/nginx/nginx.conf.erb
:worker_processes <%= node['nginx']['worker_processes'] %>;
(後略)
このようなレシピを書いておくと、worker_
を設定できます。
デフォルト値の指定
package "nginx" do
version node['nginx']['version']
end
このようなレシピをJSONを指定せず実行すると、
デフォルト値を設定するにはreverse_
メソッドを利用します。reverse_
は値が設定されていない場合のみ値を設定するメソッドです。
# nodeが以下のようになっていて
# {
# "nginx" => {
# "worker_processes" => 1
# }
# }
# reverse_merge!を呼ぶと
node.reverse_merge!({
'nginx' => {
'version' => '1.9.3-1+trusty0'
}
})
# nodeはこのようになります
# {
# "nginx" => {
# "worker_processes" => 1,
# "version" => "1.9.3-1+trusty0"
# }
# }
このメソッドを利用するとJSONで値が設定された場合にはその値を使って、
# recipe.rb
node.reverse_merge!({
'nginx' => {
'version' => '1.9.3-1+trusty0'
}
})
package "nginx" do
version node['nginx']['version']
end
この場合、1.
を利用して、reverse_
を使ってロールごとにデフォルト値を変更することもできます。例えば、
# recipes/nginx.rb
node.reverse_merge!({
'nginx' => {
'worker_processes' => 'auto',
}
})
template '/etc/nginx/nginx.conf'
# roles/app.rb
node.reverse_merge!({
'nginx' => {
'worker_processes' => 1,
}
})
# この時点でnginx.worker_processesが設定されるため、recipes/ruby.rbの設定は使われない
include_recipe '../recipes/nginx.rb'
# roles/proxy.rb
# この時点でnginx.worker_processesは設定されていないため、recipes/ruby.rbの設定が使われる
include_recipe '../recipes/nginx.rb'
appロールでは1プロセス、--node-json
や--node-yaml
を指定することができます。
バリデーション
デフォルト値を設定せず、include_
されるような場合、
バリデーションはvalidate!
を呼ぶことで実行できます。例えば、
node.validate! do
{
nginx: {
version: string,
}
}
end
このように記述すると、node['nginx']['version']
が指定されていて、
node.validate! do
{
nginx: {
user: string, # 文字列のみ(必須)
worker_processes: optional(integer), # 整数のみ(任意項目)
# sitesは配列で、server_name, root, allowed_ipsが必須
sites: array_of({
server_name: string, # 文字列(必須)
root: string, # 文字列(必須)
allowed_ips: array_of(string), # 文字列の配列(必須)
}),
listen: match(/^(80|443)$/), # /^(80|443)$/にマッチする文字列(必須)
},
}
end
例えば、
$ cat node.json { "nginx": { "user": 123 } } $ itamae local --node-json node.json recipe.rb INFO : Starting Itamae... INFO : Loading node data from /private/tmp/node.json... ERROR : 'nginx->user' is not String ERROR : 'nginx->sites' is required but missing ERROR : 'nginx->listen' is required but missing
nodeを参照する際の注意点
node
はRubyのHashnil
が返ってきます。例えば、
node.reverse_merge!({
nginx: {}
})
template "/etc/nginx/nginx.conf"
worker_processes <%= node['nginx']['worker_processes'] %>;
生成される設定ファイルは
worker_processes ;
このようになります。これを防ぐには、
- デフォルト値を設定する
- バリデーションルールを記述し、
実行前に中止する fetch
を使って値を参照する
といった方法があります。1、fetch
を使うと、
# worker_processesが設定されていない場合、例外が発生する
worker_processes <%= node.fetch('nginx').fetch('worker_processes') %>;
ホスト情報の取得
何も設定しなくても、node
経由で取得できるようになっています。これを利用すると、
case node['platform']
when 'ubuntu'
# ubuntuの場合の処理
when 'redhat'
# redhatの場合の処理
end
また、
p node['ec2']
# {"ami-id"=>"ami-35072834",
# "ami-launch-index"=>"0",
# "ami-manifest-path"=>"(unknown)",
# "block-device-mapping"=>{"ami"=>"/dev/xvda", "root"=>"/dev/xvda"},
# "hostname"=>"ip-172-30-1-119.ap-northeast-1.compute.internal",
# "instance-action"=>"none",
# "instance-id"=>"i-0d307c14",
# "instance-type"=>"t2.micro",
# "local-hostname"=>"ip-172-30-1-119.ap-northeast-1.compute.internal",
# (後略)
ホスト情報の取得はSpecinfraのHost Inventory機能を利用しているので、
プラグイン
このホスト情報取得機能はプラガブルになっていて、
role = node['ec2']['tags']['Role'] # Roleタグが取得できる
include_recipe File.join("roles", "#{role}.rb")
このようにRoleタグを参照して、
コマンドの実行結果を使ってレシピを構築する
レシピを動的に変化させ、run_
があります。レシピやテンプレートとrun_
を呼ぶと、
case run_command("your-command").stdout.chomp
when "foo"
# 処理
when "bar"
# 処理
end
まとめ
今回は設定を外から注入しレシピを汎用的に開発する方法を紹介しました。
次回は汎用的なレシピや独自リソースをプラグインとして公開するための方法をご紹介します。