Edited at

シスコルータのチャットを自動化する

More than 1 year has passed since last update.

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 モジュールの設定例


echoやcatで繋げる例

Router#echo test1 > flash:file1

Router#echo test2 > flash:file2

Router#cat flash:file1 flash:file2
test1test2Router#


なんか普通すぎて面白みに欠けるため、この案は不採用になりました。


案2: bannerに記録する

banner motd的なところに発言内容を追記していく方式です。

容量的に懸念があったので、ちょっと検証してみました。


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構文を保護しています。


cchat.go

type Message struct {

ID string `json:"id"`
Date string `json:"date"`
Owner string `json:"owner"`
Body string `json:"body"`
}

下記は入れた結果です。


show_log

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はコントローラ上のキャッシュで高速化する。

今度作ります。