[和訳] 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)、Vagrant、Virtual Boxのインストールが必要です。もしAmazonのようなクラウドコンピューティングプロバイダを利用するなら、Chef DK同梱の.kitchen.yml設定を修正してください。
■ResourceとProviderの復習
ResourceはChefの基本的な組み立てブロックです。Chefには多くのResourceが同梱されています。Resourceは宣言型インターフェイスを持ち、これはつまり、ある状態に到達するために必要な手順を記述するというより、Resourceがどのような状態であってほしいかを記述することを意味します。Resourceはtype、name、1つ以上のパラメータ、actions、notificationsを持ちます。
それではResourceの例としてrouteを見てみましょう。
route "NAME" do
gateway "10.0.0.20"
action :delete
end
route Resourceはシステムのルーティングテーブルを記述します。Resourceのtypeはrouteです。Resourceのnameはtypeに続く文字列です。route Resourceはdevice、gateway、netmask、provider、targetといった任意のパラメータを含みます。この例では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の構成要素であるtype、name、パラメータ、actions、notificationsを再検討する必要があります。
べき等性と収束性も考慮しなければいけません。
べき等性が意味するところは、望ましい状態またはポリシーに追従するようResourceを導く必要がある変更がある場合のみ、ProviderはResourceの状態を変更するということです。
収束性の意味するところは、Providerは現在のResourceの状態を望ましいResourceの状態に近づけていくということです。
Resourceはtypeを持ちます。LWRPのResource TypeはCookbook内のファイル名によって定義されます。これはcookbook_resource式によって明示的に命名されます。もしdefault.rbファイルが用いられた場合、新しいResourceの名前はcookbookとなります。
ファイル名は、resourcesディレクトリとprovidersディレクトリ内のLWRPのResourceとProviderに一致しなければいけません。chef generatorコマンドを使えば、適切なファイルを作ってくれます。
Resourceと有効なactionをLWRPのResourceファイルに記述します。
システムの一部を望ましい状態へ導くために必要な手順は、LWRPのProviderファイルに記述します。べき等性と収束性の両方を、Providerを記述する際に考慮しなければいけません。
■Resource DSL
LWRPのResourceファイルは、提供したい新しいResourceの特徴をChef Resource DSLを用いて定義します。Resource DSLは、actions、attribute、default_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の例を見ていきましょう。
djbdnsはdjbdns_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つの動作を持ち、fqdn、ip、type、cwd、という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の例を見てみましょう。
djbdnsはdjbdns_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 KitchenがChef DKのツール群の1つとして含まれています。omnibusパッケージは、ワークフローを個人向けに最適化するような多くのツールを含んでいます。では、キッチンに戻りましょう。
注意: Windowsでは、インストールされたパッケージを含むようにPATHが正しく設定されているか検証する必要があります。ChefDK on Windows サバイバルガイド (訳注:和訳)を参照してください。
VagrantとVirtual 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_resourceとupdated_by_last_actionを用いてこの例を拡張・検査してみてください。why_run機能をどのように動かすかも調べてみてください。ChefコミュニティでLWRPを共有していただけることを楽しみにしています!
疑問や質問はiennae@gmail.comまでお寄せください(訳注:英語で)。
■追加資料
- Doug IretonのLWRP作成に関する3つの記事
- Creating your own chef Cookbook Generator (訳注: 和訳)
- Supermarket - コミュニティCookbookサイト
- Science behind the Best Chocolate Chip Cookies
- Christmas Cookies from around the world
- Nathen Harvey's Cookie Cookbook Repo
■謝辞
クッキーがおいしくなるように助けていただいたすばらしい著者陣に感謝します!