LGTM

Looks Good To Me

条件を満たすとチャット通知するリマインダーをつくる

NetOpsCoding Advent Calendar 2015 3日目のエントリーです.

最近はネットワーク運用をラクにすることに興味があります.

たとえば RESTCONF のようなI-D が標準され,進化すれば ネットワークへの自動デプロイが実現できると思いますが,それはネットワーク運用の一部でしかありません.

スコープをネットワーク運用だとすれば

  1. オーダーをもらう
  2. ネットワークをつくる
  3. 監視する
  4. トラブル対応する
  5. ネットワークを拡張する
  6. 廃止する

このようなワークフロー全体をラクに回したい.デプロイツールは1つのコンポーネントなのですが,コンポーネントの進化に比べて ワークフローが進化していない(っぽい) ことに少し懸念を持っています.

雑に言えば「このコンポーネントと このコンポーネントを組み合わせれば,ほら,こんなに便利」という知見が足りないと感じています.

前置きが長くなりました.今回はそういう流れの話にしたいと思い, いろんなコンポーネントを組み合わせて リマインダーをつくってみようと思います.

やりたいこと

ネットワークを運用していると「次のタイミングでやりましょう」ということが結構あります.多くの場合は やりたいことを覚えておいて,なにかの際に,もしくは ときどき「あれ,やれるかな」というふうに判断しています.

ただ,残念ながら「あれ」が増えれば覚えきれないし 小さいことは忘れてしまいます.「タイミングがきたら絶対にここを見るから,ここに書いておこう」という場所は限られていますし,TODO リストの2ページ目以降は目に触れることすらありません.

次のステップとしては,多数の「〜のときに〜したい」を覚える代わり,「〜になったら,〜になりそうなら 勝手に通知がくる」が望ましいと思っています.

〜になったら / 〜になりそうなら のパターン

通知が欲しいタイミングと,情報ソースについてはいくつかのパターンが考えられます.

  • 定期ポーリングで条件判定すれば十分

    • ネットワークの状態から判定
    • コンフィグから判定
    • ログから判定
    • 手順書から判定
  • リアルタイムイベントをつかまえて条件判定

    • ログから判定
    • メールなどの通知時に判定

これから,いくつかの実装例を紹介します.

ポーリング: ネットワークの状態から判定

特定ポートからリンクがすべて抜けたら,ハードウェアを交換したいというケースがありますね.

  • 定期的にネットワークデバイスにログインし,インターフェイス状態を取得
  • すべて落ちていたらSlack に通知

というフローを考えます.

準備

Slack の場合,単純な通知であればSlackbot がおすすめです.

Slackbot

ほかにもIncoming Webhook やReal Time Messaging API がありますが,Slackbot のみ 同居しているHubot にコマンドを送れます.ここでは紹介しませんが「Slack に通知があったらHubot が勝手に手順書をつくってくれる」など 次のワークフローを回すことができるよう,Hubot を起こせるほうをおすすめします.*1

次にSlack 通知用のシェルスクリプトを準備します.トークンが含まれているのでchmod 700 しておきましょう.

# notify.sh

#!/bin/sh

notify() {
  curl -sfd "$1" 'https://codeance.slack.com/services/hooks/slackbot?token=ccvIktQ90VrBP03gzsA6WsJK&channel=%23test' > /dev/null
}

最後に条件判定スクリプト.ネットワークデバイスの状態を取ってくる部分は NETCONF でもNetOpsCoding Advent Calendar 2日目の例 でもなんでも構いません.今回はrancid に含まれるjlogin を使いました.

# poll.sh

#!/bin/sh

. ./notify.sh

poll() {
  jlogin -c 'show interfaces ge-0/0/* descriptions' 192.168.0.76 | egrep \^ge-0/0 | grep -v down > /dev/null
}

poll || notify 'PIC 0/0 交換のチャンスです'

実行例

ge-0/0/* がすべてdown であれば通知されます.

Slack

このスクリプトを,cron.daily のようにrun-parts 経由でcrond から実行すればOK.

リアルタイムイベント: syslog から判定

特定のBGP ピアでは,落ちたときに急ぎで手動オペレーションしないといけない場合があります.

  • ルーティングを確認したいが,ホントに落とすと怒られる → 自然に落ちたときに確認したい
  • max-prefix limit で落ちたら手動でclear しないといけない

残念ながら,いまのところsyslog 以上に有力なプッシュ通知はないと思いますので

  • syslog で特定のイベントを受信したら,HipChat に通知

というフローを考えます.zabbix やsensu のような監視システムを使っている場合は イベントハンドラーを書くことで簡単に実装できますが,syslog-ng などでも難しくありません.大人の事情で監視機能が外部組織にあっても大丈夫.

準備

HipChat であれば カスタムIntegration を使います.Slack とは違って,どのAPI でもHubot にコマンドを送ることができます.

HipChat Integration

次にHipChat 通知用のシェルスクリプトを準備します.トークンが含まれているのでchmod 700 しておきましょう.

# /home/codeout/hipchat/notify.sh

#!/bin/sh

notify() {
  curl -fd "{\"color\":\"green\",\"message\":\"$1\",\"notify\":false,\"message_format\":\"text\"}" -H 'Content-Type: application/json' 'https://codeance.hipchat.com/v2/room/1301314/notification?auth_token=0uOU8ATolyOMpBGx9P04bjLytOcGnZVm8FugRUEA'
}

最後に条件判定スクリプト.syslog-ng の外部プログラムとして動作させます.

# /home/codeout/hipchat/watch.sh

#!/bin/sh

. /home/codeout/hipchat/notify.sh

filter() {
  echo "$1" | egrep -v 'peer 192.168.0.73.*changed state from Established' > /dev/null
}

while read l; do
  filter "$l" || notify 'ピア(192.168.0.73) 落ちた.経路確認だ'
done

条件判定が成功して通知 / 条件判定が失敗 のどちらのケースでも0 を返せるよう,egrep something && notify よりegrep -v something || notify にします.

泥臭いパターンマッチングをしなくていいよう,構造化されたデータでもらいたいのはNetOpsCoding Advent Calendar 1日目のエントリー のとおりですね.

最後にsyslog-ng の設定例.

# /etc/syslog-ng/conf.d/watch.conf

source s_router {
  udp();
};

destination watch {
  program("/home/koji/hipchat/watch.sh");
};

log {
  source(s_router);
  destination(watch);
};

実行例

BGP ピアが落ちると,HipChat に通知が飛びます.

HipChat

ポーリング: コンフィグから判定

たとえば,トランジットやピア一覧に変化があった場合にリマインドして欲しい場合があります.「連絡先一覧 アップデートした?」のように管理情報の更新を促してもらいたい.

「コンフィグはGitLab で管理されていて,適宜更新されている」という仮定のもと

  • 定期的にgit fetch
  • トランジットやピア一覧に変化があったらSlack に通知

というフローを考えます.

準備

GitLab 上のレポジトリを,仮にssh://git@192.168.99.101:10022/codeout/netops.git とします.

まず 手元にgit clone

$ git clone ssh://git@192.168.99.101:10022/codeout/netops.git

さらに 空パスフレーズssh key を作り,deploy key として登録しておいてください.

詳しい説明は省略しますが,実装例を diff.rb に置きました. ここではJuniper を仮定していて,description "T: Transit A" のようなタグ(T:) を探す動作になっています.また,display-set 形式のほうがdiff に適しているため,junoser を使って変換しています.

内部にssh private key やSlack トークンを書いているので,chmod 700 しておきましょう.

実行例

トランジットやピア一覧に変化があった場合のみ,diff とともにSlack に通知が飛びます.

Slack

リアルタイム: 手順書から判定

最後に「〜になったら」ではなく「〜になりそうなら」の判定を考えてみます.たとえば「デバイスを再起動するときにあれもやりたい」というのはけっこうあるはず.

仮にGitLab で手順書をつくっているとすると Issue やMerge Request などに手順情報が含まれているはずですので,何かしらの方法でこれを見て判定すればよさそうです.

GitLab にはうまいぐあいにWebHook というしくみがあり,データを更新した際 あらかじめ指定したURL にJSON データをPOST させることができます.JSON データのスキーマはGitLab 独自です.

このWebHook を使えば,リアルタイム判定になってしまいますが手順書から「〜になりそうなら」を抽出することができそうです.*2 このWebHook は,GitHub など 流行のサービスにはだいたい備わっていますので,JSON スキーマにさえ注意すれば同じしくみが使えます.

さて,

  • GitLab のIssue / Merge Request のタイトルやコメントに「再起動」「reboot」の文字があったらSlack に通知する

ということをやってみたいと思います.

準備

GitLab 上でWebHook を設定します.そのURL で待ち受けるWebサービスを,これから作ることになります.

GitLab WebHook

単純な実装例を endpoint.rb に置きました.実際はApache やNginx などと連携して動くWeb アプリが好ましいですが,コンセプトの理解には十分でしょう.

これを実行すると ポート8080 でHTTP リクエストを受け付け,GitLab からPOST されたデータを見て 条件に合う場合のみSlack に通知します.

内部にSlack トークンを書いているため,動かす際にはchmod 700 しておきましょう.

実行例

50行程度の短いコードですが,きちんと動きます.

GitLab WebHook

Merge Request データには2つのブランチ名が入っており,ポーリング: コンフィグから判定 のやりかたの応用でMerge Request 中のdiff を見ることもできます.

まとめ

利用可能なコンポーネントをつかって,人間の代わりにコンピューターに覚えといてもらう しくみをいくつか紹介しました.今回の例ではワークフローをラクに回す ところまでいきませんでしたが,人間が苦手な部分 (=記憶) と,単純判断をコンピューターに任せることができるので 少し幸せになれそうです.

さらに言えば,もう次のステップが見えてますね.

「〜のときに〜したい」の後半.アクション部分.通知の代わりに単純作業をやらせましょう.もしくは 作業ための下準備をやらせましょう.

自動でやっていいこと,結構ありそうじゃないですか?

*1:rate limit にだけ注意.現在 1 メッセージ/s

*2:もちろんポーリング動作にもできますが,実装が複雑になる