📂

Filebeat による k8s クラスタ Logging

2023/10/04に公開

概要

Kubernetes に限った話ではないですが、 Observability の 3 本柱として以下の要素があります。

  • Logging
  • Metrics
  • Tracing

今回はこの中で logging を実装し、各クラスタの pod や node が出力するログを 1 箇所で閲覧できるようにします。

Kubernetes における Logging では pod レベル、node レベルのログをそれぞれ収集したり、ログの内容を適切に構造化することで可動性を向上化させる等々気にすべき項目はいろいろありますが、細かいことを抜きにしてとりあえずログを収集・可視化することを目的にします。
これを実現するための方法としては Fluentbit をインストールしてクラスタ内ログを収集する例が多いですが、今回は Elastic のログ収集エージェント Filebeat を使用して logging を実装します。

https://www.elastic.co/jp/beats/filebeat

収集したログを可視化するための基盤としては、Filebeat とよく組み合わせて使われる Logstash, Elasticsearch, Kibana のいわゆる ELK スタックを使用します。
k8s クラスタのログを filebeat で収集し、別ノードに構築した ELK スタックでログの長期保存と可視化を行うというような構成にします。

cannot load image
概要図

このような中央集権型の Logging 構成とすることで、ログ集積・可視化を Elasticsearch の一箇所で行うことができます。また、管理対象の k8s クラスタが増えた場合も Filebeat をインストールするだけでクラスタからのログ収集が実現できるため Availability に優れています。

Filebeat

Filebeat は helm chart が用意されているので容易にインストールできますが、そのままでは動かないので設定をカスタマイズする必要があります。

https://artifacthub.io/packages/helm/elastic/filebeat

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 以下のフィールドについて設定項目を追加・変更していきます。

values.yaml
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 値を変形、加工など様々な処理を実施できます。

https://www.elastic.co/jp/logstash

filebeat で収集したログは既にクラスタ名や pod 名が field に設定されているのでそのまま elasticsearch に入れてもある程度は有効ですが、logstash を間に挟むことでメッセージ部分のタイムスタンプやログレベルを抽出して可読性を向上させたり、不要なフィールドを削除してログサイズを削減することができます。

構築

logstash は クラスタ内とは別のノードで docker を使って構築します。

docker-compose.yml
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 ディレクトリにまとめておき、コンテナ内にマウントすることで適用します。
今回の検証で使用したファイルは以下に置いてます。

https://github.com/git-ogawa/zenn_resource/tree/main/articles/k8s_filebeat/logstash

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 の最小構成で作成します。

docker-compose.yml
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 のクラスタ名を指定していた部分を置き換えます。

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