本番でWebアプリケーションを運用していると,様々なログを取り扱う必要がある.これはKubernetesでも同じで,Kubernetes上で動かしているコンテナのログも,何かしらの方法で集約してどこかに蓄えておいて,分析とか検索とかをしたくなる.
こういう用途でまず思いつくのがfluentdで,fluentdはKubernetes用にDaemonSetを用意してくれている.
こいつを使えば,コンテナの標準出力に出てきたログは,すべてfluentdで拾えることになる.
では,標準出力以外の,例えばファイルに吐き出すようなログはどうなるだろう? こういったログを集めるためには,アプリケーションのコンテナのサイドカーとしてfluentdを動かし,ログファイルをVolumeMountすることでサイドカー側のfluentdにログファイルを読ませる,というようなことをする必要がある.
でもこれ, アプリケーションが増えてきたときに,毎回すべてのDeploymentにSidecarの定義書くのめんどくさくない?
というわけで作りました.
こいつをKubernetesクラスタに入れておくと,(annotationsで指定した)Podを立ち上げたときに,勝手にfluentdのSidecarを突っ込んでくれる.
どうなってんの?
KubernetesのAdmission 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なの?
差し込まれるのはこれ.
そして中身の設定はこんな感じになっている.
<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_DIR
や AGGREGATOR_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をレスポンスとして返しているだけ.
今回は
を使って作ったのでGoなのだが,TLSで通信して,指定のレスポンス構造を守っていれば,別にサーバ自体はRubyでもPHPでも構わない.
yamlの改変をするだけなので,特にRBACによる認可も必要ない(別のリソースを参照する場合はもちろん必要だよ).
これはすごい,マジでなんでもできるぞ!!
まとめ
普通に会社のKubernetesクラスタに入れたのだけれど,特になんの問題もなく,そしてなんのメンテも必要とせずに動いている.というか意識せずに勝手にInjectされるので,本当に楽になった. ログを吐き出したくなったら,単にアプリケーション側のロガーの設定を入れるだけで済むからね.
現状はmakeした後にkustomizeなのだけれど,証明書の扱いさえもうちょっと楽にできれば,もっとインストール方法が簡単にできるはず…….やっぱりこういうのはHelmの方が楽だと思うので. なんかいい方法を知ってる方は教えてください.
あと環境変数とかannotationsの追加は割りと簡単にできるので,ほしいものがあったらIssueにしてくれると見ます.