systemdからFluentdにデータを流し込む

#時代はsystemd

…だそうです。巷ではinit v. systemd論争が続いていますが、Fluentdのモットーは[「世の中の全ての開発者がデータを手軽に正しく集められる」](http://kzk9.tumblr.com/post/94052741274/fluentd-treasure-data)ことですので、systemdだろうが頭文字Dだろうが、拘りはありません。

td-agentもCentos7版が[昨日リリース](http://docs.treasuredata.com/articles/td-agent2-changelog#20141223-version-v213)されましたので、満を持してsystemdで管理されているサービスのログをどうやってFluentdで集められるのか、現状をシェアしたいと思います。

#journalctl -o

ご存知の方も多いでしょうが、systemd環境では、journalctlがデフォルトのログのビューワとなります。例えば`auditd`のログをtailしたい場合は

```shell-session
$ journalctl -u auditd -f
-- Logs begin at Mon 2014-12-22 13:36:30 EST. --
Dec 22 13:36:31 systemd-fluentd auditd[264]: Started dispatcher: /sbin/audispd p...3
Dec 22 13:36:31 systemd-fluentd audispd[273]: priority_boost_parser called with: 4
Dec 22 13:36:31 systemd-fluentd audispd[273]: max_restarts_parser called with: 10
Dec 22 13:36:31 systemd-fluentd audispd[273]: No plugins found, exiting
Dec 22 13:36:31 systemd-fluentd augenrules[265]: /sbin/augenrules: No change
Dec 22 13:36:31 systemd-fluentd auditd[264]: Init complete, auditd 2.3.3 listeni...)
Dec 22 13:36:31 systemd-fluentd augenrules[265]: No rules
Dec 22 13:36:31 systemd-fluentd augenrules[265]: AUDIT_STATUS: enabled=1 flag=1 ...1
Dec 22 13:36:31 systemd-fluentd systemd[1]: Started Security Auditing Service.
Dec 22 15:09:10 systemd-fluentd auditd[264]: Audit daemon rotating log files
```

みたいな感じになります。`-f`は`tail`のオプションと同じで、`-u`でサービス名のフィルタリングができます。他にも`--since`と`--until`で時間を区切れたりして、結構万能です。

機能が豊富な`journalctl`ですが、Fluentd的にもっともオイシイのは`-o`オプションです。これはアウトプットのフォーマッターオプションであり、`-o json`と指定することで、一行一JSONの形式で出力してくれます。

```shell-session
$ journalctl -u auditd -f -o json
{ "__CURSOR" : "s=09186fb5df0c4f1ba9a259995faefc2b;i=26b;b=3b8f00a5c3c7428aabec902ecdbce64e;m=1b6eef;t=50ad256595cd2;x=9cb8e91012839fc6", "__REALTIME_TIMESTAMP" : "1419273391660242", "__MONOTONIC_TIMESTAMP" : "1797871", "_BOOT_ID" : "3b8f00a5c3c7428aabec902ecdbce64e", "PRIORITY" : "6", "_UID" : "0", "_GID" : "0", "_MACHINE_ID" : "fd8cf26e06e411e4a9d004010897bd01", "SYSLOG_IDENTIFIER" : "systemd", "SYSLOG_FACILITY" : "3", "_TRANSPORT" : "journal", "_PID" : "1", "_COMM" : "systemd", "_EXE" : "/usr/lib/systemd/systemd", "_CAP_EFFECTIVE" : "1fffffffff", "_SYSTEMD_CGROUP" : "/", "CODE_FILE" : "src/core/job.c", "CODE_LINE" : "732", "CODE_FUNCTION" : "job_log_status_message", "MESSAGE_ID" : "39f53479d3a045ac8e11786248231fbf", "RESULT" : "done", "_HOSTNAME" : "systemd-fluentd", "_CMDLINE" : "/usr/lib/systemd/systemd --switched-root --system --deserialize 23", "UNIT" : "auditd.service", "MESSAGE" : "Started Security Auditing Service.", "_SOURCE_REALTIME_TIMESTAMP" : "1419273391658951" }
...
```

うわ、なんかいろいろ出てきましたね！Fluentd的には`in_tcp`と`format json`が使えるので、これは非常にありがたいですが、人間的にはツラいので、一先ず`-o json-pretty`で中身を確認してみましょう。

```shell-session
$ journalctl -u auditd -f -o json-pretty
{
        "__CURSOR" : "s=09186fb5df0c4f1ba9a259995faefc2b;i=a3b;b=3b8f00a5c3c7428aabec902ecdbce64e;m=14b71339a;t=50ad3a1af217d;x=e37f04158d5689d0",
        "__REALTIME_TIMESTAMP" : "1419278950539645",
        "__MONOTONIC_TIMESTAMP" : "5560677274",
        "_BOOT_ID" : "3b8f00a5c3c7428aabec902ecdbce64e",
        "_UID" : "0",
        "_GID" : "0",
        "_SYSTEMD_SLICE" : "system.slice",
        "_MACHINE_ID" : "fd8cf26e06e411e4a9d004010897bd01",
        "PRIORITY" : "5",
        "SYSLOG_FACILITY" : "3",
        "_CAP_EFFECTIVE" : "1fffffffff",
        "_HOSTNAME" : "systemd-fluentd",
        "_TRANSPORT" : "syslog",
        "SYSLOG_IDENTIFIER" : "auditd",
        "SYSLOG_PID" : "264",
        "_PID" : "264",
        "_COMM" : "auditd",
        "_EXE" : "/usr/sbin/auditd",
        "_CMDLINE" : "/sbin/auditd -n",
        "_SYSTEMD_CGROUP" : "/system.slice/auditd.service",
        "_SYSTEMD_UNIT" : "auditd.service",
        "MESSAGE" : "Audit daemon rotating log files",
        "_SOURCE_REALTIME_TIMESTAMP" : "1419278950538648"
}
...
```

"PRIORITY", "SYSLOG_FACILITY", "MESSAGE"といった従来のsyslog的なフィールドに加え、Hostnameが変わっても永続的にサーバーを特定できる"_MACHINE_ID"など、役に立ちそうなフィールドがたくさんあります。

Fluentd的にはどのフィールドをtimestampにするのかが重要ですが、ここでは"__SOURCE_REALTIME_TIMESTAMP"を使います（注：単位がマイクロ秒です）。

systemd側としては、`journalctl -f -o json | nc localhost <FLUENTD_PORT>`でデータを流すだけです。

# Fluentd側の設定

Fluentd側の設定はこんな感じになります。

```aconf
<source>
  type tcp
  port 24225
  format json
  time_key _SOURCE_REALTIME_TIMESTAMP
  time_format %S
  tag systemd
</source>
<match systemd>
  type stdout
</match>
```

この設定でFluentdを起動します。

```
$ fluentd -c fluent.conf
```

そしてjournatlctlからnc経由でデータを送りつけると…

```
$ journalctl -f -o json | nc localhost 24225
```

```js
2014-12-22 18:34:14 -0500 systemd: {"__CURSOR":"s=09186fb5df0c4f1ba9a259995faefc2b;i=4a0c;b=3b8f00a5c3c7428aabec902ecdbce64e;m=42b5c334d;t=50ad6819a2130;x=3e03baaff28f6db5","__REALTIME_TIMESTAMP":"1419291297194288","__MONOTONIC_TIMESTAMP":"17907331917","_BOOT_ID":"3b8f00a5c3c7428aabec902ecdbce64e","_TRANSPORT":"syslog","PRIORITY":"6","SYSLOG_FACILITY":"10","SYSLOG_IDENTIFIER":"sshd","_UID":"0","_GID":"0","_COMM":"sshd","_EXE":"/usr/sbin/sshd","_CMDLINE":"sshd: root [priv]   ","_CAP_EFFECTIVE":"1fffffffff","_SYSTEMD_CGROUP":"/system.slice/sshd.service","_SYSTEMD_UNIT":"sshd.service","_SYSTEMD_SLICE":"system.slice","_MACHINE_ID":"fd8cf26e06e411e4a9d004010897bd01","_HOSTNAME":"systemd-fluentd","MESSAGE":"pam_succeed_if(sshd:auth): requirement \"uid >= 1000\" not met by user \"root\"","SYSLOG_PID":"8331","_PID":"8331"}
```

ちゃんとデータが流れてきました！

# これから先の話

将来的には外部プログラムに頼らずに、Fluentdから直接sd_journal APIを叩きたいところです。今のところ[一応Rubyのバインディング](https://github.com/ledbettj/systemd-journal)はあるのですが、少しまだ挙動が把握しきれてなかったので、今回は`journalctl`を使うアプローチを紹介しました>_<

それではMerry systemd + Happy Fluentd!