systemdから出るjournalログをfluentdで扱う
Amazon Linux 2から、常駐サービスはsystemdで扱われるようになりました。systemdで動かしているアプリケーションのログをfluentdに取り込んで、kibanaやらS3やらに流したいので方法を調べてみました。結論を先に言うと、fluent-plugin-systemdを使ってやりましょう。
systemdで動かしているサービスは、標準でjournaldに出力され、journalctlコマンドを使ってログを見ることができます。sshで繋いで見る分にはいいのですが、fluentdなどで収集して扱うにはどうすればいいかなと。今回はアプリケーションに手を入れられないケースで行ったので、アプリケーションに直接ファイルにログを吐く機構があれば、それを使ってファイルに出して、in_tailで読み込むのもありです。
ファイルに出してin_tailすれば良いのでは
systemdで動作しているアプリケーションは、通常はjournaldに出力され、ファイルには出力されません。systemdのversion 234からはファイル出力、version 240からは追記オプションが使えるようになっているとのことですが、Amazon linux 2はversion 219が入っているようで、まだこのオプションは使えませんでした。
みんな考えることは一緒で、一種の回避策はありました。シェルを経由してリダイレクトさせることでファイルに出力するというものです。
シェルでラップして、リダイレクトを行うもので、以下実際に動作しました。
ExecStart=/bin/sh -c 'exec /home/ec2-user/echoman >> /var/log/echoman.log 2>&1'
しかし、ログをファイルに出力すること自体は成功したのですが、logrotateと相性が悪いです。リダイレクトしている途中で、logrotateが動作し別のファイルに変わっても、このリダイレクト先は変わらないので、ローテートされた(renameされたechoman.log.1みたいな方)のファイルに出力され続けます。
logrotateのpre/postでアプリケーションのrestartを呼び出すことはできますが、アプリケーション自体がgraceful stopに対応していないと、ローテートのタイミングでダウンタイムが発生します。特定のシグナルでログを開き直すパターンもありますが、回避策はシェルでリダイレクトしているので、シグナルを送るだけではこの部分は変化ありません。
それなら、ローテート時にファイルをコピーして、元のファイルを切り詰めて使い続けるcopytruncateを使えばいい?と考えましたが、これは元ファイルのコピー完了からtruncateするまでの間の書き込みが消える可能性があります。*1
個人で使っている場合や、厳密にログを残す必要がないケースなど、特に少しのロストが問題なければ、取りこぼしを許容してしまうのも一つの判断です。夜間などの特定時間帯にはアクセスなどがなく、ロストするログがそもそも存在しない場合も許容できます。
fluent-plugin-systemd
前述の課題もあり、journaldに吐きながら、それをfluentdで読み込んで、elasticsearch(kibana)やs3へ転送したりと使うことにしました。systemd向けのプラグインがちゃんとありました。
普通にプラグインを入れれば動きますが、注意点としてfluentdを動かすユーザをsystemd-journal グループに入れる必要があります。これがないと、journalログが読めません。これはREADMEのQAにも書いてありますが、忘れててもfluentdはうんともすんとも言わない*2ので、ハマりやすいポイントなんだろうなと思います。
fluentdを動かす時のユーザ(td-agentなど)をsystemd-journalグループに追加します。すでにfluentdを動かしていたら、fluentdをrestartさせてください。reloadだと読み込めない状態が解消しません。
# td-agentユーザをsystemd-journalグループに追加 sudo usermod -aG systemd-journal td-agent # td-agent再起動 sudo systemctl restart td-agent
簡単なサンプル
systemdで動いているsshdのログを、td-agentのログに出すサンプルです(systemdプラグインのサンプルをkubectlからsshに変えただけです)
<source> @type systemd tag sshd path /var/log/journal matches [{ "_SYSTEMD_UNIT": "sshd.service" }] read_from_head true <storage> @type local path /var/log/td-agent/sshd-cursor.json </storage> <entry> fields_strip_underscores true fields_lowercase true </entry> </source> <match sshd> @type stdout </match>
まとめ
最近はコンテナ化されていて、EC2ではなくECSなどで動かすことも多いかと思いますが、ストレージを使うミドルウェアであったり、様々な理由でEC2で動かすアプリケーションもあるかと思います。Amazon linux 2以外にも多くのディストリビューションでは、systemdが採用されているので、知っているとすぐ対応できます。
また、回避策は色々ありますが、実現されることに加えて、副作用の考慮やトレードオフが必要になるケースもあるので、原理をもとに他の問題を引き起こさないかの判断が必要ですね。
参考
- 【systemd】StandardOutput=fileはログをファイルに出力できるけど追記されない | 純規の暇人趣味ブログ
- systemd で動くプログラムの標準出力をファイルに出力させたい - えいのうにっき
- GitHub - fluent-plugin-systemd/fluent-plugin-systemd: This is a fluentd input plugin. It reads logs from the systemd journal.
- Linuxでユーザーをグループに追加するコマンド | LFI
- logrotateの設定 - KamoLand
- systemdからFluentdにデータを流し込む - Qiita