MRAI の代表的なふるまい
BGP には古くからMRAI (Minimum Route Advertisement Interval) という機能がある.
イマイチ理解していなかったが,動作を確認しておく必要があり いくつかの実装を調査した.
ざっくり言うと
vSRX | IOS-XRv | Quagga | |
---|---|---|---|
新しいprefix の追加時 | MRAI 待つ | MRAI 待つ(*2) | MRAI 待たない |
最後の経路削除時(*1) | MRAI 待つ | MRAI 待たない | MRAI 待たない |
経路切替時 | Best Path になってからMRAI 待つ | 前回のUpdate からMRAI 待つ | MRAI 周期でのみUpdate |
- (*1) prefix ごとの,最後の経路(path)
- (*2) 5% 程度,待たないpacket があったため要調査
このようにさまざまな実装がある.
以下目次:
- MRAI (Minimum Route Advertisement Interval) とは
- MRAI の動作
- ここまでのまとめ: MRAI の動作 (経路の追加 / 削除時)
- MRAI の動作 (続き)
- まとめ
MRAI (Minimum Route Advertisement Interval) とは
簡単にいえば「BGP Update Message の送る頻度を,prefix ごとに制限する」機能のこと.多くのルーターではBGP neighbor 単位で設定できる.
インターネットでは,各事業者のネットワーク拡張や障害などによるBGP 経路の変化が日常的に起こっているが,短時間に大量に変化したり,長時間経路がフラップし続けるとヤバい場合がある.たとえば,末端のちいさなルーターで経路計算が追いつかず,通信に影響が出たりする.
BGP は受け取った経路をもとに各ルーターがベストパスを計算し,ピアに伝達する (BGP Update を送る) プロトコルのため,ピアから大量のBGP Update を受け取った場合は力の限り計算し,力の限り 別ピアに送りつける.
MRAI はこの動作を緩和するしくみで,力の限り計算するが,結果を送りつけるときには 一定期間の間をあけて やさしく送ることができる.
多くの実装で「期間」を設定することができるが,「何と何のあいだの期間か」は実装による. だいたい共通するのは,
- BGP neighbor 単位で設定できる
- 単位は秒
- ピア & prefix (BGP 経路の宛先情報) のペアごとにタイマーを持つ
おおまかには,MRAI は経路計算結果をバッファしておいて 一定時間待ってフラッシュするしくみと捉えておけばいいと思う.同じprefix について何度も経路計算したとしてもフラッシュ時には1 Update になるため,結果的にBGP Update の数を抑えることができる.
MRAI にはデメリットもある点に注意.バッファすることでBGP Convergence が遅くなる場合もあり,インターネットを少しずつ不安定にしてしまうかもしれない.これについてはまた別のエントリーで書く.
MRAI の標準化
この機能は10年以上も前から実装があるが,2008 年にInternet Draft が書かれている.最新のI-D もExpire していて 現在でも標準はない.各社独自実装している状況.
最新のI-D によると
The Minimum Route Advertisement Interval (MRAI) timer is specified in RFC4271 [BGP]. This timer acts to rate-limit updates, on a per- destination basis. [BGP] suggests values of 30s and 5s for this interval for eBGP and iBGP respectively. The MRAI must also be applied to withdrawals according to RFC4271, a change from the earlier RFC1771.
Withdrawn にも適用せよ,とのこと.
MRAI の動作
のふるまいを調べてみた.各々設定は
Juniper vSRX
protocols bgp { group foo { out-delay 30; } }
- default: 0s
Cisco IOS-XRv
router bgp 65000 neighbor 192.168.0.75 advertisement-interval 30
- default: 0s (iBGP), 30s (eBGP)
Quagga
router bgp 65000 neighbor 192.168.0.75 advertisement-interval 30
- default: 5s (iBGP), 30s (eBGP)
こんな感じ.
経路の追加 / 削除時
まず,ベストパス選択がない場合の動作.RIB にない経路を入れ,消す.
左から重複のない新たな経路20000 prefix をBGP Update として入れ,経路処理して右に送り出すまでの時間を見る.十分時間経過したあとにBGP Update (Withdraw) を入れつつ同様に観測する.
経路の追加: Juniper vSRX (MRAI なし)
横軸に時刻(s),BGP Update Message (パケット) を受信したタイミングを青レーンに描画,BGP Update Message (パケット) を送信したタイミングを黒レーンに描画した.
Best Path Selection がないとはいえ結構な遅延があるが,Update 受信 → 経路処理 → Update 送信 までInterval をおかず処理していることがわかる.
経路の追加: Juniper vSRX (MRAI: 30s)
グラフのスケールが違ってて申し訳ないが,経路処理の遅延に加えて30s のInterval をおいていることがわかる.
経路の削除: Juniper vSRX (MRAI なし)
追加と同様に,MRAI がなければInterval なし.
経路の削除: Juniper vSRX (MRAI: 30s)
追加と同様に,Withdraw 時にも30s のInterval が乗る.
経路の追加: Cisco IOS-XRv (MRAI なし)
次にIOS-XRv の結果.Interval は見られない.
Juniper xSRX に比べてBGP Update 処理遅延が小さいことも興味深い.
(同じような処理プロセスだが爆速なのか,並列化など手続き自体を工夫しているのか調べてない)
経路の追加: Cisco IOS-XRv (MRAI: 30s)
Interval を待たず 何か一部お漏らししているように見えるが,(青い四角の下の黒い線) たぶん30s 待ちたいんだろうなあと汲み取った.Interval なくUpdate を送ってしまっているのは,全体300 packet 中の20 packet.
経路の削除: Cisco IOS-XRv (MRAI なし)
Interval は見られない.
経路の削除: Cisco IOS-XRv (MRAI: 30s)
興味深いのは,Interval が見られない点.
送信したBGP Update (黒い線全体) は60 packet ほど.経路追加時には20 packet ほどInterval を待たないBGP Update があったが,必要になればあとで詳しく調べる予定.
経路の追加: Quagga (MRAI: 30s)
次にQuagga のふるまい.一見Interval をおいているように見えるが,よくみると4s に満たない程度なので,これはBGP Update 処理遅延と考えるのが妥当.
経路の削除: Quagga (MRAI: 30s)
Cisco IOS-XRv のようなふるまいで,Interval がない.
ここまでのまとめ: MRAI の動作 (経路の追加 / 削除時)
長くなったので一旦まとめ.
vSRX | IOS-XRv | Quagga | |
---|---|---|---|
新しいprefix の追加時 | MRAI 待つ | MRAI 待つ(*2) | MRAI 待たない |
最後の経路削除時(*1) | MRAI 待つ | MRAI 待たない | MRAI 待たない |
- (*1) prefix ごとの,最後の経路(path)
- (*2) 5% 程度待たないpacket があったため要調査
MRAI の動作 (続き)
経路切替時
次にBest Path Selection が走る場合.
同じAS と2 ピア張り,連続的にBest Path Selection が実行されるよう強弱 *1 をつけて同じprefix のBGP Update を送る.
経路の切替: Juniper vSRX (MRAI: 30s)
下のグラフは,
- 横軸: 時刻(s)
- 左からBGP Update を受信した時刻 = 正の値をプロット
- 右へBGP Update を送信した時刻 = 負の値をプロット
- 線が長い = 強い経路
- 線の色が同じ = 同じ経路 *2
のように描画したもの.
MRAI: 30s に対して10s ごとに強弱のBGP Update を入れた場合:
40s ごとに強弱のBGP Update を入れた場合:
Juniper vSRX は
- 送信するべき経路がBest Path になってから,30s (MRAI) 経過して初めてBGP Update を送信
- 送信するべき経路のBest Path が<30s の間に変わり続ける場合,BGP Update は送信されない
のようにふるまうことがわかる.
経路の切替: Cisco IOS-XRv (MRAI: 30s)
MRAI: 30s に対して10s ごとに強弱のBGP Update を入れた場合:
(60s すぎのBGP Update 受信(青) と送信(オレンジ) が重複している部分,送信のほうが若干早い)
40s ごとに強弱のBGP Update を入れた場合:
- 前回BGP Update を送信してから,30s (MRAI) を経過して初めてBGP Update を送信
- 30s 待つ間にBest Path が変わっていれば30s ですぐ送信するし,変わっていなければ以降Best Path が変わるまで送信しない
- 変わった時点ですぐ送信
のようにふるまうことがわかる.
経路の切替: Quagga (MRAI: 30s)
MRAI: 30s に対して10s ごとに強弱のBGP Update を入れた場合:
40s ごとに強弱のBGP Update を入れた場合:
Quagga は
- 30s (MRAI) ごとのサイクルでのみBGP Update を送信する
- 30s おきにBest Path が変わっていれば送信するし,変わってなければ送信しない
のようにふるまうことがわかる.
ここまでの3実装のふるまい(経路切替時)について,あるprefix に限定した動作であることに注意.prefix が異なれば送信タイミングは独立している.
2016/07/23 追記
コード読んだところQuagga はprefix ごとのタイマーを持たない.BGP neighbor 単位.prefix が違っても 一定のサイクルでまとめてUpdate 送信する.
まとめ
全体のまとめ.
vSRX | IOS-XRv | Quagga | |
---|---|---|---|
新しいprefix の追加時 | MRAI 待つ | MRAI 待つ(*2) | MRAI 待たない |
最後の経路削除時(*1) | MRAI 待つ | MRAI 待たない | MRAI 待たない |
経路切替時 | Best Path になってからMRAI 待つ | 前回のUpdate からMRAI 待つ | MRAI 周期でのみUpdate |
- (*1) prefix ごとの,最後の経路(path)
- (*2) 5% 程度,待たないpacket があったため要調査
見てわかるように,実装によりふるまいがけっこう違う.
目的によってMRAI がハマるとき / ハマらないときがある気がするけど,「このベンダーのこのバージョンでは…」といった具体的な仕様はさておき,多種多様な実装があることを知っておけばひとまず十分.MRAI を検討するときに
- 守りたいものはなにか
- MRAI で自衛できるものか
- 使っているルーターのMRAI はどのように動くか.守りたいものを守れるか
- MRAI を入れる → Convergence Time が伸びて困ることはないか
- 周辺のAS はどのようにふるまうか
などを考えればよいかな,と思う.あとデフォルトが0s でない装置もあるので知らず知らずのうちに使っているかもしれない.
unicast route の変更により,flowspec validation は走るか
flowspec は forwarding に関するルールをルーター間で伝え合うプロトコルで,「他者から受け取ったルールを適用する前にvalidation しなさい」と仕様で決まっている.
では,validation するのはいつだろう?
- flowspec route を受け取ったとき
- flowspec route に関連するunicast route に変更があったとき
1 . は当然として,2 . のタイミングでvalidation が走るかどうかで振る舞いが大きく変わりそう.
RFC5575 によれば
A flow specification received from an external autonomous system will need to be validated against unicast routing before being accepted.
と書かれているものの,「いつvalidate するか」については細かく規定されていない.関連I-D にもない.
いつものように実装にまかされてるやつなので,Juniper vSRX, Cisco IOS-XRv で試してみた.
環境
eBGP ピアを2 ルーター間で張り,
- unicast route を1つずつ広告する
- destination prefix が同じflowspec route も1つずつ広告する
- ピアを張ったまま,1 . をwithdraw して2 . の経路をvalidate し直すかを見る
結果
Juniper vSRX, Cisco IOS-XRv とも unicast route の変更時にもvalidation が走る.
妥当な動きというか,直観的でよかった.そうでなければ「validation fail するはずなのに動いてる」「ebgp ピアがflap したらflowspec 動作が変わる」「soft clear したら動作が変わる」「flowspec route を止めて出し直したら動作が変わる」みたいになる.
以下,参考まで.
設定
Juniper vSRX
routing-options { static { route 10.0.1.0/24 discard; # deactivate したり activate したりする } autonomous-system 64600; flow { route flow1 { match destination 10.0.1.0/24; then discard; } } } protocols { bgp { group ebgp { family inet { unicast; flow; } export redistribute-static; peer-as 64601; neighbor 192.168.0.74; } } } policy-options { policy-statement redistribute-static { from protocol static; then accept; } }
Cisco IOS-XRv
class-map type traffic match-all flow1 match destination-address ipv4 10.0.2.0 255.255.255.0 ! end-class-map ! policy-map type pbr drop1 class type traffic flow1 drop ! class type traffic class-default ! end-policy-map ! route-policy permit-all pass end-policy ! router static address-family ipv4 unicast 10.0.2.0/24 Null0 ! no したり入れたりする ! ! router bgp 64601 address-family ipv4 unicast redistribute static ! address-family ipv4 flowspec ! neighbor-group ebgp remote-as 64600 address-family ipv4 unicast route-policy permit-all in route-policy permit-all out soft-reconfiguration inbound always ! address-family ipv4 flowspec route-policy permit-all in route-policy permit-all out soft-reconfiguration inbound always ! ! neighbor 192.168.0.67 use neighbor-group ebgp ! ! flowspec address-family ipv4 service-policy type pbr drop1 ! ! end
unicast route があるとき
koji@vsrx> show route table inetflow.0 detail inetflow.0: 2 destinations, 2 routes (2 active, 0 holddown, 0 hidden) ... 10.0.2/24,*/term:2 (1 entry, 1 announced) *BGP Preference: 170/-101 Next hop type: Fictitious Address: 0x8f3df04 Next-hop reference count: 2 State: <Active Ext> Local AS: 64600 Peer AS: 64601 Age: 10:38 Task: BGP_64601.192.168.0.74+179 Announcement bits (1): 0-Flow AS path: 64601 I Communities: traffic-rate:64601:0 Accepted # Accept されている Validation state: Accept, Originator: 192.168.0.74 Via: 10.0.2.0/24, Active Localpref: 100 Router ID: 172.16.0.3
RP/0/0/CPU0:ios#sh bgp ipv4 flowspec Dest:10.0.1.0/24 Thu May 22 10:52:24.962 UTC BGP routing table entry for Dest:10.0.1.0/24/40 Versions: Process bRIB/RIB SendTblVer Speaker 6 6 Last Modified: May 22 10:46:16.486 for 00:06:08 Paths: (1 available, best #1) Not advertised to any peer Path #1: Received by speaker 0 Not advertised to any peer 64600, (received & used) 0.0.0.0 from 192.168.0.67 (172.16.0.2) ! best になっている Origin IGP, localpref 100, valid, external, best, group-best Received Path ID 0, Local Path ID 1, version 6 Extended community: FLOWSPEC Traffic-rate:0,0
unicast route がないとき
koji@vsrx> show route table inetflow.0 detail all ... 10.0.2/24,*/term:N/A (1 entry, 0 announced) BGP /-101 Next hop type: Fictitious Address: 0x8f3df04 Next-hop reference count: 2 State: <Hidden Ext> Local AS: 64600 Peer AS: 64601 Age: 17:41 Task: BGP_64601.192.168.0.74+179 AS path: 64601 I Communities: traffic-rate:64601:0 # Reject されている Validation state: Reject, Originator: 192.168.0.74 Via: 0.0.0.0/0, Active Localpref: 100 Router ID: 172.16.0.3
RP/0/0/CPU0:ios#sh ip bgp ipv4 flowspec Dest:10.0.1.0/24 Thu May 22 10:58:36.526 UTC BGP routing table entry for Dest:10.0.1.0/24/40 Versions: Process bRIB/RIB SendTblVer Speaker 7 7 Last Modified: May 22 10:54:16.486 for 00:04:20 Paths: (1 available, no best path) Not advertised to any peer Path #1: Received by speaker 0 Not advertised to any peer 64600, (received & used) 0.0.0.0 from 192.168.0.67 (172.16.0.2) ! invalid になっている Origin IGP, localpref 100, valid, external, invalid flowspec-path Received Path ID 0, Local Path ID 0, version 0 Extended community: FLOWSPEC Traffic-rate:0,0
mention-bot を試した
Facebook が公開しているPull Request (PR) レビュー支援ツール.
PR を作ったとき,最適なレビューアーを見つけてきてPR 上でメンションしてくれる.
試したけど,うまくいかなかった.
ざっくり言うと
セキュリティ上の懸念があって断念した.
- 「ボットアカウントはGitHub の2FA をオフにしなさい」と書いてある
- このボット機能のためだけに,1 の状態で動くエンドポイントを持ちたくない
- PR 作成時にWebHook でトリガーしてもらうのにエンドポイント必要
- 得られる価値に対してリスク高い
やりたかったこと
PR レビューがコスト高になることがある
Private Repo でGitHub フローを回しており,日常的にPR レビューしている. チーム内のさまざまな事情により,ふと気づくとレビューにすごい時間を使っていることがある.
個人がレビューを嫌って押し付けあうのは避けたいが,チーム全体として効率よくレビューしたい.
日本人だし「〇〇さんレビューお願いします」って言えない
〇〇さんにお願いするのが最適な場合でも「お願いします」ってなかなか言えない.日本人だからね… 急ぎの中ちょっと待っちゃったりして,状況をすこしずつ悪くしてしまう.
また,PR がぽんっと出た瞬間「あいつが見るだろう」みたいな考えがよぎる. たぶんみんな同じことやってて そもそもの着手が遅くなり,ちょっとずつ他メンバーのタスクをブロックしちゃう.
これやめたい.
試したこと
レビューアーをランダムに決めるやつ とかもあるが, やりたいことの半分しか実現できないので mention-bot に興味を持った.
ぱっと見「WebHook トリガーにしないといけないの面倒…」と思ったが, 「PR 作ったあと チャット経由で手動キックすれば動くだろう」と,Hubot スクリプトを書いてみた.
ところが…GitHub API トークンではなくID / パスワードでログインしないと動かないことがわかった.
mention-bot の動き
ここ に書いてあるとおりだが,
- WebHook でPR の情報を受け取る.またはPR の情報を渡して手動で呼ぶ
- PR 内で,どのファイル / 行にdiff があるか探す
- 削除/変更があった行について,
git blame
でmaster ブランチでの最終更新者を探す- いじる行の最終更新者がレビューアーに向く という前提
- PR 全体で3 の頻出ランキングを作る
- いじるファイル内の変更しない行についても 同様に
git blame
ベースのmaster 最終更新者ランキングをつくる- いじる行でなくても,同じファイル内の最終更新者がレビューアーに向く という前提
- 5 を4 のケツにくっつける
- 6 からPR 作成者を除いたトップ N人について,指定のコールバックを呼ぶ
問題は,git blame
に相当するGitHub API がないこと.
Web UI にはあるのでスクレイピングして代用しているが,これが原因でボットアカウントを2FA にできない.
場合によってはAPI も使うため, さらにトークンが必要.
あきらめた
とても便利そうなんだけれど,セキュリティ的にイマイチ という判断をした.
今回Private Repo なのでやめたが,GitHub Enterprise で社内に閉じてるとか,
Public Repo であれば敷居が下がったかもしれない.
git blame
をローカルでやればいいだけかもしれない.
Public Repo なら認証不要*1 なので,
var mentionBot = require('./run-mention-bot.js'); var GitHubApi = require('github'); var github = new GitHubApi({ version: '3.0.0' }); var config = { maxReviewers: 5, findPotentialReviewers: true, fileBlacklist: [], userBlacklist: [], requiredOrgs: [] } mentionBot .guessOwnersForPullRequest( 'https://github.com/fbsamples/bot-testing', 98, 'mkonicek', 'master', false, null, config, github ) .then(function(users) { console.log(users); }) .catch(function(err) { console.error(err); });
こういう感じのが動く.
特定Organization 内からのみレビューアー候補を選びたいときは,API トークンが必要なことがある.
それでもちょっと試したいひと向け
Private Repo でちょっとだけ試す方法.
ES6 のtranspile 環境をつくるのが面倒なので,mention-bot レポジトリ内にスクリプトを置く作戦.
git clone https://github.com/facebook/mention-bot.git cd mention-bot npm install npm install github --save
Web UI 認証をやめて,認証済みCookie をつかう.
# githubAuthCookies.js を書き換え
module.exports = '_octo=GHx.x.xxxxxxxxxx.xxxxxxxxxx; logged_in=yes; dotcom_user=codeout; _gat=1; _gh_sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; _ga=GAx.x.xxxxxxxxxx.xxxxxxxxxx; tz=Asia%2FTokyo; user_session=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
私はChrome から抜いた.
最後にスクリプト本体.
# mention.js var mentionBot = require('./run-mention-bot.js'); var GitHubApi = require('github'); var github = new GitHubApi({ version: '3.0.0' }); // Organization 内からのみレビューアー候補を選びたいときのみ必要 github.authenticate({ type: 'oauth', token: '...token...' }); var config = { maxReviewers: 5, findPotentialReviewers: true, fileBlacklist: [], userBlacklist: [], requiredOrgs: [] // レビューアー候補がprivate メンバーだと動かない } mentionBot .guessOwnersForPullRequest( 'https://github.com/user/private_repo', 1, // PR Number 'codeout', // PR 作成者 'master', // blame するブランチ false, // Private Repo でも false, null でOK null, // Organization 縛りにするときは true, 'organization' config, github ) .then(function(users) { console.log(users); }) .catch(function(err) { console.error(err); });
実行結果
mention-bot $ time node mention.js [ 'codeout', 'foo', 'bar', 'baz' ] node mention.js 0.89s user 0.23s system 11% cpu 9.385 total
Organization 縛りをつけるとAPI コールが増えて遅い.
mention-bot $ time node mention.js [ 'codeout', 'foo', 'bar' ] node mention.js 1.01s user 0.25s system 6% cpu 18.059 total
*1:PR 上でメンションしない場合
高速にnetflow を吐く
最近 netflow コレクターをさわる機会が多く「コレクターをベンチマークしたい」と思っている. 「netflow v5 なら何 flow/s まで受けられる」「v9 ならこのくらい」など,性能限界を知っておきたい.
2016/02/20 追記: NetFlow-Generator
戦略
いくつか考えられる方法があって
- ネットワークデバイスのサンプリングレートを変え,多めにnetflow export する
- netflow データを用意し,高速に送信する
1 は実網に影響あるかもしれないためパス.2 の方法にする. netflow データの作り方もいくつか思いつく.
2-1. ネットワークデバイスが吐いたnetflow を再利用する
2-2. ウソデータをでっち上げる
ネットワークデバイスが吐いたnetflow を再利用する
nfdump というツールを利用する.
$ nfcapd -l . -p <ポート>
データが溜まるのを待つ.1M flowほどあれば十分だが,溜まるのに時間がかかる場合は-t <interval>
オプションをつけておく.
これはnetflow データを1 ファイルにまとめるため.(デフォルトでは5分でファイルローテートしてしまう)
1M flowも溜めるのは,netflow データストアによって重複netflow を無視する可能性があるため. なるべく重複レコードが少ないほうがよい.
実行すると nfcapd.201602180335
のようなファイルができ,
$ nfreplay -H <IP アドレス> -p <ポート> -r nfcapd.201602180335
のようにするとnetflow パケットを送信できる.デフォルトではnetflow v5 で,-v9
オプションをつけるとnetflow v9 になる.
OSX の場合は
Error sending data: No buffer space available
のようなエラーが出るかもしれない.手元ではLinuxからnetflow を吐き,ベンチマークした.
ウソデータをでっち上げる
nfdump に加え,flow-tools というツールを利用する.
$ nfcapd -l .
しておき,
$ flow-gen -n 1000000 -V 5 | flow-send 0/127.0.0.1/9995
すると各フィールドがシーケンシャルなnetflow データが作れる.
srcIP dstIP prot srcPort dstPort octets packets 0.0.0.0 255.255.0.0 17 0 65280 1 1 0.0.0.1 255.255.0.1 17 1 65281 2 2 0.0.0.2 255.255.0.2 17 2 65282 3 3 0.0.0.3 255.255.0.3 17 3 65283 4 4 0.0.0.4 255.255.0.4 17 4 65284 5 5 0.0.0.5 255.255.0.5 17 5 65285 6 6 0.0.0.6 255.255.0.6 17 6 65286 7 7 0.0.0.7 255.255.0.7 17 7 65287 8 8 0.0.0.8 255.255.0.8 17 8 65288 9 9 0.0.0.9 255.255.0.9 17 9 65289 10 10 0.0.0.10 255.255.0.10 17 10 65290 11 11 0.0.0.11 255.255.0.11 17 11 65291 12 12 0.0.0.12 255.255.0.12 17 12 65292 13 13
- flow-gen が作るnetflow のタイムスタンプはエポックタイムで0 だが,nfcapd が一定時間シフトさせてしまう (バグの可能性ある)
- flow-gen が作るnetflow は30 flow/pkt
- MTU1500 ではこれが限界
- 変更するオプションはなさそう
これをネットワークデバイスが吐いたnetflow を再利用する と同様にnfreplay
すればよい.
手元のFE NIC 環境ではwire rate 出た.(およそ 250k flow/s)
さらにスピードを出す
250k flow/s も出れば十分すぎるが,さらにコレクターをいじめるためにtcpreplay をつかう. 前述のnfreplay 時にパケットキャプチャーしておき,
$ tcpreplay -t -i eth0 nfreplay.pcap
すれば1G NIC 環境でもwire rate 出た.(およそ 2.5M flow/s)
ちなみにdst IP アドレス,dst MAC アドレスを変えつつ送信してもwire rate 出る.
$ tcpreplay-edit -tC -i eth0 -D 0.0.0.0/0:<IP アドレス>/32 --enet-dmac <MAC アドレス> nfreplay.pcap
netmap を使えば10Gbps ちかく出る可能性 はあるが,やめてあげてください.コレクターが動くわけがない.
スピードを調整する
tcpreplay が便利なのは送信スピードを変えられること.
-x string, --multiplier=string -p number, --pps=number -M string, --mbps=string
オプションを使う.
課題
nfreplay を使った方法ではIPFIX,sflow を吐けない.対応しているのはnetflow v5/v9 のみ. 実パケットをキャプチャーし,tcpreplay で高速送信する方法ならOK.
2016/02/20 追記: NetFlow-Generator
「NetFlow-Generator というのがあるよ」と教えてもらいました.
@codeout 拙作のこんなのもあります。flow-tools と速度比較をしたことはありませんが、柔軟性は高いと思います。https://t.co/oTQvIMoyCx
— Motonori Shindo (@motonori_shindo) February 20, 2016
ウソデータをでっち上げる ときのflow-tools (のflow-gen) の代わりに使える.
- タイムスタンプをいじれる
- flow/pkt をいじれる
- flow-gen → nfcapd でフローデータ保存 → nfreplay と同等のスループットで動く
柔軟でよさそう.
$ flowgen -h Usage: flowgen [options] [flowrec-options] <collector> options: -n, --count <num> -p, --port <num> -V, --version <version> -f, --flowrec <# of flow records in packet> -d, --debug <debug level> -N, --nosend -h, --help flowrec-options: -w, --wait <wait time> -i, --interval <interval> --enginetype <engine type> --engineid <engine id> --srcaddr <src ip address> --dstaddr <dst ip address> --nexthop <nexthop ip address> --inputif <input IfIndex> --outputif <output IfIndex> --packets <# of packets> --octets <# of octets> --firstseen <first seen> --lastseen <last seen> --srcport <src port> --dstport <dst port> --tcpflags <tcp flags> --protocol <protocol number> --tos <tos value> --srcas <src AS#> --dstas <dst AS#> --srcmask <src subnet mask length> --dstmask <dst subnet mask length> Numbers can be expressed using the following meta characters: 111 (static) 111-222 (sequential) 111:222 (random) 100@70,200@20,300@10 (probabilistic)
junoser internals
junoser というRuby gem をメンテしている. これはJUNOS Config のPEG パーサーで,標準入力やファイルを JUNOS Config として構文解析できる.解析後,文法チェックやスタイルの変換もできる.
たとえば,
構文チェック.display | set
形式とデフォルトの構造化スタイル両方をサポート.
$ junoser -c config.invalid Invalid syntax: set protocols bgp group e1 neighbor 10.0.26.1 exprot s2b zsh: exit 1 junoser -c config.invalid
スタイルの変換.display | set
スタイル → 構造化スタイル
junoser -s config.set
スタイルの変換.構造化スタイル → display | set
スタイル
junoser -d config
さっとチェックしたり,CI に使ったり,レビュー時 display | set
スタイルがつらい時に重宝してる.
文法定義はどこから来るか
CLI と,NETCONF 用のXML が構造的に似ていることを利用して,<get-schema>
で取れるXML Schema(XSD) を使っている.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> <xsd:import schemaLocation="junos.xsd" namespace="http://xml.juniper.net/junos/12.1X47/junos"/> <xsd:element name="configuration"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="interfaces" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="interface" minOccurs="0" maxOccurs="unbounded" type="interfaces-type"> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="interfaces-type"> <xsd:sequence> <xsd:element name="name"> <xsd:complexType> <xsd:simpleContent> <xsd:restriction base="key-attribute-string-type"> <xsd:enumeration value="$junos-interface-ifd-name"> </xsd:enumeration> <xsd:enumeration value="interface-name"> </xsd:enumeration> </xsd:restriction> </xsd:simpleContent> </xsd:complexType> </xsd:element> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="unit" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:element name="name"> <xsd:complexType> <xsd:simpleContent> <xsd:restriction base="key-attribute-ulong-type"> <xsd:enumeration value="$junos-underlying-interface-unit"> </xsd:enumeration> <xsd:enumeration value="$junos-interface-unit"> </xsd:enumeration> <xsd:enumeration value="interface-unit-number"> </xsd:enumeration> </xsd:restriction> </xsd:simpleContent> </xsd:complexType> </xsd:element> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="family" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="inet" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="address" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:element name="name"> <xsd:complexType> <xsd:simpleContent> <xsd:extension base="ipv4prefix"> <xsd:attribute name="key" type="xsd:string" fixed="key"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:schema>
上記のXSD はごく一例*1だが,XML パーサーを使って,これを下のruby コードに変換する. (junoser に含めたパーサージェネレーターで自動生成)
module Junoser class Parser < Parslet::Parser rule(:configuration) do c( b(str("interfaces"), c( interfaces_type ) ) ) end rule(:interfaces_type) do b((arg | str("interface-name")).as(:arg), c( a(str("unit"), arg | arg | str("interface-unit-number"), c( b(str("family"), c( b(str("inet"), c( b(a(str("address"), arg)) ) ) ) ) ) ) ) ) end end end
全体で60万行くらいのXSD が,4万行くらいのPEG パーサーになる.これがjunoser の本体だが,パーサージェネレーターの生成物でもある.
パーサージェネレーターの闇
ここから本題です.ここまでは比較的スッキリと書ける.処理する対象がゴチャゴチャしているため一見ややこしいが,やっていることはシンプル.
問題1: CLI とXSD の微妙なちがい
interfaces { ge-0/0/0 { description "interface 1"; } }
上のようなJUNOS Config がある.よく見ますね.
description
ステートメントの後ろに文字列が来る.
これをdisplay | xml
すると,
<interfaces> <interface> <name>ge-0/0/0</name> <description>interface 1</description> </interface> </interfaces>
なるほど,<description/>
要素に文字列が入っている.
routing-options { autonomous-system 64500; }
いっぽう,上のようなJUNOS Config がある.
autonomous-system
ステートメントの後ろに文字列が来る.
これをdisplay | xml
すると,
<routing-options> <autonomous-system> <as-number>64600</as-number> </autonomous-system> </routing-options>
なるほど,こちらは<autonomous-system/>
要素の中で,新たに<as-number/>
要素で文字列を包んでいて interface description と違う.
では2つのXSD を見てみましょう.
<xsd:complexType name="interfaces-type"> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="description" minOccurs="0" type="xsd:string"/> </xsd:choice> </xsd:sequence> </xsd:complexType>
<xsd:element name="autonomous-system" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="as-number" type="xsd:string"/> </xsd:choice> </xsd:sequence> </xsd:complexType> </xsd:element>
ほぼ同じなわけです.「as-number
はただのラベルであって,CLI 上には現れないこと」がXSD からは分からない.
junoser では文脈をくみとりながら「これはただのラベル要素」「これは必要な要素」と選別していて,そこが泥臭いハックになっている.
問題2: そもそもXSD にない定義
junoser はvSRX のXSD をベースにしているため,vSRX にない機能はパースできない.
<xsd:element name="service-set" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:choice minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element>
たとえば,上はset services service-set ...
に対応するXSD だが,vSRX には定義が存在しない.
問題3: ライセンス
XSD で定義される構文木に泥臭いハックを入れて,CLI にあてはめるためのパーサーを作った.このコードのライセンス,微妙ですよね. いちおうJuniper のかたに確認して公開しているが…大丈夫かな? 詳しいかたがいればアドバイスいただけると嬉しいです.
Juniper はよくてもCisco がNG とかだと,IOS-XR 版が作れなくて困ってしまう.
あと,junoser は個人プロジェクトとして進めているので,Juniper にフィードバックしたくてもできないことがあって困ってる.
おわりに
junoser をメンテするにあたって,けっこう泥臭いことをやっています.でも,けっこう動くレベルのはずです.
もし興味をもってもらえたら ぜひ使ってみてください.パースできないCLI コマンドがあれば,フィードバックよろしくお願いします.自分ではあまり使わないConfig については,問題1 を潰しきれていません.
また,手元にXSD がないのが原因で動かないケース(問題2) もあるので,その際にはXSD の提供に協力してもらえると助かります.
こちらが安定してきたら,IOS-XR 版にも着手予定です.
*1:関係のない要素も省略
netconf / restconf 時代にもxlogin 使いまくってる話
NetOpsCoding Advent Calendar 2015 18日目のエントリーです. ハードウェア製品を使ったネットワーキングの話.
さまざまなコンポーネントについて YANG Model の標準化が進み,NETCONF 使える気運が高まっています.さらにRESTCONF も待っていて,ハードウェアで高速にルーティングしつつ運用を自動化できそうな予感がしています.
私は,条件がそろえばNETCONF を使います.XML といえど エラーや出力が構造化されていて きちんと取れるのはうれしい.
一方で 日常のネットワーク運用はCLI に頼っていて,判断をはさみつつ手続き (= コマンド) を変えます.このような用途にはNETCONF / RESTCONF はたぶん向きません.大がかりすぎです.
今回は,自動化できないときでも便利な xlogin を紹介します.
( ようやく,これ自動化の話じゃないわ と気づきました… 気にせず行きます すみません )
xlogin ?
rancid に同梱されている clogin, jlogin, flogin, ... のことをまとめて xlogin と呼んでいます.これらはrancid のためのサポートツールで,各種デバイスにログインするためのexpect スクリプトセットです.
ssh / telnet を薄くwrap するだけの単純なexpect ですが,めちゃ便利なので, いまはネットワークデバイスにログインするとき ssh / telnet を呼ぶことはほとんどありません.たぶんrancid 本体より便利です.
つかいかた
ログインする例 (IOS 系)
xlogin はデバイス種別ごとに実行ファイルが分かれており,IOS 系にはclogin を使います.
clogin <デバイス名>
パスワードを自動入力しつつ telnet でログインしています.よく見れば,ログインと同時にenable
になっていることがわかると思います.
上はQuagga の例ですが,IOS でも同じ動きです.
複数デバイスにコマンド実行する例 (Junos)
複数のJunos に対し,show version
を実行します.Junos にはjlogin を使います.
jlogin -c "show version" <デバイス名1> <デバイス名2>
2台のJunos に順にssh ログインし,show version
を実行し,ログアウトします.結果は標準出力されるので,grep
に渡したり,ファイルにリダイレクトしたり,| vi -
して開く → 編集 → 保存したりできます.
よく見ると ssh のまえにtelnet を試しているのがわかると思いますが,xlogin はデフォルトでは「telnet / ssh 両方を試してログインできるほうを使う」という動きをします.
初めてssh するデバイスについては,自動でhostkey のfingerprint を~/.ssh/known_hosts
に保存します.fingerprint が違っていることを検出した場合はログインしません.
複数コマンドを実行する例 (IOS-XR)
IOS-XR にログインし,interface description を変更します.
! ./cmd show interfaces description configure interface Gi0/0/0/0 description interface1 commit exit exit show interfaces description exit
上のような一連のコマンド群を実行する必要がありますが,ファイルに書いておいてxlogin に渡すだけ.
clogin -x <コマンドファイル> <デバイス名>
単純に逐次実行するだけなので,失敗時にrollback しない点に注意してください
- デバイス固有のコメント行をいれておくと便利
- 引数をふやせば,複数デバイスに対して実行できる
jlogin -c "show version; show chassis hardware" router1
のようにコマンドラインからも渡せる- vty にコピー&ペーストしたときのバッファ溢れが気になる時など,
vi cmd; clogin -x cmd <デバイス名>
すれば安心.プロンプトが返ってきたら1行実行,また1行実行…という動きをする
以上,xlogin の使用例をならべてみました.
clogin, jlogin, ... を使い分けるのは 少々めんどうですが,便利そうじゃないですか?
対応デバイス
xlogin は対応デバイスが多くてスゴい.めんどくさいプロンプト処理をやってくれてます.
xlogin | Vendor |
---|---|
alogin | Alteon WebOS switch login |
anlogin | Arbor Networks login |
avologin | Avocent login |
blogin | Bay Networks(Nortel) login |
clogin | Cisco login |
complogin | Compass login |
dllogin | D-Link login |
elogin | ADC EZT3 login |
flogin | foundry login |
fnlogin | fortinet login |
hlogin | hp login |
htlogin | Hitachi router login |
jlogin | juniper login |
mrvlogin | MRV optical switch login |
mtlogin | MikroTik router login |
nlogin | netscreen login |
nslogin | Netscaler login |
panlogin | Palo Alto Networks login (based on alogin) |
rivlogin | Riverstone (and Enterasys SSR) login |
rscmd | Riverstone Networks Automated login |
tlogin | Netopis login |
wlogin | Cisco Wireless Lan Controller login |
xilogin | Xirrus login |
残念なのは,github などにレポジトリがなくてソーシャルコーディングできないこと. ここにないデバイスをサポートしても,本家にマージしてもらうのがつらい.
認証情報
認証情報の持たせかたについても触れておきましょう.
認証情報は,あらかじめ~/.cloginrc
という設定ファイルに書いておかないといけません.
などを書いておきます.ログインする際 「xlogin に渡したデバイス名 を上から順に探し,最初にマッチした設定を使う」という動きをします. サーチドメインをつけた名前や,A レコードを引いたあとのIP アドレスではないことに注意です.デバイス名にはワイルドカードが使えます.
# ~/.cloginrc add method router4 {telnet:2605} add password router4 {zebra} {zebra} # default add user * {koji} add password * {login_passwd} {enable_secret}
~/.cloginrc
が他者に読まれそうなパーミッションであれば,xlogin 実行時に警告します.
設定方法の詳細はrancid web サイト にあります.プロンプトのカスタマイズや enable するかどうかなど,けっこう細かく設定できます.
その他のヒント
インストール
- deb パッケージがあるディストリビューションが多い.Debian, Ubuntu などは
apt-get
一発 - rpm パッケージは (たぶん)ないので,自分でつくらないといけない
- OSX であれば brew にある
ping 127.0.0.1
の応答がないと./configure
がコケる.OSX のfirewall など注意- ソースパッケージからビルドしてもよい.なぜか
./configure
でxlogin が生成される.「./bin/*login
だけ$PATH のどこかにコピー」とかよくやる
~/.cloginrc
をやめる
ファイルに認証情報を書くのは セキュリティ上いまいちなので,私はvault に保管しています. 完全ではありませんが,他者からアクセスする機会を大幅に減らせますし,認証情報へのアクセスログが取れます.ずいぶんマシ.
やりかたは別のエントリー を参照してください.
まとめ
xlogin の使い方とヒントについて説明しました.
ログインするとき ssh / telnet がわりに使うのはもちろんですが,自動でなにかするコードを書くときにも重宝しています. そのコードには「なにかする」ことだけを記述し,認証とコマンド実行をxlogin に切り出せば 少ない手数で見通しよくなりますし,認証レイヤーを共通化できます.
NETCONF を使いたくても,サポートしていないデバイスがあったりで xlogin + CLI パーサー を選ぶ局面はまだまだありそうです.残念ですけどね.
条件を満たすとチャット通知するリマインダーをつくる
NetOpsCoding Advent Calendar 2015 3日目のエントリーです.
最近はネットワーク運用をラクにすることに興味があります.
たとえば RESTCONF のようなI-D が標準され,進化すれば ネットワークへの自動デプロイが実現できると思いますが,それはネットワーク運用の一部でしかありません.
スコープをネットワーク運用だとすれば
- オーダーをもらう
- ネットワークをつくる
- 監視する
- トラブル対応する
- ネットワークを拡張する
- 廃止する
このようなワークフロー全体をラクに回したい.デプロイツールは1つのコンポーネントなのですが,コンポーネントの進化に比べて ワークフローが進化していない(っぽい) ことに少し懸念を持っています.
雑に言えば「このコンポーネントと このコンポーネントを組み合わせれば,ほら,こんなに便利」という知見が足りないと感じています.
前置きが長くなりました.今回はそういう流れの話にしたいと思い, いろんなコンポーネントを組み合わせて リマインダーをつくってみようと思います.
やりたいこと
ネットワークを運用していると「次のタイミングでやりましょう」ということが結構あります.多くの場合は やりたいことを覚えておいて,なにかの際に,もしくは ときどき「あれ,やれるかな」というふうに判断しています.
ただ,残念ながら「あれ」が増えれば覚えきれないし 小さいことは忘れてしまいます.「タイミングがきたら絶対にここを見るから,ここに書いておこう」という場所は限られていますし,TODO リストの2ページ目以降は目に触れることすらありません.
次のステップとしては,多数の「〜のときに〜したい」を覚える代わり,「〜になったら,〜になりそうなら 勝手に通知がくる」が望ましいと思っています.
〜になったら / 〜になりそうなら のパターン
通知が欲しいタイミングと,情報ソースについてはいくつかのパターンが考えられます.
定期ポーリングで条件判定すれば十分
- ネットワークの状態から判定
- コンフィグから判定
- ログから判定
- 手順書から判定
リアルタイムイベントをつかまえて条件判定
- ログから判定
- メールなどの通知時に判定
これから,いくつかの実装例を紹介します.
ポーリング: ネットワークの状態から判定
特定ポートからリンクがすべて抜けたら,ハードウェアを交換したいというケースがありますね.
というフローを考えます.
準備
Slack の場合,単純な通知であれば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 であれば通知されます.
このスクリプトを,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 通知用のシェルスクリプトを準備します.トークンが含まれているので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 に通知が飛びます.
ポーリング: コンフィグから判定
たとえば,トランジットやピア一覧に変化があった場合にリマインドして欲しい場合があります.「連絡先一覧 アップデートした?」のように管理情報の更新を促してもらいたい.
「コンフィグは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 に通知が飛びます.
リアルタイム: 手順書から判定
最後に「〜になったら」ではなく「〜になりそうなら」の判定を考えてみます.たとえば「デバイスを再起動するときにあれもやりたい」というのはけっこうあるはず.
仮に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サービスを,これから作ることになります.
単純な実装例を endpoint.rb に置きました.実際はApache やNginx などと連携して動くWeb アプリが好ましいですが,コンセプトの理解には十分でしょう.
これを実行すると ポート8080
でHTTP リクエストを受け付け,GitLab からPOST されたデータを見て 条件に合う場合のみSlack に通知します.
内部にSlack トークンを書いているため,動かす際にはchmod 700
しておきましょう.
実行例
50行程度の短いコードですが,きちんと動きます.
Merge Request データには2つのブランチ名が入っており,ポーリング: コンフィグから判定 のやりかたの応用でMerge Request 中のdiff を見ることもできます.
まとめ
利用可能なコンポーネントをつかって,人間の代わりにコンピューターに覚えといてもらう しくみをいくつか紹介しました.今回の例ではワークフローをラクに回す ところまでいきませんでしたが,人間が苦手な部分 (=記憶) と,単純判断をコンピューターに任せることができるので 少し幸せになれそうです.
さらに言えば,もう次のステップが見えてますね.
「〜のときに〜したい」の後半.アクション部分.通知の代わりに単純作業をやらせましょう.もしくは 作業ための下準備をやらせましょう.
自動でやっていいこと,結構ありそうじゃないですか?