年中アイス

いろいろつらつら

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が入っているようで、まだこのオプションは使えませんでした。

jyn.jp

みんな考えることは一緒で、一種の回避策はありました。シェルを経由してリダイレクトさせることでファイルに出力するというものです。

blog.a-know.me

シェルでラップして、リダイレクトを行うもので、以下実際に動作しました。

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向けのプラグインがちゃんとありました。

github.com

普通にプラグインを入れれば動きますが、注意点として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が採用されているので、知っているとすぐ対応できます。

また、回避策は色々ありますが、実現されることに加えて、副作用の考慮やトレードオフが必要になるケースもあるので、原理をもとに他の問題を引き起こさないかの判断が必要ですね。

参考

*1:あるよね?

*2:おそらくjournald自体はアクセスできるものの、ログがないと判断されてエラーになってないので検出できない?