右往左往ブログ

日々よりみち

chefの依存定義(depends)を勘違いしていた話

chefでいうmetadata.rb中のdependsが保証する依存性がどこまでなのかを勘違いしていたのでメモしておきます。 結論からいうと、 depends指定はattributeの参照先として読み込むだけ の機能でした。

  • dependsにより保証されること
    • depends参照先cookbookのattributeを流用できること
  • dependsで保証されないこと
    • 必ずしもdepends参照先cookbookを実行する必要がないこと
    • ましてや、depends参照先が、記載元cookbookより「先に」実行される必要はないこと

この、「保証されないこと」ができるとばかり勘違いしていたので、dependsに頼った開発をしていたらエラーにならずに、あれ…?となってしまったのでした。

公式の記述

公式のmetadata.rb

以下のように記載があります。

Use to show that a cookbook has a dependency on another cookbook.

これだけだと分からないですね。

テスト

事前準備

cookbook aとcookbook bを作っておきます。

概ね以下のような構造です。(検証で無関係なところは省略しています)

    chef-repo
     ├cookbooks
     │├a
     ││├attributes/default.rb
     ││├recipes/default.rb
     ││└metadata.rb
     │└b
     │  ├attributes/default.rb
     │  ├recipes/default.rb
     │  └metadata.rb
     └localhost.json

cookbook bのmetadata.rb中でaへの依存性を定義します。

cookbooks/b/metadata.rb

<略>
depends          'a'

cookbook a,b内ではそれぞれattributeを定義しておきます。

cookbooks/a/attributes/default/rb

default['a']['value'] = "attribute a"

cookbooks/b/attributes/default/rb

default['b']['value'] = "attribute b"

cookbook bのrecipeでは、cookbook a,bのattributeを参照するようにします。

cookbooks/b/recipes/default.rb

log "this is cookbook b"
log "attribute b: #{node['b']['value']}"
log "attribute a: #{node['a']['value']}"

chef-solo実行時は、cookbook bのみを実行するようにします。

localhost.json

{
    "run_list":[
        "recipe[b]"
    ]
}

このまま実行する場合

depends指定をすれば、たとえ他cookbookを実行しなかったとしても、attributeは利用できます。

$ sudo chef-solo -c solo.rb -j ./localhost.json
Starting Chef Client, version 12.0.3
Compiling Cookbooks...
Converging 3 resources
Recipe: b::default
  * log[this is cookbook b] action write

  * log[attribute b: attribute b] action write

  * log[attribute a: attribute a] action write


Running handlers:
Running handlers complete
Chef Client finished, 3/3 resources updated in 1.688893613 seconds

depends指定をせず、他cookbookのattributeを参照する場合

attributeが参照できないので、当然エラーになります。

cookbooks/b/metadata.rb

<略>
# depends          'a' # コメントアウト

実行時エラー。

$ sudo chef-solo -c solo.rb -j ./localhost.json
Starting Chef Client, version 12.0.3
Compiling Cookbooks...

================================================================================
Recipe Compile Error in /home/vagrant/chef-repo/cookbooks/b/recipes/default.rb
================================================================================

NoMethodError
-------------
undefined method `[]' for nil:NilClass

Cookbook Trace:
---------------
  /home/vagrant/chef-repo/cookbooks/b/recipes/default.rb:12:in `from_file'

Relevant File Content:
----------------------
/home/vagrant/chef-repo/cookbooks/b/recipes/default.rb:

  5:  # Copyright 2015, YOUR_COMPANY_NAME
  6:  #
  7:  # All rights reserved - Do Not Redistribute
  8:  #
  9:
 10:  log "this is cookbook b"
 11:  log "attribute b: #{node['b']['value']}"
 12>> log "attribute a: #{node['a']['value']}"
 13:

<略>

attribute上の依存関係をなくして、依存先cookbookを利用しない場合

cookbook aのattributeを参照しないようにします。

cookbooks/b/recipes/default.rb

log "this is cookbook b"
log "attribute b: #{node['b']['value']}" 
# log "attribute a: #{node['a']['value']}" # コメントアウト

cookbook bのaへの依存性も元に戻しておきます。

cookbooks/b/metadata.rb

<略>
depends          'a'

この時点で実行すると、エラーは発生せず正常終了します。

$ sudo chef-solo -c solo.rb -j ./localhost.json
Starting Chef Client, version 12.0.3
Compiling Cookbooks...
Converging 2 resources
Recipe: b::default
  * log[this is cookbook b] action write

  * log[attribute b: attribute b] action write


Running handlers:
Running handlers complete
Chef Client finished, 2/2 resources updated in 41.895869267 seconds

ポイントは、 dependsによりcookbook aを指定しているにも関わらず、aを実行しなくても問題がない ことです。

依存先cookbookを配置しない場合

では、cookbook aをそもそも配置しなかったら…エラーになります。

chef-repo
 ├cookbooks
 ││# aディレクトリを消す
 │└b
 │  ├attributes/default.rb
 │  ├recipes/default.rb
 │  └metadata.rb
 └localhost.json

実行時エラー。

$ sudo chef-solo -c solo.rb -j ./localhost.json
Starting Chef Client, version 12.0.3
Compiling Cookbooks...

Running handlers:
[2015-03-29T00:11:22+00:00] ERROR: Running exception handlers
Running handlers complete
[2015-03-29T00:11:22+00:00] ERROR: Exception handlers complete
[2015-03-29T00:11:22+00:00] FATAL: Stacktrace dumped to /tmp/chef-solo/chef-stacktrace.out
Chef Client failed. 0 resources updated in 41.570472421 seconds
[2015-03-29T00:11:22+00:00] ERROR: Cookbook a not found. If you're loading a from another cookbook, make sure you configure the dependency in your metadata
[2015-03-29T00:11:23+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)

まとめ

metadata.rb中のdepends指定により分かったことは以下です。

  • depends参照先のcookbookはcookbook_pathに配置している必要がある
  • ただし、配置するだけでよく、実行される必要はない

「cookbook aが実行されてからでないとcookbook bが実行されてはならない」というシチュエーション時に、dependsを記載すれば依存関係や順序性が(実行はされずとも)チェックされると思ったのですが、全然そんなことはなかったです…。例えば、「jdkをインストールしてからelasticsearchをインストール」とか、「rubyをインストールしてから各種gemをインストール」とか、そういったケースはあり得ると思うのですが、それはdependsでチェックするべきではなかったんですね…。

期待していたのは、

"recipe[gem]",
"recipe[ruby]"

みたいな書き方をlocalhost.json中で書いたとし、「rubyを先に実行する必要があるよ」とエラーを出してくれるとか、

"recipe[gem]"

と書いたときに「rubyが実行されてないよ」とエラーを出してくれることだったのですが、そうはならず…。

おそらく、ひとつのcookbookにinclude_recipeを記載することで順序性や依存性を定義するのが理想なのかと思いますが、それを汲み取ることができずに苦労した話でした。