fbpx

[和訳] SysAdvent Day 21: ChefでおいしくResourceを調理 #getchef

この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。

本稿は SysAdvent Day 21: Baking Delicious Resources with Chef (2014/12/22) の和訳です。

この記事はSysAdventからの転載(訳注:原文)です。

みなさん、クリスマスはいつも焼きたてのクッキーの甘い匂いに満ちています。キッチンはアイシングシュガークッキーからピーナッツバタークッキーまでいろいろ準備してひどく散らかっています。クッキー缶は近所の人々や友人にくばるためにあふれんばかりになっているでしょう。

この習慣の幼少の記憶では、祖母がピーナッツバタークッキーに丁寧に網目をつけるにはどのようにしたらよいか教えてくれたことです。クッキー生地に刺さらないようにフォークを砂糖に差し込み、慎重にクッキー生地に押し込みます。このクッキーの伝統のように、Chefの知識を拡充してLWRPを使ってクッキーを焼き上げられるのに必要なコンセプトを紹介しようと思います。

これから紹介する一通りの例を試すには、Chef Development Kit (Chef DK)VagrantVirtual Boxのインストールが必要です。もしAmazonのようなクラウドコンピューティングプロバイダを利用するなら、Chef DK同梱の.kitchen.yml設定を修正してください。

ResourceとProviderの復習

ResourceはChefの基本的な組み立てブロックです。Chefには多くのResourceが同梱されています。Resourceは宣言型インターフェイスを持ち、これはつまり、ある状態に到達するために必要な手順を記述するというより、Resourceがどのような状態であってほしいかを記述することを意味します。Resourceはtypename、1つ以上のパラメータactionsnotificationsを持ちます。

それではResourceの例としてrouteを見てみましょう。


route "NAME" do
gateway "10.0.0.20"
action :delete
end

route Resourceシステムのルーティングテーブルを記述します。Resourceのtypeはrouteです。Resourceのnameはtypeに続く文字列です。route Resourceはdevicegatewaynetmaskprovidertargetといった任意のパラメータを含みます。この例ではgatewayパラメータのみを記述しています。またdelete actionを用い、notificationsはありません。

Chefの各Resourceは、Resourceを実際に望ましい状態へと導くためのProviderを1つ以上含みます。Chef公式のResourceを使っているなら、通常、Providerを選ぶ必要はありません。Chefは作業を行うための最適のProviderを選択します。Providerを検査するなら、Chefの基礎コードを確認することで行えます。例えば、route Providerのコードや、クラスのrubydocです。

既製のResourceとProviderでは、インフラを単純明快なRecipeでプログラミングするように記述するという要求を十分満たせないこともあるかもしれません。繰り返しや複雑さを減らし、可読性を高めたいという段階に到達しました。Chefは機能拡張のために、Definition、Heavy Weight Resources and Providers (HWRP)、Light Weight Resources and Providers (LWRP)を提供しています。

Definitionは本質的にはRecipeマクロです。これはCookbookのdefinitionsディレクトリに格納されます。通知を受け取れません。

HWRPはCookbookのlibrariesディレクトリに格納される純粋なRubyコードです。デフォルトではChef DSLからコアResourceを利用できません。

本稿の主題であるLWRPは、Chef DSLとRubyの組み合わせです。繰り返しパターンを抽象化するのに有用です。実行時に構文解析されてRubyクラスにコンパイルされます。

LWRP

Resourceを拡張するためには、Resourceの構成要素であるtypenameパラメータactionsnotificationsを再検討する必要があります。

べき等性と収束性も考慮しなければいけません。

べき等性が意味するところは、望ましい状態またはポリシーに追従するようResourceを導く必要がある変更がある場合のみ、ProviderはResourceの状態を変更するということです。

収束性の意味するところは、Providerは現在のResourceの状態を望ましいResourceの状態に近づけていくということです。

Resourceはtypeを持ちます。LWRPのResource TypeはCookbook内のファイル名によって定義されます。これはcookbook_resource式によって明示的に命名されます。もしdefault.rbファイルが用いられた場合、新しいResourceの名前はcookbookとなります。

ファイル名は、resourcesディレクトリとprovidersディレクトリ内のLWRPのResourceProviderに一致しなければいけません。chef generatorコマンドを使えば、適切なファイルを作ってくれます。

Resourceと有効なactionをLWRPのResourceファイルに記述します。

システムの一部を望ましい状態へ導くために必要な手順は、LWRPのProviderファイルに記述します。べき等性と収束性の両方を、Providerを記述する際に考慮しなければいけません。

Resource DSL

LWRPのResourceファイルは、提供したい新しいResourceの特徴をChef Resource DSLを用いて定義します。Resource DSLは、actionsattributedefault_actionという複数のメソッドを持ちます。

Resourcesは名前を持ちます。Resource DSLは、:name_attributeで、Resourceのnameとして特定のパラメータをタグづけできます。

Resourceは動作を持ちます。Resource DSLはactionsメソッドで、サポートする動作の一覧をコンマ区切りのシンボルのリストで定義します。default_actionメソッドで、Recipeで動作を指定しなかった場合の動作を定義します。

注意: 常にdefault_actionを定義することを推奨します。

Resourceはパラメータを持ちます。Resource DSLはattributeメソッドでResourceの新しいパラメータを定義します。各パラメータは検証パラメータと関連づけることができます。

既存のCookbookのLWRP Resourceの例を見ていきましょう。

djbdnsdjbdns_rr Resourceを含んでいます。


actions :add
default_action :add

attribute :fqdn, :kind_of => String, :name_attribute => true
attribute :ip, :kind_of => String, :required => true
attribute :type, :kind_of => String, :default => "host"
attribute :cwd, :kind_of => String

rr Resourceはaddという1つの動作を持ち、fqdniptypecwd、という4つの動作を持つことで定義されています。Attributeの検証パラメータは、すべてのAttributeがStringクラスが期待されていると示しています。加えて、ipがこのResourceをRecipeで用いる際の唯一必須のAttributeです。

Provider DSL

LWRPのProviderファイルは、Chef Provider DSLを用いて新しいResourceが“どうするか”を定義します。

新しいResourceの機能がべき等性と収束性を持つためには、次の要素が必要です。

  • Resourceの望ましい状態
  • Resourceの現在の状態
  • 実行後のResourceの最終的な状態
必要項目 Chef DSL Providerメソッド
望ましい状態 new_resource
現在の状態 load_current_resource
最終的な状態 updated_by_last_action

Chef DSL Providerメソッドの説明のために、既存のCookbookからLWRP Providerの例を見てみましょう。

djbdnsdjbdns_rr を含んでいます。


action :add do
type = new_resource.type
fqdn = new_resource.fqdn
ip = new_resource.ip
cwd = new_resource.cwd ? new_resource.cwd : "#{node['djbdns']['tinydns_internal_dir']}/root"

unless IO.readlines("#{cwd}/data").grep(/^[\.\+=]#{fqdn}:#{ip}/).length >= 1
execute "./add-#{type} #{fqdn} #{ip}" do
cwd cwd
ignore_failure true
end
new_resource.updated_by_last_action(true)
end
end

new_resource

new_resourceは、Resourceの望ましい状態を表現するオブジェクトを返します。すべてのAttributeにオブジェクトのメソッドとしてアクセスできます。これによって、Resourceの望ましい最終状態をプログラム的に知ることができます。

type = new_resource.typeは、rr Resourceをtypeパラメータと共にRecipeで利用した際に作られるnew_resourceオブジェクトのtype Attributeの値を割り当てます。

load_current_resource

load_current_resourceはデフォルトでは空のメソッドです。Resourceの現在の状態を表現するオブジェクトを返すようなメソッドを定義する必要があります。このメソッドはResourceの現在の状態を@current_resourceに読み込みます。

前述の例ではload_current_resourceを用いていません。

updated_by_last_action

updated_by_last_actionはResourceを望ましい状態に収束させる変更が発生したことをChefに通知します。

new_resource.updated_by_last_action(true)を実行するunlessブロックが、Resourceを望ましい状態に収束させる変更が発生したことをChefに通知します。

action

LWRPのResourceファイル中に、サポートするそれぞれの動作についてのメソッドを定義する必要があります。このメソッドは、望ましい状態へResourceを設定するのに必要な、どのような動作も扱えなくてはいけません。

定義されている動作は、LWRPのResourceで定義している動作に一致する:addであることがわかります。

Cooking up a cookies_cookie resource

Preparing our kitchen

まず、料理のためのキッチンの準備を始めましょう! Test KitchenChef DKのツール群の1つとして含まれています。omnibusパッケージは、ワークフローを個人向けに最適化するような多くのツールを含んでいます。では、キッチンに戻りましょう。

注意: Windowsでは、インストールされたパッケージを含むようにPATHが正しく設定されているか検証する必要があります。ChefDK on Windows サバイバルガイド (訳注:和訳)を参照してください。

VagrantVirtual Boxの両方を、まだ準備できていないならダウンロードしてインストールしてください。代わりにAWSを利用するなら、.kitchen.ymlを編集してください。

cookie Recipeのための“cookies”Cookbookを作成しましょう。まず、Cookbookを生成するために、Cookbookのデフォルト生成器としてchef CLIツールを用いましょう。環境に合わせて独自のCookbook生成器を作成することもできます: 独自のChef Cookbookジェネレータを作成する (訳注:和訳)


$ chef generate cookbook cookies
Compiling Cookbooks...
Recipe: code_generator::cookbook

この後にまだまだ出力が続きます。

cookies Cookbookに対する作業を行うために、Cookbookディレクトリ内に移動しましょう。


$ cd cookies

chef generate cookbookを実行すると、事前設定済のファイルをいくつも得られます。そのうちの1つがデフォルトのTest Kitchen設定ファイルです。キッチンの設定は.kitchen.ymlファイルを見ることで調べられます。


$ cat .kitchen.yml

---
driver:
name: vagrant

provisioner:
name: chef_zero

platforms:
- name: ubuntu-12.04
- name: centos-6.5

suites:
- name: default
run_list:
- recipe[cookies::default]
attributes:

driverセクションは、Test Kitchenの動作を設定する部分です。この場合、Chef DKが提供しているkitchen-vagrantドライバを利用することになります。簡単にAWSを用いる設定や他のクラウドコンピューティングプロビジョナを用いる設定に変更することができます。

provisionerは、インストールや管理の手間が必要ないChef Serverであるchef_zeroの機能の多くを用います。

platformsはテストを行いたいオペレーティングシステムを定義します。ここでは、このファイルで定義されているCentOSプラットフォームのみで作業します。Ubuntuの行は削除するかコメントアウトしてください。

suitesはどのようなテストを行いたいかを定義する部分です。cookbook::default Recipeを含むrun_listを指定しています。

次に、CentOSインスタンスを起動しましょう。

注意: Test Kitchenは、ワークステーションに存在していなければ自動的にVagrant Boxファイルをダウンロードします。十分な速度が出るネットワークに接続しておきましょう!


$ kitchen create

インスタンスが作成されたことを確認しましょう。


$ kitchen list

➜ cookies git:(master) ✗ kitchen list
Instance Driver Provisioner Last Action
default-centos-65 Vagrant ChefZero Created

これでローカルに仮想ノードが作成されたことが確認できます。

仮想ノードにChefをインストールしてNodeの収束をさせましょう。


$ kitchen converge

Cookie LWRPの準備

LWRPのResourceファイルとProviderファイルを作り、デフォルトのRecipeを更新する必要があります。

Chef DKに含まれているchef cliツールを用いてLWRPの雛形ファイルを作成します。これはresources/cookie.rbファイルとproviders/cookie.rbを作成します。


$ chef generate lwrp cookie

cookie LWRP Resourceファイルを編集し、createという単一のサポートする動作を追加しましょう。

次の内容をresources/cookie.rbファイルに追加します。


actions :create

次に、cookie LWRP Providerファイルを編集し、サポートするcreateの動作を定義します。createメソッドはnew_resourceの名前を含むログメッセージを標準出力に書き出します。

次の内容をproviders/cookie.rbファイルに追加します。


use_inline_resources

action :create do
log " My name is #{new_resource.name}"
end

注意: use_inline_resourcesはChef 11で導入されました。この変更は、LWRP ResourceがどのようにResourceのインライン評価を有効に扱うかを変更しました。これは通知がどのように動作するかを変更したので、現在のLWRPを変更する前に慎重に確認してください。

注意: Chef Resource DSLメソッドはactionsです。Providerファイル中で個別に定義できるactionを複数定義できるからです。

では、これらを用いたRecipeを書くことで新しいResourceの機能をテストしましょう。cookie CookbookのデフォルトRecipeを編集してください。新しいResourceは#{cookbookname}_#{resource}の形式で命名されます。


cookies_cookie "peanutbutter" do
action :create
end

イメージを再度収束させましょう。


$ kitchen converge

次のような出力になります。


Converging 1 resources
Recipe: cookies::default
* cookies_cookie[peanutbutter] action create[2014-12-19T02:17:39+00:00] INFO: Processing cookies_cookie[peanutbutter] action create (cookies::default line 1)
(up to date)
* log[ My name is peanutbutter] action write[2014-12-19T02:17:39+00:00] INFO: Processing log[ My name is peanutbutter] action write (/tmp/kitchen/cache/cookbooks/cookies/providers/cookie.rb line 2)
[2014-12-19T02:17:39+00:00] INFO: My name is peanutbutter

cookies_cookie Resourceが正しくメッセージを出力しました。

Cookie LWRPの改良

cookies_cookie Resourceを改良したくなったとしましょう。いくつかのパラメータを追加するとします。LWRPの適切なパラメータを決定するには、変更したいResourceの構成要素について検討する必要があります。

クッキーの基本的な共通の構成要素があります。必須の要素としては、油脂、つなぎ、甘味料、ふくらし粉、小麦粉、チョコチップやピーナッツバターのような添加物です。油脂は味、きめ、バターです。つなぎは成分を互いにくっつける“接着剤”のようなものです。甘味料は色、味、きめ、やわらかさに影響を与えます。ふくらし粉はクッキーに空気を送り込んできめと盛りに変化を与えます。小麦粉はクッキーの構造の大部分と同じようなきめを提供します。添加物はクッキーの味に違いを与えます。

基本的なレシピでは、水気のある具材と乾燥した具材を別々にかき混ぜて、それから両方を混ぜて最後に添加物を入れます。ここでは、すべての具材を1つのパラメータにまとめてしまいましょう。

他の要素では、クッキーをどれくらいの温度でどれくらいの時間焼けばいいのか知る必要があります。

LWRP Resourceにパラメータを追加する際、attributeキーワードから始めて、Attributeの名前と0個以上の検証パラメータを続けます。

resources/cookie.rbファイルを編集します。


actions :create

attribute :name, :name_attribute => true
attribute :bake_time
attribute :temperature
attribute :ingredients

これらのAttributeを組み込むようにRecipeを更新しましょう。


cookies_cookie "peanutbutter" do
bake_time 10
temperature 350
action :create
end

Data Bagの利用

要素を文字列か配列に入れられるのなら、コードから分離してしまいましょう。そうするための方法の1つがData Bagです。

クッキーの具材を入れておくのにdata_bagを利用しましょう。本番のdata_bagsは通常は、Organization policy_repoにあるCookbookの外に存在します。chef_zeroを用いて開発しているので、Data BagはCookbookのtest/integration/data_bagsディレクトリに含めておきます。

これを開発環境で行うには、chef_zeroがdata_bagsを見つけられるように、.kitchen.ymlを更新しておきます。

新しいResource機能のテストのために、.kitchen.ymlのデフォルトのsuiteセクションに次を追加します。


data_bags_path: "test/integration/data_bags"

この時点で.kitchen.ymlのようになっているはずです。


---
driver:
name: vagrant

provisioner:
name: chef_zero

platforms:
- name: centos-6.5

suites:
- name: default
run_list:
- recipe[cookies::default]
attributes:
data_bags_path: "test/integration/data_bags"

peanutbutter.jsonという名前のファイルをディレクトリ内に作成することで、ピーナッツバターを格納しておくcookies_ingredients data_bagを作成しましょう。


{
"id" : "peanutbutter",
"ingredients" :
[
"1 cup peanut butter",
"1 cup sugar",
"1 egg"
]
}

実際にcookies_ingredients data_bagを用いるようにRecipeを更新しましょう。


search('cookies_ingredients', '*:*').each do |cookie_type|
cookies_cookie cookie_type['id'] do
ingredients cookie_type['ingredients']
bake_time 10
temperature 350
action :create
end
end

では、実際に入力パラメータを検証するようにLWRP Resourceを更新し、ノードにファイルを作るようにProviderを更新し、Attributeを使うようにしましょう。また、Resourceに“eat”Actionを追加しましょう。

resources/cookie.rbファイルを次のように編集します。


actions :create, :eat

attribute :name, :name_attribute => true
# bake time in minutes
attribute :bake_time, :kind_of => Integer
# temperature in F
attribute :temperature, :kind_of => Integer
attribute :ingredients, :kind_of => Array

標準出力にログを書き出す代わりにノードにファイルを作成するようにProviderを更新しましょう。Providerにtemplate Resourceを用いるので、必要なテンプレートを作成します。

テンプレートファイルを作成します。


$ chef generate template basic_recipe

templates/default/basic_recipe.erbを次のように編集します。


Recipe: <%= @name %> cookies

<% @ingredients.each do |ingredient| %>
<%= ingredient %>
<% end %>

Combine wet ingredients.
Combine dry ingredients.

Bake at <%= @temperature %>F for <%= @bake_time %> minutes.

ではcookie Providerをこのテンプレートを用いるように更新し、テンプレートにはAttributeを渡すようにしましょう。また、eat actionを定義し、createで作成したファイルを削除するようにします。

providers/cookie.rbファイルを次のように編集します。


use_inline_resources

action :create do

template "/tmp/#{new_resource.name}" do
source "basic_recipe.erb"
mode "0644"
variables(
:ingredients => new_resource.ingredients,
:bake_time => new_resource.bake_time,
:temperature => new_resource.temperature,
:name => new_resource.name,
)
end
end

action :eat do

file "/tmp/#{new_resource.name}" do
action :delete
end

end

更新したLWRPをTest Kitchenで収束させてみます。


$ kitchen converge

ノードにログインし、peanutbutter Resourceが作成されたことを確認してみましょう。


$ kitchen login

/tmp/peanutbutterファイルが作成されています。確認しましょう。


[vagrant@default-centos-65 ~]$ cat /tmp/peanutbutter
Recipe: peanutbutter cookies

1 cup peanut butter
1 cup sugar
1 egg

Combine wet ingredients.
Combine dry ingredients.

Bake at 350F for 10 minutes.

ではeat Actionを試してみましょう。Recipeを次のように更新します。


search("cookies_ingredients", "*:*").each do |cookie_type|
cookies_cookie cookie_type['id'] do
action :eat
end
end

ノードを収束させてログインし、ファイルがなくなってしまっていることを確認します。


$ kitchen converge
$ kitchen login
Last login: Fri Dec 19 05:45:23 2014 from 10.0.2.2
[vagrant@default-centos-65 ~]$ cat /tmp/peanutbutter
cat: /tmp/peanutbutter: No such file or directory

クッキーを追加するには、新しいdata_bag itemを追加するだけです。

キッチンの片付け

最後に、キッチンで本日テストを終えたなら、kitchen destroyで仮想インスタンスを片付けましょう。


$ kitchen destroy

次の手順

ピーナッツバタークッキーづくりに成功したところで、LWRPでChefを拡張することのほんのさわりを見ただけに過ぎません。Jon Cowieの「Customizing Chef」8章Doug IretonのLWRP作成に関する3つの記事が参考になるでしょう(訳注:『Chef活用ガイド コードではじめる構成管理』13章も参照してください)。load_current_resourceupdated_by_last_actionを用いてこの例を拡張・検査してみてください。why_run機能をどのように動かすかも調べてみてください。ChefコミュニティでLWRPを共有していただけることを楽しみにしています!

疑問や質問はiennae@gmail.comまでお寄せください(訳注:英語で)。

追加資料

謝辞

クッキーがおいしくなるように助けていただいたすばらしい著者陣に感謝します!

新規CTA