PodにfluentdのSidecarを自動でinjectする

本番でWebアプリケーションを運用していると,様々なログを取り扱う必要がある.これはKubernetesでも同じで,Kubernetes上で動かしているコンテナのログも,何かしらの方法で集約してどこかに蓄えておいて,分析とか検索とかをしたくなる.

こういう用途でまず思いつくのがfluentdで,fluentdはKubernetes用にDaemonSetを用意してくれている.

github.com

こいつを使えば,コンテナの標準出力に出てきたログは,すべてfluentdで拾えることになる.

では,標準出力以外の,例えばファイルに吐き出すようなログはどうなるだろう? こういったログを集めるためには,アプリケーションのコンテナのサイドカーとしてfluentdを動かし,ログファイルをVolumeMountすることでサイドカー側のfluentdにログファイルを読ませる,というようなことをする必要がある.

blog.mosuke.tech

でもこれ, アプリケーションが増えてきたときに,毎回すべてのDeploymentにSidecarの定義書くのめんどくさくない?

というわけで作りました.

github.com

こいつをKubernetesクラスタに入れておくと,(annotationsで指定した)Podを立ち上げたときに,勝手にfluentdのSidecarを突っ込んでくれる.

どうなってんの?

KubernetesAdmission Webhookという仕組みを使っている.これによって,Podの起動にフックして fluentd-sidecar-injector が動くようになっている.

fluentd-sidecar-injector 内では,起動のリクエストがあったPodの定義を受け取っており,こいつを勝手に書き換えて中にfluentdのSidecarを突っ込んでいる.

使い方

Injectorの制御方法

といってもすべてのPodに対してfluentdを差し込んでいるわけではない.

annotationsに fluentd-sidecar-injector.h3poteto.dev/injection: "enabled" の指定があるPodでのみ発動するようになっている.なので,Sidecarを差し込んでほしい場合は,

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-test
  labels:
    app: nginx-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-test
  template:
    metadata:
      annotations:
        fluentd-sidecar-injector.h3poteto.dev/injection: "enabled"
        fluentd-sidecar-injector.h3poteto.dev/application-log-dir: "/var/log/nginx"
      labels:
        app: nginx-test
    spec:
      containers:
        - name: nginx
          image: nginx:latest

こういうような定義を書く.すると

$ kubectl describe pod nginx-test-6cbf4485f8-kq8ws
Name:           nginx-test-6cbf4485f8-kq8ws
Namespace:      default
Containers:
  nginx:
    Container ID:   docker://ce74393381205786668a1fe2a4bc83ba058d380714b8a7ddca23966c8c7f0eb0
    Image:          nginx:latest
    Image ID:       docker-pullable://nginx@sha256:ad5552c786f128e389a0263104ae39f3d3c7895579d45ae716f528185b36bc6f
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Fri, 14 Feb 2020 13:49:21 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/log/nginx from fluentd-sidecar-injector-logs (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-8rcns (ro)
  fluentd-sidecar:
    Container ID:   docker://49503c3836fa5ebc40c55db3717f16f21fbdbfaae8859a8ed8a366d04a2b6d9b
    Image:          h3poteto/fluentd-forward:latest
    Image ID:       docker-pullable://h3poteto/fluentd-forward@sha256:5d93af333ad9fefbfcb8013d20834fd89c2bbd3fe8b9b9bfa620ded29d7b3205
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Fri, 14 Feb 2020 13:49:23 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      memory:  1000Mi
    Requests:
      cpu:     100m
      memory:  200Mi
    Environment:
      AGGREGATOR_HOST:      127.0.0.1
      APPLICATION_LOG_DIR:  /var/log/nginx
      TAG_PREFIX:           prod
      TIME_KEY:             time
    Mounts:
      /var/log/nginx from fluentd-sidecar-injector-logs (rw)

こんな感じで, /var/log/nginx がVolumeMountされ,Sidecarのfluentdと共有される. いろいろannotationsで設定を変えられるようにはしているが,それについては後述.

差し込まれるfluentdのDocker Imageについて

差し込まれるのはわかったとして,それどんなfluentdの設定が入ったSidecarなの?

差し込まれるのはこれ.

hub.docker.com

github.com

そして中身の設定はこんな感じになっている.

<source>
  @type tail
  path "#{ENV['APPLICATION_LOG_DIR']}/*.log"
  pos_file /var/tmp/application.log.pos
  format json
  time_key "#{ENV['TIME_KEY']}"
  time_format "#{ENV['TIME_FORMAT'] || '%Y-%m-%dT%H:%M:%S%z'}"
  keep_time_key true
  tag "#{ENV['TAG_PREFIX']}.*"
</source>

<match **>
  @type forward
  send_timeout "#{ENV['SEND_TIMEOUT'] || '60s'}"
  recover_wait "#{ENV['RECOVER_WAIT'] || '10s'}"
  hard_timeout "#{ENV['HARD_TIMEOUT'] || '60s'}"

  <server>
    name aggregator
    host "#{ENV['AGGREGATOR_HOST']}"
    port "#{ENV['AGGREGATOR_PORT'] || '24224'}"
  </server>

  <secondary>
    @type file
    path /var/log/fluent/forward-failed
  </secondary>
</match>

これはつまり,Sidecarとして差し込まれたfluentdは, APPLICATION_LOG_DIR 配下に存在する *.log 形式のファイルを読み込んでくれるということ.そして,読み込んだものを AGGREGATOR_HOST に送信している.

即ち,この構成では,各Sidecarでログファイルを読み込み,それを集約する別のfluentdサーバが必要になるということである.

この集約用fluentdサーバは自分で建ててね.それを AGGREGATOR_HOST に指定してくれさえすれば,あとは自動で全部送ってくれる.

各種の設定変数

上記で指定されていた,APPLICATION_LOG_DIRAGGREGATOR_HOST はどうやって指定したらいいのか?

これは全部,Pod起動時のannotationsでいじれるようになっている.

必須なのが,

  • fluentd-sidecar-injector.h3poteto.dev/aggregator-host これが AGGREGATOR_HOSTマッピングされている
  • fluentd-sidecar-injector.h3poteto.dev/application-log-dir これが APPLICATION_LOG_DIRマッピングされ,さらにVolumeMountの設定にも使われている

の2つ.

その他, TIME_KEY とか TIME_FORMATとかもいじれるようになっているので,詳しくはREADMEを読んでいただけると.

別のDocker Imageに差し替えたい

はい,可能です.

もし自分で独自のfluentd Docker Imageを用意している場合は,fluentd-sidecar-injector.h3poteto.dev/docker-image で,InjectするDocker Imageを差し替えることができる. ただし,Podのannotations -> fluentdの環境変数 へのマッピングは変わらないので,annotationsに定義されていない変数を追加することはいまのところできない.

もし要望があったらIssueを建ててくれたら,なんか考えます.

インストール方法

Helmでインストールできるようにしたかったのだが,現状そこまでできていない. Webhookの痛いところは,TLSでアクセスを受け付ける必要があり,証明書を管理しなければならないというところ.

これがあるためにHelmで一発適用ができない.

まず,makeしてやる.このときに証明書を生成している.

$ git@github.com:h3poteto/fluentd-sidecar-injector.git
$ cd fluentd-sidecar-injector
$ make build NAMESPACE=kube-system

あとはただのkustomize templateになっているので,

$ kubectl apply -k ./install/kustomize

でインストールできる.

Webhookすごい,なんでもできる

このWebhookによるSidecar Injectionというのは,もともとIstioでやっているのを見かけて,同じようなことをやりたいと思っていた. 思っていたら,割りと簡単にできたのでびっくりする.

このWebhookには2種類あり,mutating webhookとvalidating webhookというもの.今回使っているのはmutating webhookで,こいつはWebhookで受け取ったオブジェクトの中身を好きに改変することができる. これが,かなり柔軟に,というかWebhookで得られたオブジェクトであればいかようにも改変可能なので,なんでもできてしまう. しかも,やってることは,ただ受け取ったyamlの中身を書き換えて,新たなyamlをレスポンスとして返しているだけ.

今回は

github.com

を使って作ったのでGoなのだが,TLSで通信して,指定のレスポンス構造を守っていれば,別にサーバ自体はRubyでもPHPでも構わない.

yamlの改変をするだけなので,特にRBACによる認可も必要ない(別のリソースを参照する場合はもちろん必要だよ).

これはすごい,マジでなんでもできるぞ!!

まとめ

普通に会社のKubernetesクラスタに入れたのだけれど,特になんの問題もなく,そしてなんのメンテも必要とせずに動いている.というか意識せずに勝手にInjectされるので,本当に楽になった. ログを吐き出したくなったら,単にアプリケーション側のロガーの設定を入れるだけで済むからね.

現状はmakeした後にkustomizeなのだけれど,証明書の扱いさえもうちょっと楽にできれば,もっとインストール方法が簡単にできるはず…….やっぱりこういうのはHelmの方が楽だと思うので. なんかいい方法を知ってる方は教えてください.

あと環境変数とかannotationsの追加は割りと簡単にできるので,ほしいものがあったらIssueにしてくれると見ます.