Ciscoルーターに特定のSyslogが発生したら事前設定されたアクションを実行したい

ルーターやスイッチに特定のイベントが発生したら事前設定されたアクションを実行したい、というのはよくあるシチュエーションだと思う。Cisco IOS, IOS-XE, IOS-XRなどでは[EEM (Embedded Event Manager)](https://gblogs.cisco.com/jp/2017/12/ios-use-case-embedded-event-manager/)を使って実現が可能で、ゲストシェルが有効な装置であればOn-BoxのPythonによるスクリプトを定義しておくこともできる。

ほとんどのケースではこのEEMを使った運用で充分に用をなすだろうけれど、より複雑な挙動を外部スクリプトで実現したい場合は…？
例えばBundle-Etherを構成している2ポートのうち1ポートがLinkdownした場合に即座に冗長系のポートの正常性確認がしたい場合はどうだろう…？

ということで、特定のsyslogをトリガーにFluentdを利用して外部サーバーのスクリプトを実行する仕組みを考えてみた。

## 構成

今回の構成はこんな感じ。

```
                   +--------------------------------+
                   |                                |
                   |   +---------+        server    |
+--------+ 514/udp |   |         |                  |
| client +------------->         |                  |
+--------+         |   |         |                  |
                   |   | rsyslog |                  |
+--------+ 514/udp |   |         |                  |
| client +------------->         |                  |
+--------+         |   |         |                  |
                   |   +----+----+                  |
                   |        |                       |
                   |        | 5514/tcp              |
                   |        |                       |
                   |   +----v-----+  +----------+   |
                   |   | fluentd  +-->  script  |   |
                   |   +----------+  +----------+   |
                   |                                |
                   +--------------------------------+

```

ルーターからのsyslogをRsyslogの514/udpポートで受けてFluentdの5514/tcpポートへ転送。fluentdのout_execモジュールでスクリプトを実行する。

syslogはFluentdで直接受信するよう設定してもよいが、すでにサーバーで稼働しているであろうrsyslogをそのまま利用する構成としてみた。


## 設定

まずはRsyslogの設定。/etc/rsyslog.d/50-forward.confファイルに以下を記述する。

```
$ModLoad imudp
$UDPServerRun 514

$ActionQueueType LinkedList # use asynchronous processing
$ActionQueueFileName srvrfwd # set file name, also enables disk mode
$ActionResumeRetryCount -1 # infinite retries on insert failure
$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down

*.* @@127.0.0.1:5514;RSYSLOG_SyslogProtocol23Format
```

次にFluentdの安定配布版であるtd-agentの設定。/etc/td-agent/td-agent.confに以下を追記する。

```
<source>
  @type syslog
  @id syslog_in
  port 5514
  bind 0.0.0.0
  severity_key severity
  facility_key facility
  tag syslog
  <transport tcp>
  </transport>
  <parse>
    message_format rfc5424
  </parse>
</source>

<match syslog.**>
  @type rewrite_tag_filter
  <rule>
    key message
    pattern /%([A-Z0-9_]+-[0-9]+-[A-Z0-9_]+):/
    tag $1.${tag}
  </rule>
</match>

<match LINK-3-UPDOWN.syslog.**>
  @type exec
  command /path/to/script.py
  <buffer>
    @type file
    path /var/log/td-agent/buffer/LINK-3-UPDOWN.syslog
    retry_max_times 3
    flush_interval 1m
  </buffer>
  <format>
    @type json
  </format>
</match>

## 不要なsyslogはnullに捨てておく
<match *.syslog.**>
  @type null
</match>
```

ここでポイントは `rewrite_tag_filter` を使ってタグを細分化している点。Cisco装置のsyslogは[公式ドキュメントに記載されているように](https://www.cisco.com/c/ja_jp/td/docs/wl/wllancntrller/5700wllancntrllers/cg/016/b_sm_3e_5700_cg/b_sm_3e_5700_cg_chapter_01111.html)一定のフォーマットで表示される。
例えばIOS-XEの場合はこんな感じ。 

```
timestamp: %facility-severity-MNEMONIC:description

例:
00:00:47: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/1, changed state to up
```

この`rewrite_tag_filter`によりログ種別のフォーマット沿ってtd-agent側でタグ付けをしてくれるようになる。
あとはこのタグにひっかけて`exec`でスクリプトを叩くという流れ。

上記のtd-agentの設定では、発生した`LINK-3-UPDOWN`ログは`/var/log/td-agent/buffer/LINK-3-UPDOWN.syslog/`ディレクトリにJSON形式でバッファファイルとして蓄えられ、1分毎にそれを引数として`/path/to/script.py`が実行される。

つまりtd-agentが起動するコマンドは以下の形式になる。`/path/to/script.py`に実行権限を付与しておくのを忘れずに。

```
/path/to/script.py /var/log/td-agent/buffer/LINK-3-UPDONW.syslog/buffer.{random}.log
```

バッファファイルの中身は以下のようなフォーマットになっている。`/path/to/script.py`では引数で与えられたこのJSONを読んでmessageを解析し、任意のスクリプトを実行すればよい。


```
{"host":"router1.example.com","message":"00:00:47: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/1, changed state to up","priority":"notice","facility":"local7","ident":"11165741","pid":"-","msgid":"-","extradata":"-"}
{"host":"router2.example.com","message":"00:00:47: %LINK-3-UPDOWN: Interface GigabitEthernet2/0/1, changed state to up","priority":"notice","facility":"local7","ident":"99618182","pid":"-","msgid":"-","extradata":"-"}
```

あとは`/path/to/script.py`にお好きな処理を書けばよい。
面倒なのでここではスクリプトの書き方は省略する。Ansibleなんかに連携させれば良いんじゃないかな？

これで自由自在にSyslogトリガーに外部コマンドを叩けるようになる。Slack通知やJira連携とかもできそう。
楽しい:v: