Filebeat による k8s クラスタ Logging
概要
Kubernetes に限った話ではないですが、 Observability の 3 本柱として以下の要素があります。
- Logging
- Metrics
- Tracing
今回はこの中で logging を実装し、各クラスタの pod や node が出力するログを 1 箇所で閲覧できるようにします。
Kubernetes における Logging では pod レベル、node レベルのログをそれぞれ収集したり、ログの内容を適切に構造化することで可動性を向上化させる等々気にすべき項目はいろいろありますが、細かいことを抜きにしてとりあえずログを収集・可視化することを目的にします。
これを実現するための方法としては Fluentbit をインストールしてクラスタ内ログを収集する例が多いですが、今回は Elastic のログ収集エージェント Filebeat を使用して logging を実装します。
収集したログを可視化するための基盤としては、Filebeat とよく組み合わせて使われる Logstash, Elasticsearch, Kibana のいわゆる ELK スタックを使用します。
k8s クラスタのログを filebeat で収集し、別ノードに構築した ELK スタックでログの長期保存と可視化を行うというような構成にします。
概要図
このような中央集権型の Logging 構成とすることで、ログ集積・可視化を Elasticsearch の一箇所で行うことができます。また、管理対象の k8s クラスタが増えた場合も Filebeat をインストールするだけでクラスタからのログ収集が実現できるため Availability に優れています。
Filebeat
Filebeat は helm chart が用意されているので容易にインストールできますが、そのままでは動かないので設定をカスタマイズする必要があります。
chart の設定ファイルを values.yaml
に書き出して設定項目をカスタマイズしていきます。
$ helm repo add elastic https://helm.elastic.co
$ helm show values elastic/filebeat > values yaml
chart は次のバージョンを使用しているため、別バージョンでは以下の設定項目が異なる可能性があります。
- chart version : 8.5.1
- app version : 8.5.1
daemonset
書き出した values.yaml の中身を見ると、トップレベルで daemonset と deployments のフィールドが定義されていることがわかります。logging を行う場合は daemonset としてデプロイしてクラスタ内の各 node に pod を配置するため、以下のカスタマイズでは daemonset
以下のフィールドについて設定項目を追加・変更していきます。
daemonset:
...
deployments:
...
extraEnvs のコメントアウト
デフォルトではログ送信先 elasticsearch の認証方法を secret から読み込むように設定されていますが、今回は logstash にログ送信するのでこの設定は不要です。
extraEnvs
内の対応箇所を削除するなりコメントアウトします。
extraEnvs: []
# - name: "ELASTICSEARCH_USERNAME"
# valueFrom:
# secretKeyRef:
# name: elasticsearch-master-credentials
# key: username
# - name: "ELASTICSEARCH_PASSWORD"
# valueFrom:
# secretKeyRef:
# name: elasticsearch-master-credentials
# key: password
elasticsearch の証明情報を secretMounts
でマウントしていますがこの箇所も不要です。
secretMounts:
- name: elasticsearch-master-certs
secretName: elasticsearch-master-certs
path: /usr/share/filebeat/certs/
filebeatConfig
filebeat では filebeat.yml という設定ファイルに送信先の設定などを記述しますが、chart では filebeatConfig.filebeat.yml
のフィールドに設定項目を記述します。ここで設定した内容は configmap として作成され、filebeat pod 内に filebeat.yml としてマウントされます。
デフォルトでは filebeat から直接 elasticsearch へ送信するように設定されていますが、logstash に送信するように output.logstash
を書き換えます。
filebeatConfig:
filebeat.yml: |
- # output.elasticsearch:
- # host: '${NODE_NAME}'
- # hosts: '["https://${ELASTICSEARCH_HOSTS:elasticsearch-master:9200}"]'
- # username: '${ELASTICSEARCH_USERNAME}'
- # password: '${ELASTICSEARCH_PASSWORD}'
- # protocol: https
- # ssl.certificate_authorities: ["/usr/share/filebeat/certs/ca.crt"]
+ output.logstash:
+ host: '${NODE_NAME}'
+ hosts: ["[logstash の ip address]:5044"]
pod ログの収集
pod レベルのログ収集はデフォルトで有効化されています。
pod ログはホストの /var/log/containers/ 以下に出力されるため、filebeat pod では /var/log 以下をコンテナ内にマウントし、container input を使って収集、namespace や pod 名などのメタデータを追加して output に指定されたサーバーに送信します。
デフォルト設定では filebeat.inputs[0].type: container
の部分がこの箇所に対応しており、ここに設定を追加することでログにフィールドを追加するなどカスタマイズを行うことが可能です。
今回は kibana でログを可視化した際、ログがどのクラスタの pod から出力されたものであるかを判別するために cluster 名を表す field を追加します。
processors[].add_fields
に以下のように追加することで、このクラスタから送信されたログにはクラスタ名を表す cluster: k8s-control
のフィールドが追加されます。
filebeat.inputs:
- type: container
paths:
- /var/log/containers/*.log
processors:
- add_kubernetes_metadata:
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: "/var/log/containers/"
+ - add_fields:
+ target: ""
+ fields:
+ cluster: k8s-control
node ログの収集
デフォルトでは node レベルのログは収集されませんが、ホストの /var/log は pod 内にマウントされるため、この配下のログを収集する設定を filebeat.inputs
に追加することで node レベルのログを収集できます。
node レベルのログでは systemd 配下のサービスのログや audit ログなどが収集対象として考えられますが、ここでは簡単のため systemd の kubelet サービスのログのみを収集します。
systemd で管理されるサービスのログは /var/log/syslog に出力されるため、この中から kubelet
を含む行のみを送信対象とするinclude_lines: kubelet
を指定します。
filebeat.yml: |
filebeat.inputs:
+ - type: filestream
+ paths:
+ - /var/log/syslog
+ include_lines:
+ - 'kubelet'
+ tags:
+ - kubelet
+ processors:
+ - add_fields:
+ target: ""
+ fields:
+ cluster: k8s-control
toleration
filebeat pod は DaemonSet で展開されるのでクラスタ内の worker nodes に配置されますが、デフォルトでは control plane には配置されません。control plane にも配置して control plane コンポーネントの pod ログを収集するには tolerations で設定します。
tolerations:
+ - key: node-role.kubernetes.io/control-plane
+ operator: Exists
+ effect: NoSchedule
インストール
values.yaml の設定を書き換えたら helm でインストールできます。
helm install -n filebeat filebeat -f values.yaml elastic/filebeat --create-namespace
まだ送信先の logstash, elasticsearch を構築していないため、この段階でインストールすると filebeat のログ送信がエラーとなります。なので構築後に上記コマンドでインストールします。
Logstash
logstash は filebeat から受信したログデータに対してメッセージを parse して field を追加したり、既存の field 値を変形、加工など様々な処理を実施できます。
filebeat で収集したログは既にクラスタ名や pod 名が field に設定されているのでそのまま elasticsearch に入れてもある程度は有効ですが、logstash を間に挟むことでメッセージ部分のタイムスタンプやログレベルを抽出して可読性を向上させたり、不要なフィールドを削除してログサイズを削減することができます。
構築
logstash は クラスタ内とは別のノードで docker を使って構築します。
services:
logstash:
image: docker.elastic.co/logstash/logstash:8.10.2
container_name: logstash
restart: always
volumes:
- ./pipeline:/usr/share/logstash/pipeline
- ./logstash.yml:/usr/share/logstash/config/logstash.yml
ports:
- 5044:5044
filebeat のログを受信・加工・送信するための設定ファイルは pipeline
ディレクトリにまとめておき、コンテナ内にマウントすることで適用します。
今回の検証で使用したファイルは以下に置いてます。
filebeat からの受信設定
filebeat からログを受信するよう beats input plugin を使用します。
input {
beats {
port => 5044
}
}
ログの parse ・加工
filter を使ってログのメッセージ部分のパースや field の書き換えなどの処理を行います。
この部分の記述はメッセージ部分からどの値を抽出するか、どの field を使用してどの field は捨てるかなどの思想により多様に異なります。
一例として今回使用した filter は上記の github に置いてあります。
Elasticsearch への送信
parse 処理したログは elasticsearch の index に格納します。
node レベルのログでは pod ログ固有の情報 (namespace や pod 名など) が存在しないため、これらを同じ index に格納すると kibana で可視化する際に見づらくなります。
なので、ここでは node レベル、pod レベルのログはそれぞれ node-, pod- の prefix をもつ別の index に格納するように設定を分けます。
output {
if "kubelet" in [tags] {
elasticsearch {
hosts => ["http://elastic.centre.com:9200"]
index => "node-%{+YYYY.MM.dd}"
}
} else {
elasticsearch {
hosts => ["http://elastic.centre.com:9200"]
index => "pod-%{+YYYY.MM.dd}"
}
}
}
Elasticsearch, Kibana
elasticsearch, kibana は docker で構築します。
セキュリティ的には https や認証を設定する必要がありますが、今回はとりあえず認証なし、 http の最小構成で作成します。
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:8.9.2
restart: always
container_name: es01
environment:
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata_01:/usr/share/elasticsearch/data
ports:
- 9200:9200
kibana:
restart: always
image: docker.elastic.co/kibana/kibana:8.9.2
container_name: kibana
environment:
- "ELASTICSEARCH_HOSTS=http://es01:9200"
ports:
- 5601:5601
depends_on:
- es01
volumes:
esdata_01:
name: esdata_01
driver: local
ログの可視化
各コンポーネントを起動し、特に設定の不備などもない場合、クラスタの pod, node ログは elasticsearch に保存されます。
このログはブラウザから kibana にアクセスすることで可視化できます。
pod ログ
pod に関するログでは生のメッセージに加えて以下のメタデータを表示するようにしているため、ログがどのクラスタ、どの pod から出力されたものであるかがひと目でわかるようになっています。
- cluster
- namespace
- pod 名
- container 名
- log level
- message
pod ログを kibana で表示した例
この他に、pod の label や pod IP アドレスなどがメタデータとして付加されており、必要に応じて画面に表示する情報をカスタマイズすることも可能です。
node ログ
node に関するログでは生のメッセージに加えて以下のメタデータを表示するようにしています。
- cluster
- node 名
- service 名
- log level
- message
node ログを kibana で表示した例
filebeat の収集対象を絞っているので上記の例で service は kubelet のみですが、systemd 配下の全ログを収集するように変更した場合でもサービス名毎に表示・フィルタリングできるようになっています。
クラスタの追加
別の k8s クラスタのログを収集する際も、既に filebeat で作ったカスタム chart を使用することで logging を容易に実装できます。
ただ一点、ログのメタデータに追加される kubernetes クラスタ名を変更するため values.yaml のクラスタ名を指定していた部分を置き換えます。
filebeat.inputs:
- type: container
...
- add_fields:
target: ""
fields:
- cluster: k8s-control
+ cluster: k8s-dev
filebeat をインストールして kibana で見ると、新しいクラスタ k8s-dev
のログが elasticsearch に送信されていることがわかります。
まとめ
filebeat helm chart を使用して k8s クラスタの Logging を実装する方法を検証しました。
今回の範囲では比較的少ないカスタマイズでわりと見やすいログの可視化を実現できましたが、logstash や elasticsearch は非常に多機能で設定可能な項目がかなり多いため、要件や環境に合わせて設定を柔軟に変更してやりたいことが実現できるようになっています。
ただその分ドキュメントが膨大だったり求めている情報が中々見つからなかったりとなかなか辛い面もあります。
Discussion