ChefでRubyコンパイル時とChefリソース収束時による実行順序の問題が生じるケースと対応をまとめておく
問題ケース1 : システムの状態を取得し使用するロジックが、レシピではその前方にあるChefリソースの収束結果を使用できない
File.exist?('/tmp/test.txt')
s = `cat #{path}`
などの、Rubyによるシステム状態の取得は、Rubyコンパイル時に実行される為、Chef Resourceによる収束の結果を使用できない。
- 例外:Chefリソースにおけるnot_ifなどのbarrierの評価は、収束時に行われる。
対応1a : 収束時にRubyによるシステム状態の取得を行う(後実行)
1a-1 : ruby_blockを用いる
この場合、このruby_block内からChefのリソースを呼び出すことは、以下の対応により可能。
scope0 = self
ruby_block 'foo' do
block do
scope0.package 'bar'
end
end
notifiesも使用可能。
使用する場合、宛先リソースの指定にはxxx.は不要。
ネストも可能。
デメリット (あまり思いつかないが)
- ruby_blockにnot_ifなどのbarrierを付けなければ収束時に実行リソース数を0にできない。(気分的な問題)
- 単に大きなruby_blockにして全体を包んでしまうとbarrierの作成が困難に
- 情報を入手し利用する単位でブロック化
1a-2 : リソースの属性においてはlazy {}による遅延評価が使用可能
個人的にはfileリソースのcontent属性でよく使用している。
file '/tmp/foo' do
content lazy {
%x(cat #{path} | sed 's/A/B/g')
}
end
fileリソースの中で p (lazy { ... }).class などとすると、Chef::DelayedEvaluator クラスであることが判る。
以下の書式も可能
- content lazy { ... }
- content (lazy { ... })
- content (lazy do ... end)
- content(lazy { ... })
- content(lazy do ... end)
以下はエラーとなった
- content lazy do ... end
対応1b : コンパイル時にChefリソースの実行を行う(前実行)
Chefリソースに.run_action(:some_action)を用いる
resource_name 'foo' do
action :nothing
end.run_action(:some_action)
この場合、デフォルトアクションの選択は無いので、:some_actionとしてアクションの明示指定が必要。
リソース本体にaction :nothingが無ければ、収束時にも実行されるので、冪等性が不十分だと問題が生じ得る。
デメリット
公式にもある通り、notificationsは使えない。
使ったことは無いが、少なくともchefspecでは問題となる様子。
chefspec action: nothing/end.run_action(:create) examples? #107
問題ケース2 : 収束時に遅延実行されるRubyロジックにおける変数
以下などが該当
- lazy指定によるChefリソースの属性 (対応1aケースも該当)
- ruby_blockにおけるblock (対応1aケースも該当)
- Chefリソースにおけるnot_ifなどのbarrier
Chefリソースのlazy指定による属性におけるRuby変数の値
val = 'true'
file '/tmp/test.txt' do
content lazy {
# 何らかの文字列を返す処理
`cat #{path} | sed -e 's/^AAA=.*$/AAA=#{val}/'` # この#{val}の値は'false'
}
end
...
val = 'false' # 別の処理でvalに'false'を設定して使用
他のChefリソースの反映結果を取り込む為に遅延評価のlazyを使用する。
すると、lazyブロック内の変数に関して、コンパイル時ではなく、収束時に評価される為、上の例では AAA=false となってしまう。
この問題は、ruby_blockやnot_ifなどのバリヤーでも該当する。
ruby_blockにおけるRuby変数の値
ruby_block内のsは遅延評価される為、最後に設定された値が使用される
s = "abc"
ruby_block 'test' do
block do
%x(echo #{s} > /tmp/test.txt) # このsは"def"
end
end
s = "def"
Chefリソースでのnot_ifなどのbarrierにおけるRuby変数の値
barrierで使用されるbは遅延評価される為、最後に設定された値が使用される
b = true
file '/tmp/test.txt' do
content %x(date)
not_if { b } # このbはfalse
end
b = false
結果としてこの例では変更される
対応2: 対象の変数をイテレーターブロックへの変数としてしまう
以下に記載した対応が可能。
Chefで、Ruby実行時とリソース実行時の変数の値の差を抑えるには、イテレータで変数として渡された値を使うと容易
nodeオブジェクトから.eachにより渡せるとスマートに対応可能。
とりあえず強引かつシンプルに対応するには、例えば以下の様にする事で可能。
["abc"].each do |s|
ruby_block 'test' do
block do
%x(echo #{s} > /tmp/test.txt) # このsは"abc"
end
end
end
s = "def" # この影響は受けない
きっともっとスマートな方法があるとは思うのですが。。