Chefのハンドラを使ってSensuやSlackと組み合わせる方法
みなさんこんにちは。@ryuzeeです。
Chef Serverを使って大規模なインフラを自動運用している場合、当然のことながら各ノードできちんとChef Clientが動作して、指定したロールやクックブックが実行されていることを確認しなければいけません。 残念なことに、継続的インテグレーションでクックブックの品質を常時担保していたとしても、実際にそのクックブックをノードに適用する歳に正常終了するとは限りません。たとえばパッケージの配布元が高負荷で落ちていたといったこともあります。 つまり、Chef Clientが正常に実行が終わったかどうかは監視の対象になるということです。
そこで今回は、Chefのハンドラを使って、SensuやSlackに通知する方法を紹介しましょう。
Chefのハンドラってなんぞ?
詳細は公式サイトを見ていただくのが一番ですが、ハンドラとは処理の前後や例外の発生時に別の処理を呼び出す機能です。例えばこれらの場合に外部への通知をおこなったりデータを取得して他の場所に蓄積したりといった用途に使えます。
ハンドラには3種類あります。
- Exception Handler:Chef Clientの実行に失敗したときに呼ばれます
- Report Handler:Chef Clientの実行に成功した場合に呼ばれます。例えば変更内容を通知したりするのに使えます
- Start Handler:Chef Clientの実行が開始された場合に呼ばれます
また、ハンドラを設定するための方法は2つあります。
- レシピの中で
chef_handler
のリソースを指定する - ノード側のclient.rbで設定をおこなう
Chef Clientの実行が失敗した場合にSlackに通知する
では、まずは、Chef Clientの実行が失敗した場合にメジャーなチャットツールの1つであるSlackに通知する方法を見て行きましょう。 ここでは、GitHubで公開されているchef-handler-slackを使い、レシピの中でchef_handler
リソースを指定する方法を使って設定してみます。
SlackのTokenを取得する
まずはSlackに対してメッセージを送信するために、Tokenを取得します。SlackのIntegrations設定の画面に移動してください。 ページ下部に以下のような箇所がありますので、Incoming WebHooks
を選択します。

選択すると、以下のような画面が表示されますので、既存のチャンネルを設定するか新たなチャンネルを作成します。

Add Incoming WebHook
をクリックすると、以下のような画面が表示され、Tokenの生成が完了します。図の中でグレーになっている箇所がTokenになります。

Cookbookの作成
いつも通りの方法でクックブックを作成してください。knifeとかBerkshelfとか使えばOKです。
Berksfile
に依存するchef_handler
を追加します。
source "https://supermarket.getchef.com"
metadata
cookbook 'chef_handler'
またmetadata.rb
にも依存関係を定義するために以下を追加します。
depends 'chef_handler'
recipes/default.rb
は以下のようになります。冒頭でslack用のハンドラのgemをインストールし、その後で、通知先を設定します。
chef_gem "chef-handler-slack" do
action :upgrade
end
require 'chef/handler/slack'
chef_handler "Chef::Handler::SlackReporting" do
source "chef/handler/slack"
arguments [
# The name of your team registered with Slack
:team => node["slack-handler"]["team"],
# Your incoming webhook token
:token => node["slack-handler"]["token"],
# An existing channel
:channel => node["slack-handler"]["channel"],
# Watever.
:icon_emoj => node["slack-handler"]["icon_emoj"],
]
action :nothing
end.run_action(:enable)
ここでは、チーム名などの可変情報をアトリビュートから引くようにしていますので、attributes/default.rb
を以下のように作成してください。 なお、プライベートレポジトリであればベタで書いてしまっても大丈夫かもしれませんが、普通はChef Server側でアトリビュートの設定をすることをオススメします。
default['slack-handler']['channel'] = '#chef'
default['slack-handler']['token'] = 'SUSHIKUITAIUNIIKURATORO'
default['slack-handler']['team'] = 'your-team-name-here'
default['slack-handler']['username'] = 'chef'
default['slack-handler']['icon_emoj'] = ':chef:'
Cookbookの適用
最後にこのクックブックの適用の設定をおこないます。
Chef Serverを使っている場合はberks upload
コマンドを使ってクックブックをアップロードし、各ノードのrun_listに追加してください。また併せて前述の通り、アトリビュートの設定をChef Server側でおこないます。 Chef Serverを使っていない場合は、run_listにslack-handler
を追加しつつ、アトリビュートをコマンドの引数などで指定してください。
これでChef Clientの実行に失敗した場合は以下のような感じでSlackに通知されるようになります。

もうこれでいつChef Clientの実行でこけてもその場で検知して、「まーた◯◯の依存関係が壊れた」とか愚痴りながらすぐに修正することができますね!!
Chef Clientの実行が失敗した場合にSensuに通知する
次に同じようにSensuに通知してみるようにしましょう。 ここで使うのは、同じくGitHubで公開されているchef-handler-sensu-eventです。
今回はclient.rb
に設定を行う方法でやってみましょう。client.rbを見れば分かるように/etc/chef/client.d
以下にある拡張子.rb
のファイルを読むようになっていますので、直接client.rbをいじらないで、このディレクトリに設定ファイルを置くようにします。
Cookbookの作成
まずは、/etc/chef/client.d
に配置するためのテンプレートをtemplates/default/sensu.rb.erb
という名前で以下の内容で作成します。 これはChefのテンプレートなので、ERBの書式部分は別で設定されたアトリビュートによって実行時に設定されファイルが作成されます。
require 'chef-handler-sensu-event'
option = Hash.new
option[:server] = "<%= node['sensu-handler']['server'] %>"
option[:port] = "<%= node['sensu-handler']['port'] %>"
option[:severity] = <%= node['sensu-handler']['severity'] %>
option[:handlers] = <%= node['sensu-handler']['handlers'] %>
sensu_handler = Chef::Handler::SensuEvent.new(option)
report_handlers << sensu_handler
exception_handlers << sensu_handler
次にレシピです。recipes/default.rb
を以下の内容で作成します。 まず冒頭で先ほどのテンプレートから設定ファイルを作成します。その上で、ハンドラ保存用のディレクトリを作成し、ファイルを保存しつつ、このハンドラを有効にします。
template "/etc/chef/client.d/sensu.rb" do
source "sensu.rb.erb"
owner "root"
group "root"
mode "00644"
end
directory "/var/chef/handlers" do
owner "root"
group "root"
mode 00755
action :create
end
remote_file "#{node[:chef_handler][:handler_path]}/chef-handler-sensu-event.rb" do
source "https://raw.githubusercontent.com/SimpleFinance/chef-handler-sensu-event/master/lib/chef-handler-sensu-event.rb"
mode 0644
end
chef_handler 'Chef::Handler::SensuEvent' do
source "#{node[:chef_handler][:handler_path]}/chef-handler-sensu-event.rb"
action :enable
end
最後にアトリビュートです。attributes/default.rb
を作成します。 ここで設定できるようにしているのは、Sensuで使うRabbitMQのIPアドレスとポート番号、警告の重大度、そしてSensuのどのハンドラ(Chefのハンドラとは違います)を呼ぶかを設定します このアトリビュートについても先ほどのSlackの場合と同様にChef Server側で設定する方が良いでしょう。
default['sensu-handler']['server'] = '127.0.0.1'
default['sensu-handler']['port'] = '5672'
default['sensu-handler']['severity'] = 1
default['sensu-handler']['handlers'] = ['default']
Cookbookの適用
あとは今までと同様にクックブックを適用します。この際に、Chef Clientが正常に終了しないと、以下のような感じでSensuに通知されます。

もちろんこの警告を受けて、更にメールを投げたり、PagerDutyで電話したりといったこともできます。
ということで楽しいインフラ職人ライフを!!