NetOpsCoding Advent Calendar 2016 12/16分の記事です。
背景
もともとChatOps系のネタで何をやろうかと考えていたのですが、IOS自身にもチャットに近い機能(send
コマンド)があることを思い出しました。
Router#send *
Enter message, end with CTRL/Z; abort with CTRL/C:
test message
^Z
Send message? [confirm]
Router#
***
***
*** Message from tty1 to all terminals:
***
test message
で、このsend
コマンドがとても使いづらいんです。
- 発言の度にログインするのが非常に手間である。
- ログインしてないと発言を受けることができない。
- 過去のメッセージが記録されない。
- 日本語や絵文字が使えない。
- チャットだったらGUIの方が使いやすい。
- アバター画像があると親近感あって良い。
なので、これらの問題を自動化その他もろもろで何とかしてみました。
成果物(Web UI部分)
全然send
コマンドの面影がありませんが、バックエンドでは重要な役割を担っています。
裏の仕組みについては後述します。
CLI部分を完全に自動化したので、非常に快適にチャットができるようになりました。
https://github.com/tanksuzuki/cchat
構成
Router(as a Database)
標準のsend
ではログイン中のユーザにしか発言できず、過去の発言は保存されません。
そのため、データを保持できる何かしらの場所が必要となります。
案1: Flashにファイルを置く
Flashにファイルを作って、 IOS.shで発言内容を記録してく方法です。
Cisco IOS シェル CLI モジュールの設定例
Router#echo test1 > flash:file1
Router#echo test2 > flash:file2
Router#cat flash:file1 flash:file2
test1test2Router#
なんか普通すぎて面白みに欠けるため、この案は不採用になりました。
案2: bannerに記録する
banner motd
的なところに発言内容を追記していく方式です。
容量的に懸念があったので、ちょっと検証してみました。
Router(config)#banner ^
Enter TEXT message. End with the character '^'.
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
----- 省略 -----
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
1234567890123456789012345678901234567
Maximum Banner size 3000 bytes reached. Banner message is truncated
Router(config)#$23456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
^
% Invalid input detected at '^' marker.
3000byteでアウト。容量的に辛いので、この案は不採用になりました。
案3: ログに記録する
send
は、ユーザに直接メッセージを送るだけでなく、ログに任意の文字列を出力させることもできます。
Router#send log hogehoge
Router#sh log | tail 1
Dec 12 2016 03:48:11: %SYS-7-USERLOG_DEBUG: Message from tty1(user id: hello@tanksuzuki.com): hogehoge
Router#
これは素晴らしい。
- WriteもReadもコマンド化されてる。
- バッファから溢れたログは自動破棄されるので管理が楽。
- バッファを増やすことで、ある程度の量の発言を保持できる。
ということで、ログ領域をNoSQL的に使うことに決めました。
JSONを突っ込んでしまいましょう。
SSHクライアント
役割は2つです。
- APIで渡されたメッセージを、ルータにログインして
send log
する。 - APIでメッセージ取得要求を受けたら、ルータにログインして
sh log
する。
メッセージ構造は下記の通り。
この構造体をJSON化してログに突っ込むんですが、send log
時にダブルクォーテーションが削られてしまうため、全体をBase64化してJSON構文を保護しています。
type Message struct {
ID string `json:"id"`
Date string `json:"date"`
Owner string `json:"owner"`
Body string `json:"body"`
}
下記は入れた結果です。
Dec 12 2016 04:28:35: %SYS-7-USERLOG_DEBUG: Message from tty1(user id: hello@tanksuzuki.com): eyJpZCI6IjZiN2YzZmQ2YTliZjM1ZWFkY2U5NjE1OGQ3YjEzYzI2ZjE0ZDM3ZDAiLCJkYXRlIjoiMjAxNi0xMi0xMlQwNDoyODozM1oiLCJvd25lciI6ImhlbGxvQHRhbmtzdXp1a2kuY29tIiwiYm9keSI6InRlc3QifQ==
API / Web UI
inputに入れた文章は、APIサーバのPOST /api/message
経由で SSHクライアントに渡されます。
また、30秒ごとにAPIサーバのGET /api/messages
を叩いてメッセージ一覧を取得し、Riotで描画しています。
アバター画像はGravatarから取得しています。
Gravatarに登録しているメールアドレスを、SSHのユーザ名にすればOKです。
課題
大きなところで3点あります。
課題1: 発言データは揮発性
ログはメモリに乗っているだけなので、再起動すると揮発します。
ログ領域をDB化する以上、半ば最初から諦めていた部分ですが、できることなら何とかしたいです。
課題2: ログの文字数制限
ログ1行あたりの文字数上限が340文字でした。
それ以上の文字数をsend log
しても、溢れた部分が削られるだけでコマンドとしては正常終了します。
長い文章を送るとJSONの後半部分が落ちて不良レコード化するため、現状だと半角40文字くらいまでがメッセージの上限になっています。
課題3: Read/Writeが遅すぎる
ストレスを感じるレベルで読み書きに時間がかかります。
ルータをDBにするのはやめた方が良いかもしれません。
今後について
まだ構想段階ですが、クライアントとDB(ルータ)の間に挟むコントローラ的なものがあれば劇的な改善が見込めそうです。
- 複数のDB(ルータ)に対し、RAID5的にレコードを分散配置して耐障害性UP。
- 長いレコードは1行に収まる程度に細切れにして分散配置し、コントローラ上で結合処理。
- Writeを非同期化し、Readはコントローラ上のキャッシュで高速化する。
今度作ります。