Chef の Cookbooks を一発で NIFTYCloud クラウドストレージにアップロードする ncss-cookbooks コマンド

ニフティクラウド上で手軽に Chef を試したいときには、ニフティクラウドストレージへ tgz 形式でアップロードした上で chef-solo コマンドの -r オプションで URL を指定する方法が便利です。

$ chef-solo -j node.json -r https://my-bucket.ncss.nifty.com/v0.0.0/cookbooks.tgz

上記コマンドを打つと、chef-solo は -r に指定された tgz アーカイブファイルをウェブ経由で取得し、ローカルへ展開した上で cookbooks を実行してくれます。

chef-solo -r で取得できるのは便利なのですが、いちいち cookbooks を tgz に書庫化してニフティクラウドストレージへアップロードする部分が面倒なので、これを自動化するコマンドを作りました。

インストール

$ gem install ncss-cookbooks

使い方

管理している cookbooks レポジトリの直下に VERSION というファイルを作成してバージョンを書き込みます。

$ echo "0.0.0" > VERSION

環境変数ニフティクラウドストレージの API キーを設定します。

$ export ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
$ export SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

あとはバケットを作成するコマンドと、作成したバケットに cookbooks.tgz をアップロードするコマンドを叩くだけです。

$ ncss-cookbooks create my-bucket
$ ncss-cookbooks upload my-bucket
Version is v0.0.0
Creating directory /tmp/ncss-cookbooks-20130519-30448-2im9m1/cookbooks/
Copying files from /home/tily/dev/ncss-cookbooks to /tmp/ncss-cookbooks-20130519-30448-2im9m1/cookbooks/
  Excluded /home/tily/dev/ncss-cookbooks/.git
Archiving /tmp/ncss-cookbooks-20130519-30448-2im9m1/cookbooks/ to /tmp/ncss-cookbooks-20130519-30448-2im9m1/cookbooks.tgz
Uploading /tmp/ncss-cookbooks-20130519-30448-2im9m1/cookbooks.tgz to bucket:my-bucket, object:v0.0.0/cookbooks.tgz
Temporary directory /tmp/ncss-cookbooks-20130519-30448-2im9m1 will be deleted automatically
Uploaded to https://my-bucket.ncss.nifty.com/v0.0.0/cookbooks.tgz

発行された URL は chef-solo -r から利用できます。

$ chef-solo -j node.json -r https://my-bucket.ncss.nifty.com/v0.0.0/cookbooks.tgz

あとがき

  • 特に大したことはやっていないのですが、バージョン管理の運用と組み合わせて使えるので結構便利なのではないかと思います
  • 最初 knife コマンドに組み込もうと思ったのですが、knife は色々なファイルをロードして結構重いので単体のコマンドにしてみました
  • 個人的には前に作ったニフティクラウドを vagrant のように使える some コマンドと組み合わせて使おうと思っています
  • そもそも chef-solo -r が BASIC 認証等に対応していないので cookbooks.tgz を認証なしの URL に置かなければならないのが少し厳しい感じがします、やはり本格的に運用するなら Chef サーバーでしょうか

ニフティクラウドを vagrant のように使える some コマンド

heroku の中の人が作った sumo (相撲)というツールがあります。vagrant のような感覚で amazon ec2インスタンスを作ったり Chef で設定したり削除できたりするやつです。これを修正してニフティクラウドでも使えるようにしてみました。独自の機能もいろいろ追加してあります。
詳しくは上記 github の README を参照していただきたいのですが、ここでは mysql サーバーを立てる例を簡単に説明してみます。

インストール

rubygems からインストールします。

$ gem install some

設定ファイルを書く

~/.some ディレクトリを作成し、~/.some/config.yml に下記の設定を書き込みます。

  • ~/.some/config.yml
---
access_key: XXXXXXXXXXXXXXXXXXXX
secret_key: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
cookbooks_url: http://some.ncss.nifty.com/cookbooks.tgz
role:
  mysql: |
    {
      "run_list": [
        "recipe[mysql::server]"
      ],
      "mysql": {
        "server_root_password": "mysql",
        "server_debian_password": "mysql",
        "server_repl_password": "mysql",
        "bind_address": "ZZZ.ZZZ.ZZZ.ZZZ",
        "allow_remote_root": true
      }
    }

some コマンド実行

実行するのは 2 つのコマンドのみです。

$ some launch mysql
---> Launch instance...       0ebfa18a (8.4s)
---> Acquire hostname...      AAA.AAA.AAA.AAA (82.5s)
---> Wait for ssh...          done (0.0s)
---> Bootstrap chef...        done (63.3s)
---> Setup mysql...           done (145.4s)
$ some openfw 3306
port 3306 scheduled for open

ここまで終われば SSH ログインができるようになっているし、

$ some ssh AAA.AAA.AAA.AAA
Enter passphrase for key '/home/tily/.some/keypair.pem': (パスワードを入力)
[root@localhost ~]# 

外部から mysql サービスに接続できるようになっています。便利。

$ mysql -h AAA.AAA.AAA.AAA -u root -p
Enter password: (パスワードを入力)
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.1.67-log Source distribution

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql>

何が行われているか

簡単に流れを説明します。

$ some launch mysql
  • 存在しなければ something という名前の FW を作成する
  • 存在しなければ something という名前の SSH キーを作成してローカルの ~/.some/keypair.pem に保存する
  • FW=something, SSH キー=something でサーバーを作成する
  • サーバーが起動し SSH が疎通するまで待つ
  • SSH が疎通したら SSH 経由でサーバーに Chef をインストールし /etc/chef/solo.rb を設定する
  • config.yml に書いてある mysql の設定をサーバーの /etc/chef/dna.json に置く
  • config.yml の cookbooks_url から cookbooks を取得し chef-solo を実行する
$ some openfw 3306
  • something FW の 3306 ポートを開放する

という感じです。ちなみに要らなくなったサーバーはコマンド一発で削除することができます。

$ some terminate 0ebfa18a
---> Wait to stop...          done (20.8s)
AAA.AAA.AAA.AAA scheduled for termination

編集後記

自分でガシガシ使えそうなツールが作れたのでよかった。
あと最近 vagrant が流行っているようなんだけど、個人的にはクラウドは面倒を他人に委ねることだから、sumo や cucumber-chef のように本番環境だけでなく検証やテストも外部に委ねられるようになるのが未来だと思っています。

cookbook とか用意しなくても chef を試せる patty.rb というスクリプトを書いた

オハイ! Chef はよくも悪くも仕組みが大げさなので、思いついたときにぱっと試すのが面倒くさいと前々から思っていた。
なんとか cookbook を作らなくても簡単に試す仕組みを作れないかなと思って、自分のアプリケーションから Chef Solo を呼び出す — Gist とか 手軽に chef-solo を実行するためにスケルトンを作ってくれるやつ (ユースケース要検討) — Gist とか色々試していたんだけど、先日Opscode 社 Chef ハンズオン・トレーニングに出席したときに shef がかなり理想に近いことが分かったので、shef のコードを読み、インタラクティブ・シェルではなく 1 枚のファイルの中にレシピとテンプレートを記述できる仕組みを作ってみた。

こんな感じのレシピファイルを作成して、

  • recipe.rb
file '/tmp/hello.txt' do
  content "hello!\n"
end

template "/tmp/hello2.txt" do
  local true
  source "hello"
  variables :name => 'world'
end

template "/tmp/foobar.txt" do
  local true
  source "foobar"
  variables :name => 'tily'
end

__END__
@@ hello
hello, <%= @name %>
@@ foobar
this is another template: name is <%= @name %>

こんな風に実行すると、

  • 実行方法
ruby patty.rb recipe.rb

chef-solo っぽくスタンドアロンで Chef を実行できる。

  • 実行結果
bash-3.2$ ruby patty.rb recipe.rb 
Loading....[2012-11-10T12:48:58+09:00] DEBUG: Building node object for linwttaa.local
[2012-11-10T12:48:58+09:00] DEBUG: Extracting run list from JSON attributes provided on command line
[2012-11-10T12:48:58+09:00] DEBUG: Applying attributes from json file
[2012-11-10T12:48:58+09:00] DEBUG: Platform is mac_os_x version 10.7
[2012-11-10T12:48:58+09:00] INFO: Run List is []
[2012-11-10T12:48:58+09:00] INFO: Run List expands to []
done.

[2012-11-10T12:48:59+09:00] INFO: Processing file[/tmp/hello.txt] action create ((eval) line 2)
--- /var/folders/9q/tq6hwz3j7qd_nctywdr_4mp40000gn/T/chef-tempfile20121110-23863-enwe0z 2012-11-10 12:48:59.000000000 +0900
+++ /var/folders/9q/tq6hwz3j7qd_nctywdr_4mp40000gn/T/chef-diff20121110-23863-vo0wbv     2012-11-10 12:48:59.000000000 +0900
@@ -0,0 +1 @@
+hello!
[2012-11-10T12:48:59+09:00] INFO: entered create
[2012-11-10T12:48:59+09:00] INFO: file[/tmp/hello.txt] created file /tmp/hello.txt
[2012-11-10T12:48:59+09:00] INFO: Processing template[/tmp/hello2.txt] action create ((eval) line 6)
[2012-11-10T12:48:59+09:00] DEBUG: Current content's checksum:  
[2012-11-10T12:48:59+09:00] DEBUG: Rendered content's checksum: 853ff93762a06ddbf722c4ebe9ddd66d8f63ddaea97f521c3ecc20da7c976020
--- /var/folders/9q/tq6hwz3j7qd_nctywdr_4mp40000gn/T/chef-tempfile20121110-23863-4d1443 2012-11-10 12:48:59.000000000 +0900
+++ /var/folders/9q/tq6hwz3j7qd_nctywdr_4mp40000gn/T/chef-rendered-template20121110-23863-8svaro        2012-11-10 12:48:59.000000000 +0900
@@ -0,0 +1 @@
+hello, world
[2012-11-10T12:48:59+09:00] INFO: template[/tmp/hello2.txt] updated content
[2012-11-10T12:48:59+09:00] INFO: Processing template[/tmp/foobar.txt] action create ((eval) line 12)
[2012-11-10T12:48:59+09:00] DEBUG: Current content's checksum:  
[2012-11-10T12:48:59+09:00] DEBUG: Rendered content's checksum: 04fc2a8f0333e6412a408e347ca49aab17b1b179f42be3d4cc2fd42fc4bcdf50
--- /var/folders/9q/tq6hwz3j7qd_nctywdr_4mp40000gn/T/chef-tempfile20121110-23863-1woio2b        2012-11-10 12:48:59.000000000 +0900
+++ /var/folders/9q/tq6hwz3j7qd_nctywdr_4mp40000gn/T/chef-rendered-template20121110-23863-1ai39uj       2012-11-10 12:48:59.000000000 +0900
@@ -0,0 +1 @@
+this is another template: name is tily
[2012-11-10T12:48:59+09:00] INFO: template[/tmp/foobar.txt] updated content

実験的なスクリプトなのであまり品質は高くないかも。でもなんかこういう「Chef をカンタンに使える仕組み」がもっとあっていいと思っている。

Opscode 社 Chef ハンズオン・トレーニング

ブログを書くのが遅れてしまったのですが、10/5(金) 10:00〜17:00 に「Opscode 社 Chef ハンズオン・トレーニング」というものに参加してきました。
Opscode 社員である Sean OMeara 氏から Chef の中級的な使い方を教えてもらうというもので、非常に内容が濃く充実した時間を過ごすことができました。

内容については jedipunkz さん・tkak さんがかなり詳しく書いてくださっているので (ありがとうございます!)、この記事には個人的におもしろかったところや感想を書いておきます。

参考:

構成管理の歴史

Sean 氏曰く、従来型の構成管理では下に挙げる 4 つのアプローチが採用されてきました。

  • 手動で設定を行う (Manual Configuration)
  • スクリプトを書く (Scripting)
  • ファイル配布 (File Distribution)
    • NFS mounts, rdist, scp-on-a-for-loop, rsync on cron
  • 実行管理 (Execution Management)
    • Cluster SSH、Golden Images、ISConf

スクリプトを書く (Scripting)

上記はいずれも欠点を抱えており、「手動で実行する」というのはサーバが増えれば増えるほど非効率になるためもちろん論外ですが、一見筋の良さそうな「スクリプトを書く」というアプローチにも不便なところがあります。

Sean 氏は "not safe to run twice" (2 回実行したときに安全ではない) と表現されていましたが、このことについて自分の言葉で少し説明してみます。

そもそも「環境構築のためにスクリプトを書く」とき、ほとんどのケースでは「まっさらなサーバ」に対して「1 度だけ実行する」こと、つまり「初期構築」専用であることが前提となっているように思います。

これは当然のようですが、実は効率が悪い面もあります。なぜなら「環境」というのは 1 度作ったあとで要件が変わり、変更を施す必要がかならずと言っていいほど出てくるからです。

こんなとき「スクリプトを書く」アプローチでは、下記のどれかで対処するかと思います。

  • 仕方ないから新しい変更点だけは手動で適用してしまう
  • 新しい変更点を手動で適用し、古くなったスクリプトの末尾にも書き加えておく
  • 新しい変更点を加える 2 つめのスクリプトを作成して実行する
  • 古いスクリプトで実行した結果を全て切り戻した上で、修正したスクリプトを実行する
  • サーバを一度初期化し、修正したスクリプトを実行する

どれもイマイチだし、そもそも「一度作成したスクリプトをどう保守していくか」という運用指針を考えるのがすごく面倒くさいです。
(あとサーバの運用がすでに開始されている場合には、4・5 番目は使えなかったり。)

これに対して Chef がサーバに設定を反映する過程は "convergence" (収束、あるべき状態に持っていく) と呼ばれており、「レシピ」は「初期構築」専用ではなく何度も実行することを前提としています。

たとえばレシピでは「ファイルへの書き込み」をこのように記述することができますが、

file "/tmp/file1" do
 content "hi there!\n"
end

これは「初期構築時に 1 回だけファイルを書き込む」ことを意味しているわけではありません。実際の挙動は下記のようになります。

  • もし /tmp/file1 が存在しない場合
    • /tmp/file1 を作成し、"hi there!\n" という文字列を書き込む
  • もし /tmp/file2 が存在する場合
    • 内容が "hi there!\n" だった場合
      • なにもしない
    • 内容が "hi there!\n" ではなかった場合
      • /tmp/file2 を上書きし、"hi there!\n" という文字列を書き込む

このように比較してみると Chef が「2 度実行しても安全」なのがよく分かると思います。
この仕組みなら 1 つのレシピを、初期構築だけでなく運用時のどの場面でも差分適用のために利用できます。

(もちろん、スクリプトでも「ファイルが存在しない場合にだけ書き込む」といった convergence を実装することはできますが、それはすなわち独自で Chef を実装するようなものなので、最初から Chef を使っておいたほうが効率がよいです。)

ファイル配布 (File Distribution)

次に、これもよく使われているように思うのですが、NFS / rsync 等の方法で複数のサーバへディレクトリ構造ごとファイルを同期する「ファイル配布」という方法があります。
Sean 氏は「スクリプトを書くよりはマシだけど『Apache を再起動する』とかサービス管理は行えない」と言っていました。
まさにその通りで、静的にディレクトリ構造を同期するだけなので、サーバの内部で行わなければならない「サービス起動」「シンボリックリンク張り替え」「Cron 登録」等は行うことができません。別途 capistrano 等の他の仕組みと組み合わせるのがメジャーでしょうか。

実行管理 (Execution Management)

Sean 氏によると今いちばん普及しているのが「実行管理」という手法らしいです。
勉強不足で「実行管理」というのがどういうくくりなのかいまいちピンとこなかったのですが、例としては "Cluster SSH、Golden Images、ISConf" が挙げられていました。

このうち "Cluster SSH" というのは capistrano や tomahawk 等、複数サーバへ SSH 経由でコマンド実行する仕組みのことを指しているのだと思います。Sean 氏は「サーバの台数が極端に増えたときにスケールしない」というようなことを言っていたように思います (うろ覚え)。

Golden Images というのはあとから調べて分かったのですが、設定済みのサーバを再利用可能なイメージの形で保存しておき、使い回す手法のようです (クラウドが主流になってからは非常によく使われている手法だと思います。)

「分かりやすい」「すぐにサーバを作れる」「どんな OS でも使える」のがメリットですが、下記のようなデメリットもあります。

  • Web サーバ/DB サーバ/DNS サーバ等、サーバの種類に応じて多数のイメージを管理する必要がある
  • イメージに対して加えた変更の履歴が管理できない
  • イメージに対して加えた変更を、運用中のサーバに加える際にはリブートが必要になる

参考:

説明してきた通り、構成管理には本当にさまざまなやり方があり、そしてそのどれもが「なんかイマイチ」だったのですが、そういう欠点を考慮した上で解決策 (pull 型アーキテクチャや convergence) を提示しようとしてきたのが CFEngine/Puppet/Chef の流れだったんだろうな、というのをしみじみと感じました。

shef の使い方

shef というツールがあるのは知っていたけど、どういう風に使うのかあまり具体的な例を見れてよかったです。
下記が実際に Sean 氏が打っていたコマンド。人に Chef がどんなものかを説明する際にも使えそうに思いました。

[root@localhost ~]# shef --solo
chef > attributes
chef:attributes > default['foo']['bar']='baz'
chef:attributes > ^D
chef > node['foo']['bar']
chef > recipe
chef:recipe > file "/tmp/file1" do
chef:recipe >   content "hi there!\n"
chef:recipe ?> end
chef:recipe > file "/tmp/file2" do
chef:recipe >   content "hi there again!\n"
chef:recipe ?> end
chef:recipe > file "/tmp/file3" do
chef:recipe >   content "blah\n"
chef:recipe ?> end
chef:recipe > run_chef

chef:recipe > # C-z で抜けて /tmp/file1 を消したり /tmp/file2 の内容を書き換えたりして、
chef:recipe > # fg で戻り run_chef を実行し「同じ状態」に保ってくれることを確かめていた。

chef:recipe > resources # 誰かが質問していたリソースの一覧を見るコマンド

Tab キーでメソッドの補完をしてくれるのも便利そうでした (これは irb 自体の機能か…)。

chef > node.na[Tabキー]
node.name            node.name=           node.name_type       node.named_captures  node.names           node.nan?          
node.native

その他雑多なメモ

  • 環境 (environment) について
    • 検索時に環境で絞り込む haproxy cookbook の例 (solr のクエリに "AND chef_environment:#{node.chef_environment}")
    • 環境は cookbook のバージョンを管理するために使える
    • タグ (tag) はあまり使われていないっぽい (環境のように cookbook のバージョン管理もできない)
  • why-run について
  • Chef は基本的に pull だが push したいときにどうするのか?
    • 99% は Chef、最後の 1% は capistrano や fabric に任せる (スケールしない)
    • 手動で chef を実行する
    • data_bags を使って chef 実行のタイミングを制御する

まとめ

とりとめのないメモになってしまいましたが、Chef を開発している Opscode 社の社員が Chef をどんな風に使いこなしているのかを実演してもらえたのがすごく良かったです。
また機会があればぜひ参加したいと思いました。英語が話せなくて質問ができなかったので、今度はがんばって英語で質問したりしたい。
最後になりましたが、主催者の皆様、お誘いありがとうございました&お疲れ様でした。

オープンソースカンファレンス2012 Tokyo/Fall で話した資料を公開しました

すっかり忘れていたけど 2012/09/08 に オープンソースカンファレンス2012 Tokyo/Fall で Japan Chef Users Group として発表した資料を公開しています。前半は基礎的な話だけど、後半は割とあまり日本のウェブ界隈では取り上げられていない内容のような気がする。よかったらご参照ください。 (資料はブラウザで見るタイプのものなんだけど、Google Chrome でしか動作確認していないので他ブラウザだと崩れるかもです)

Chef Cookbook のスタイルについて参考になりそうなサイト一覧

Chef は自由すぎて cookbook をどう書けばいいか迷ったり、人によってかなりスタイルが変わってきたりするので、こういうドキュメントは結構有益だと思う。時間のあるときに日本語へ翻訳したい。

Chef Cookbook のスタイルをチェックしてくれる Foodcritic を使ってみた

Foodcritic というのは「Chef の Cookbook のスタイルをチェックしてくれる Lint ツール」みたいなやつで、どんなものなのか気になったので少し触ってみた。

インストール

$ rvm use 1.9.3
$ gem install foodcritic
$ foodcritic # 正常にインストールされると foodcritic コマンドが使えるようになる

とりあえず使ってみる

「使ってみる」と言っても foodcritic [チェックしたい cookbook のパス] を実行するだけ。デフォルトではこのページに載っている 32 個のルールでチェックが行われる。試しに Opscode 公式の mysql cookbook をチェックしてみた。公式なのにけっこう指摘が出る…。

$ mkdir try-foodcritic
$ cd try-foodcritic
$ git clone git://github.com/opscode-cookbooks/mysql.git
$ ls
mysql
$ foodcritic mysql
FC002: Avoid string interpolation where not required: mysql/attributes/server.rb:70
FC007: Ensure recipe dependencies are reflected in cookbook metadata: mysql/recipes/client.rb:42
FC019: Access node attributes in a consistent manner: mysql/attributes/server.rb:105
FC023: Prefer conditional attributes: mysql/recipes/server.rb:142
FC024: Consider adding platform equivalents: mysql/recipes/server.rb:117
FC024: Consider adding platform equivalents: mysql/recipes/server.rb:119

他のルールを適用してみる

デフォルトのルールだけではなく自分でルールを作ったりもできるっぽい。ほんとは自分でルールを作ってみたかったんだけど、特にめぼしいものを思いつかなかったので、Etsy 社が公開しているルールを試してみた。"-I [ルールを記述した ruby ファイルへのパス]" を指定することができる。

$ git clone https://github.com/etsy/foodcritic-rules.git etsy-rules
$ ls
etsy-rules mysql
$ foodcritic -I etsy-rules/rules.rb mysql
ETSY005: Action :restart sent to a core service: mysql/recipes/server.rb:132
FC002: Avoid string interpolation where not required: mysql/attributes/server.rb:70
FC007: Ensure recipe dependencies are reflected in cookbook metadata: mysql/recipes/client.rb:42
FC019: Access node attributes in a consistent manner: mysql/attributes/server.rb:105
FC023: Prefer conditional attributes: mysql/recipes/server.rb:142
FC024: Consider adding platform equivalents: mysql/recipes/server.rb:117
FC024: Consider adding platform equivalents: mysql/recipes/server.rb:119

感想

Chef は自由すぎて、どう cookbook を書けばいいかよく分からなかったり、書く人によって結構スタイルがバラバラになってしまったりするので、こういう仕組みは有益だと思う。

あと、Travis CI や Jenkins 等の継続的インテグレーションツールに組み込んで使うのもおもしろそうに思った。なかなかオリジナルの cookbook とか公開できていないんだけど、公開するときには Travis + foodcritic とか試してみたい。