GKE はどのようにログを収集しているのか
Ubie で SRE をしている @kamina_zzz です。
Ubie では Kubernetes を GKE で運用しているんですが、GKE だと container の STDOUT がいい感じに Cloud Logging へと集約されていますよね。
この経路、以前は Fluentd が使われていたんですが、近年は Fluent Bit が使われています。
Fluent Bit になってからどのようにしてログを集約していい感じに Cloud Logging へ送信しているのか気になってしまったので今回はこの辺りをまとめてみます。
さしあたって今回使用するバージョンはこんな感じです。
- Kubernetes: v1.18.16-gke.502
- Fluent Bit: v1.3.11[1]
- GKE にバンドルされてるバージョンがこれ
はじめに
そもそも Fluent Bit の用語や仕組みをある程度知っておかないとお話にならないと思うのでその辺を簡単に整理していきます。
Fluent Bit は Fluentd と同じく Treasure Data によって立ち上げられたプロジェクトで、Fluentd は Ruby の gem として提供されていたのに対し、Fluent Bit は全て C で書かれた zero dependency なプロダクトになっています[2]。
アーキテクチャはこんな感じです。
公式ドキュメントより抜粋
このひとくくりを Service
と呼んでいて、この中の Input や Filter, Output なんかをそれぞれカスタマイズして行くことでどんなデータソースから入力を受けてどのような処理を行い、どこに流していくかを制御することができます。
Configuration も Fluentd に比べると非常に見やすく[3]なっており、ひと目で何をしているかわかるかと思います[4]。
# sample config
[SERVICE]
Daemon off
log_level debug
Flush 1
[INPUT]
Name tail
Path /var/log/syslog
[OUTPUT]
Name stdout
Match *
また、Input から入ってきたデータは基本的には Fluent Bit のメモリ上に一度載せて処理をし、順次 Output Plugin へと流していく形になります。
詳細はこの辺のドキュメントを参照してもらうとちゃんと書かれています。
GKE での設定を読んでいく
Fluent Bit は GKE では kube-system
namespace に DaemonSet としてデプロイされています。
DaemonSet の yaml フルバージョンはこちら
Name: fluentbit-gke
Selector: component=fluentbit-gke,k8s-app=fluentbit-gke
Node-Selector: kubernetes.io/os=linux
Labels: addonmanager.kubernetes.io/mode=Reconcile
k8s-app=fluentbit-gke
kubernetes.io/cluster-service=true
Annotations: deprecated.daemonset.template.generation: 5
Desired Number of Nodes Scheduled: 6
Current Number of Nodes Scheduled: 6
Number of Nodes Scheduled with Up-to-date Pods: 6
Number of Nodes Scheduled with Available Pods: 6
Number of Nodes Misscheduled: 0
Pods Status: 6 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: component=fluentbit-gke
k8s-app=fluentbit-gke
kubernetes.io/cluster-service=true
Annotations: EnableNodeJournal: false
EnablePodSecurityPolicy: false
SystemOnlyLogging: false
components.gke.io/component-name: fluentbit
components.gke.io/component-version: 1.3.8
monitoring.gke.io/path: /api/v1/metrics/prometheus
Service Account: fluentbit-gke
Containers:
fluentbit:
Image: gke.gcr.io/fluent-bit:v1.3.11-gke.0
Port: 2020/TCP
Host Port: 2020/TCP
Limits:
memory: 250Mi
Requests:
cpu: 50m
memory: 100Mi
Liveness: http-get http://:2020/ delay=120s timeout=1s period=60s #success=1 #failure=3
Environment: <none>
Mounts:
/fluent-bit/etc/ from config-volume (rw)
/var/lib/docker/containers from varlibdockercontainers (ro)
/var/lib/kubelet/pods from varlibkubeletpods (rw)
/var/log from varlog (rw)
/var/run/google-fluentbit/pos-files from varrun (rw)
fluentbit-gke:
Image: gke.gcr.io/fluent-bit-gke-exporter:v0.14.2-gke.0
Port: 2021/TCP
Host Port: 2021/TCP
Command:
/fluent-bit-gke-exporter
--kubernetes-separator=_
--stackdriver-resource-model=k8s
--enable-pod-label-discovery
--pod-label-dot-replacement=_
--split-stdout-stderr
--logtostderr
Limits:
memory: 250Mi
Requests:
cpu: 50m
memory: 100Mi
Liveness: http-get http://:2021/healthz delay=120s timeout=1s period=60s #success=1 #failure=3
Environment: <none>
Mounts: <none>
Volumes:
varrun:
Type: HostPath (bare host directory volume)
Path: /var/run/google-fluentbit/pos-files
HostPathType:
varlog:
Type: HostPath (bare host directory volume)
Path: /var/log
HostPathType:
varlibkubeletpods:
Type: HostPath (bare host directory volume)
Path: /var/lib/kubelet/pods
HostPathType:
varlibdockercontainers:
Type: HostPath (bare host directory volume)
Path: /var/lib/docker/containers
HostPathType:
config-volume:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: fluentbit-gke-config-v1.0.6
Optional: false
Priority Class Name: system-node-critical
Events: <none>
これを見ると色々わかることがあります。
- コンテナは
fluentbit
とfluentbit-gke
の2つ -
config-volume
という名前でマウントされている ConfigMap がいかにも設定ファイルっぽい- name も
fluentbit-gke-config-v1.0.6
だしね[5]
- name も
- それぞれ HTTP を受け付けている
- PORT は 2020 と 2021
早速 config-volume
の中身を見てみると、確かに正解で、こいつに Fluent Bit の設定がしっかり書かれています。
中身は fluent-bit.conf
と parsers.conf
でした。
フルは流石に長すぎるので自分で見てもらうとして、大きく関連する部分を抜き出すとこんな感じです。
[SERVICE]
Flush 5
Grace 120
Log_Level info
Log_File /var/log/fluentbit.log
Daemon off
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_PORT 2020
[INPUT]
Name tail
Alias kube_containers
Tag kube_<namespace_name>_<pod_name>_<container_name>
Tag_Regex (?<pod_name>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace_name>[^_]+)_(?<container_name>.+)-
Path /var/log/containers/*.log
Exclude_Path /var/log/containers/*_kube-system_*.log,/var/log/containers/*_istio-system_*.log,/var/log/containers/*_knative-serving_*.log,/var/log/containers/*_gke-system_*.log,/var/log/containers/*_config-management-system_*.log
DB /var/run/google-fluentbit/pos-files/flb_kube.db
Buffer_Max_Size 1MB
Mem_Buf_Limit 5MB
Skip_Long_Lines On
Refresh_Interval 5
[OUTPUT]
Name http
Match *
Host 127.0.0.1
Port 2021
URI /logs
header_tag FLUENT-TAG
Format msgpack
Retry_Limit 2
Input について
上では user defined な container log を集約する Input の config のみ抜粋していますが、この他には kube-system
配下の container log を集約するものだったり、istio-system
, knative
, gvisor
系のものだったりいろいろあります。
これらは tail プラグインが使われており[6]、 node host の /var/log
がそのまま mount されていて、その中にある log ファイルを tail する形になっています。
もう少し kube_containers
の Input について詳しく見てみると
- Path, Exclude_Path
- user defined な log file のみに絞っている
- Mem_Buf_Limit: 5MB
- これは tail target 全体の合計(つまり node 内に展開された全 user defined container の全ての log file)に対するバッファメモリの許容最大値
- この上限に達すると Output Plugin へ流すのをやめ、pause する
- バッファが flush されていき、メモリに余裕ができると resume していく
- Buffer_Max_Size: 1MB
- tail target の file ごとに決められたバッファメモリの許容最大値
- デフォルトの挙動だとこの値を超えたファイルは opt out され、再度 tail されることは無いが、
Skip_Long_Lines
がOn
になっている場合はその行だけ skip するだけで次の行からは resume される - この挙動は以前の GKE に入っていた Fluentd とは挙動が異なる
- 以前はいい感じに truncate されて、送れるサイズだけはなんとか送ってくれていた
- 「1行が 1MB を超えるようなログなんて吐かないやろ…吐かないよね?」という GKE からのメッセージと受け取っておく
- DB: /var/run/google-fluentbit/pos-files/flb_kube.db
- tail plugin では
DB
ディレクティブで設定をすると、渡した file を使って SQLite3 で管理してくれる -
id
,name
,offset
,inode
,created
といったものが一つの table にまとめられており、 file ごとに insert される - inode が覚えられているので rotate されても大丈夫
- tail plugin では
- storage.type は設定せず
- storage.type の設定がされていると buffering 戦略として memory 以外も使用することができる
- 今回は特に filesystem への backup は設定されていない
- output plugin が詰まるなどした場合は buffer memory の上限に達するはずで、そうすると service は pause するが、次回は db の offset から読み出し再開するので backup 自体はされてなくても、この設定起因では欠損しないはず
- Refresh_Interval: 5
- 5ns ごとに refresh
Output について
Output は Input と違い、単一の http のみが設定されています。
宛先を見ると localhost:2021
となっており、これは fluentbit
の sidecar である fluentbit-gke
が受けていることがわかります。
fluentbit-gke:
Image: gke.gcr.io/fluent-bit-gke-exporter:v0.14.2-gke.0
Port: 2021/TCP
Host Port: 2021/TCP
ただ、ここまではわかるのですがこの fluent-bit-gke-exporter
についての情報は特に見当たらなく、おそらく GCP 側で管理された image であろうというところまでしかわかりません。
この sidecar が Cloud Logging へと送信してくれているはずです[7]。
おわりに
一通り追えるところまでは追えたかなと思ったのでひとまず満足です。
自分の出力したいログの種類や要件として container の STDOUT に出せば十分なのか、あるいは自身で他の仕組みを構築しないと見合わないのか、ある程度判断できるようになった気がします。
あと、本当は Prometheus をつかった監視とか、それ用のダッシュボードの共有とか、自前で Fluent Bit を GKE に展開するチュートリアルの紹介とかをしてもいいかな~と思ったんですが、長くなりすぎるのでやめました。
こういった記事を書くのは久々だったんですが、これからもちゃんと何か書いていこうと思います。
あとこれを読んでいるみなさんはきっと Ubie の SRE にも興味があると思うので Twitter で DM くださいね。
お待ちしております。
-
ちなみに、v1.3.11 は Mar 19, 2020 にリリースされていて、最新は v1.7.4 の Apr 19, 2021 なので次の GKE バージョンではアップグレードされてたりするのかも ↩︎
-
機能的な差異は大きく無いですが、公式では「Aggregation 機能について Fluent Bit では提供していないので、ほしい場合は Fluentd を使ってね」、みたいなことが書いてあります ↩︎
-
主観 ↩︎
-
本当は TOML じゃないんだけど見やすさのためコードブロックは toml を指定した ↩︎
-
ところで v1.0.6
はどこから来たのか。わからないけど GKE 側で管理されてるバージョニングだと信じたい…まさか Fluent Bit のバージョン v1.0.6 (March 27, 2019) ってことは無いよね?まあ挙動変わってないなら良いんだけど… ↩︎ -
ちなみに log file から直接収集していないものもあり、それらは tail ではなく systemd plugin が使われている ↩︎
-
ちなみに Fluent Bit の Output Plugin にも stackdriver という Cloud Logging へ送るためのものがあります。こちらを使わない理由としてはこの機能単体のマシンリソース制御を行える、といったところでしょうか。 ↩︎
Discussion