こんにちは。ご機嫌いかがでしょうか? SREチームの栗山(id:shepherdMaster)です。
弊社ではKubernetesを導入するために着々と準備を進めております。
どんなシステム上でアプリケーションを動かすにせよ、ログ収集は必要になってきます。
Kubernetes上でログ収集をするために色々調べましたが実用的な情報があまり豊富ではなかったので、今回はKubernetes上でのログ収集、特にFluentdの設定について共有をしたいと思います。
なおまだ実運用は開始してないので今後細かい部分は変わるかもしれません。
ログ収集&ログ分析の構成
構成は以下にしました。
Fluentd + S3 + Amazon Athena
理由は以下です。
- S3に保存すると非常に安い
- SQLでログを検索できるのは非常に便利
- Fluentdの設定の柔軟性
- 既存のログ収集基盤がFluentd + S3 + Amazon Athenaになっていたため、資産の流用ができ、学習コストや管理コストも抑えられる
ログ収集ツールとしてはより軽量なFluent Bitも考えましたが、S3に保存するためのoutput pluginがなかったので諦めました。
Kubernetes上でのログ取集の課題
Kubernetes上でのログ収集を進めていくうちに課題がいくつか出てきました。
まずログ収集の全体の流れですが、コンテナの標準出力/標準エラー出力結果がホストマシンの/var/log/containers以下にファイル出力されます。そしてFluentdがそのファイルをtailし、S3に保存する流れになります。
このとき、/var/log/containers以下出力されるファイルに難があります。 具体的に/var/log/containers以下にあるログを見てみましょう。
{ "log": "time:2020-03-11T11:09:55+00:00\thost:10.3.48.x\tvhost:health-check.studyplus.jp\tserver:deployment-7f94cc5958-bgs7g\treq:GET /health_check HTTP/1.1\turi:/health_check\tstatus:200\tmethod:GET\treferer:-\tua:kube-probe/1.14+\treq_time:0.011\tapp_revision:-\truntime:0.009058\tcache:-\tapp_time:0.008\trequest_id:9967a0a0a6486435accf64b495da67b0\tx_request_id:-\tres_size:0\treq_size:177\n", "stream": "stdout", "time": "2020-03-11T11:09:55.217300042Z" }
log
の部分にコンテナが出力したログが入っています。 ちなみに上記はnginxのログですが、nginxはLTSV形式でログ出力するようにしています。なので\t
という文字がところどころ入っています。また厄介なことに末尾に\n
が入ってます。
stream
にはstdout(標準出力)かstderr(標準エラー)かが入ります。
このことから、
- コンテナが出力したログをS3に保存するためには、JSONの
log
値を取り出さないといけない。 log
値の末尾の\n
を削除する必要がある(じゃないとJSONとしてはinvalid扱いになる)- ログの中を見ないと標準出力ログなのか、標準エラー出力ログなのか分からない。
ということが分かります。 つまり、単純に /var/log/containers以下のログをそのままS3に放り込んでもAthenaでは、log値をlike検索するくらいしかできません。 それだと調査のときにログを柔軟に絞り込むことが出来なくて困るので、ログが適切な形でS3に保存されるようにFluentdの設定をする必要があります。
最終的にやりたいことを整理すると、
- コンテナごとに保存先を変えたい
- 標準出力ログ、標準エラーログで、保存先を変えたい
- なぜかというとnginxはエラーログに対してログフォーマットを指定できないので、保存先を変えてエラーログはAthenaから通常のログとは別に検索したい
- コンテナが出力したログをそのまま保存したい
FluentdをDaemonSetで動かすには
Fluentdの設定ファイルの前にまずFluentdをDaemonSetで動かします。
https://github.com/fluent/fluentd-kubernetes-daemonset で提供されているものを使うと比較的楽にDaemonSetで動かすことができます。
https://github.com/fluent/fluentd-kubernetes-daemonset/tree/master/docker-image 以下にバージョンごとにディレクトリがありますが、さらにそのディレクトリ以下をみると出力先に応じたimageが用意されています。
S3に保存したいので、debian-s3
を選びました。
以下が具体的なマニフェストファイルの内容です。
apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-daemonset spec: selector: matchLabels: name: fluentd-pod template: metadata: labels: name: fluentd-pod spec: containers: - name: fluentd-container image: fluent/fluentd-kubernetes-daemonset:v1.8-debian-s3-1 env: - name: S3_BUCKET_NAME value: <バケット名> - name: FLUENTD_SYSTEMD_CONF value: disable resources: requests: cpu: 200m memory: 500Mi limits: memory: 1Gi volumeMounts: - name: config-volume mountPath: /fluentd/etc - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true volumes: - name: config-volume configMap: name: fluentd-configmap - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers
独自のfluent.confを配置するために、ConfigMapを使ってvolumeマウントをしています。こうすることで、/fluentd/etc/以下にfluent.confが配置されます。 そうしないと https://github.com/fluent/fluentd-kubernetes-daemonset/tree/master/docker-image/v1.9/debian-s3/conf 以下にあるfluent.confが使用されます。
/var/logと/var/lib/docker/containersの両方をvolumeマウントしている理由は、/var/log以下のログは/var/lib/docker/containers以下のログにシンボリックリンクがはられているので両方マウントしないとログが読み込めないためです。
FLUENTD_SYSTEMD_CONF
をdisableにしているのはここにあるように不要なログを出力しないためです。
fluent.confの設定
実際には、RailsのログやFluentdのログも処理するように設定を書いてますが、今回は話をシンプルにするためにnginxのログ設定のみを書いてます。
# /var/log/containers以下には様々なコンテナのログが保存されているので、pathで該当のコンテナのログを指定します。 <source> @type tail path "/var/log/containers/*_nginx-container-*.log" pos_file /var/log/nginx-container.log.pos tag nginx read_from_head true <parse> @type json time_format %Y-%m-%dT%H:%M:%S.%NZ </parse> </source> # logの値の末尾に\nがつくので削除する <filter nginx> @type record_transformer enable_ruby <record> log ${record["log"].strip} </record> </filter> # 標準出力と標準エラーでtagを分ける <match nginx> @type rewrite_tag_filter <rule> key stream pattern /^stdout$/ tag "${tag}.stdout" </rule> <rule> key stream pattern /^stderr$/ tag "${tag}.stderr" </rule> </match> # jsonのlog値にコンテナが出力ログが入っているのでを取り出す <filter nginx.stdout> @type parser key_name log <parse> @type ltsv keep_time_key true types status:integer, req_time:float, runtime:float, app_time:float, res_size:integer, req_size:integer </parse> </filter> <filter nginx.stderr> @type parser key_name log <parse> @type none </parse> </filter> # S3に保存する <match nginx.stdout> @type s3 format json s3_bucket "#{ENV['S3_BUCKET_NAME']}" s3_region ap-northeast-1 s3_object_key_format "${tag[0]}-log/%{time_slice}/${tag[0]}-%{index}.log.%{file_extension}" time_slice_format year=%Y/month=%m/day=%d/hour=%H <buffer tag,time> @type file path "/var/log/fluentd-buffers/s3.buffer" timekey 3600 timekey_wait 10m timekey_use_utc true chunk_limit_size 1G flush_at_shutdown true </buffer> </match> <match nginx.stderr> @type s3 format single_value s3_bucket "#{ENV['S3_BUCKET_NAME']}" s3_region ap-northeast-1 s3_object_key_format "${tag[0]}-error-log/%{time_slice}/${tag[0]}-error-%{index}.log.%{file_extension}" time_slice_format year=%Y/month=%m/day=%d/hour=%H <buffer tag,time> @type file path "/var/log/fluentd-buffers/s3-error.buffer" timekey 3600 timekey_wait 10m timekey_use_utc true chunk_limit_size 1G flush_at_shutdown true </buffer> </match>
ちなみに、fluent.conf内で使えるfluentd pluginは https://github.com/fluent/fluentd-kubernetes-daemonset/blob/master/docker-image/ 以下にあるGemfile(たとえばこれ)内に定義されているものが使えます。
おまけ
Fluentdの設定ファイルを書いていると、すぐにログをflushしてS3に保存し、保存されたログファイルの中身を確認したいケースがでてきます。そういうときは、USR1 signalを送ると強制的にflushしてくれます。 たとえば以下のようなワンライナーを用意しておくと便利です。
kubectl exec `kubectl get pod -o name | grep fluentd` -- /bin/sh -c "pkill -USR1 -f fluentd"
まとめ
Kubernetes上で動くアプリケーションのログ収集のために、FluentdのDaemonSetリソースファイルとfluent.confの紹介をしました。 Dockerが出力する癖のあるログによって苦戦しましたが、Fluentdの柔軟さによって助けられました。
それでは、みなさん良きログ収集ライフを。