前々回、
ノード属性(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'
endtemplates/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まとめ
今回は設定を外から注入しレシピを汎用的に開発する方法を紹介しました。
次回は汎用的なレシピや独自リソースをプラグインとして公開するための方法をご紹介します。
