GKE はどのようにログを収集しているのか

10 min read読了の目安(約9300字

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>

これを見ると色々わかることがあります。

  • コンテナは fluentbitfluentbit-gke の2つ
  • config-volume という名前でマウントされている ConfigMap がいかにも設定ファイルっぽい
    • name も fluentbit-gke-config-v1.0.6 だしね[5]
  • それぞれ HTTP を受け付けている
    • PORT は 2020 と 2021

早速 config-volume の中身を見てみると、確かに正解で、こいつに Fluent Bit の設定がしっかり書かれています。
中身は fluent-bit.confparsers.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_LinesOn になっている場合はその行だけ 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 されても大丈夫
  • 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 に出せば十分なのか、あるいは自身で他の仕組みを構築しないと見合わないのか、ある程度判断できるようになった気がします。

あと、本当は Prometheu をつかった監視とか、それ用のダッシュボードの共有とか、自前で Fluent Bit を GKE に展開するチュートリアルの紹介とかをしてもいいかな~と思ったんですが、長くなりすぎるのでやめました。

こういった記事を書くに久々だったんですが、これからもちゃんと何か書いていこうと思います。
あとこれを読んでるみなさんはきっと Ubie の SRE にも興味があると思うので Twitter で DM くださいね。
お待ちしております。

脚注
  1. ちなみに、v1.3.11 は Mar 19, 2020 にリリースされていて、最新は v1.7.4 の Apr 19, 2021 なので次の GKE バージョンではアップグレードされてたりするのかも ↩︎

  2. 機能的な差異は大きく無いですが、公式では「Aggregation 機能について Fluent Bit では提供していないので、ほしい場合は Fluentd を使ってね」、みたいなことが書いてあります ↩︎

  3. 主観 ↩︎

  4. 本当は TOML じゃないんだけど見やすさのためコードブロックは toml を指定した ↩︎

  5. ところで v1.0.6 はどこから来たのか。わからないけど GKE 側で管理されてるバージョニングだと信じたい…まさか Fluent Bit のバージョン v1.0.6 (March 27, 2019) ってことは無いよね?まあ挙動変わってないなら良いんだけど… ↩︎

  6. ちなみに log file から直接収集していないものもあり、それらは tail ではなく systemd plugin が使われている ↩︎

  7. ちなみに Fluent Bit の Output Plugin にも stackdriver という Cloud Logging へ送るためのものがあります。こちらを使わない理由としてはこの機能単体のマシンリソース制御を行える、といったところでしょうか。 ↩︎