2026年9月21日

2026年9月21日

KubernetesでEFK(Elasticsearch・Fluentd・Kibana)のログ基盤を構築する手順

Kubernetes のログ集約は、各ノードで Fluentd を DaemonSet として動かし、/var/log/containers/ のコンテナログを読み取って Elasticsearch に送り、Kibana で検索する構成が基本です。この記事では、収集側(Fluentd)の権限とパーサー設定、送信先(Elasticsearch)の認証と保存期間、Kibana での確認までを順に組み立て、つまずきやすい箇所の確認方法も紹介します。

EFK構成の全体像と、先に決めておくこと

EFK は役割の異なる3つの部品でできています。

  • Fluentd: 各ノードで1つずつ動き、コンテナのログファイルを読み、Pod名や名前空間などのメタデータを付けて送る
  • Elasticsearch: ログを索引(インデックス)として保存し、検索できるようにする
  • Kibana: Elasticsearch の中身をブラウザで検索・可視化する

構築を始める前に、次の3点を決めておくと後戻りが減ります。

  1. Elasticsearch をどこで動かすか。クラスタ内で動かすなら、Elastic 社の Operator である ECK(Elastic Cloud on Kubernetes)を使うのが現在の主流です。以前よく使われた Elastic 公式の Helm チャートは、ECK への移行が案内されています。クラスタ外のマネージドサービスに送る方法もあり、運用負荷を下げたい場合はこちらも候補です。
  2. ライセンスとフォーク。Elasticsearch と Kibana は 2021 年にライセンスが変更され、その後 AGPL も選べるようになりました。フォークである OpenSearch / OpenSearch Dashboards を使う構成もあります。社内規程に関わるため、採用前に各プロジェクトの最新のライセンス表記を確認してください(2026年10月現在)。ライセンスの条件は今後も変わることがあります。
  3. 保存期間と容量。ログは想像以上に増えます。「何日分を検索できればよいか」を先に決め、後述のライフサイクル管理(ILM)で古い索引を消す前提で容量を見積もります。

なお、収集側を Fluentd ではなく軽量な Fluent Bit にする構成(EFK の F を Fluent Bit にしたもの)も広く使われています。メモリ使用量を抑えたい場合は検討の価値があります。

手順1:Elasticsearch と Kibana を用意する

ECK を使う場合は、公式ドキュメントの手順で Operator を導入したあと、Elasticsearch と Kibana のカスタムリソースを作成します。最小構成の例です(バージョン番号は導入時点の対応版に置き換えてください)。

apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: logs
  namespace: logging
spec:
  version: 8.15.0   # 例。ECK が対応する版を確認して指定する
  nodeSets:
  - name: default
    count: 3
    config:
      node.store.allow_mmap: false   # vm.max_map_count を変更できない環境向け
    volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 50Gi
---
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: logs
  namespace: logging
spec:
  version: 8.15.0
  count: 1
  elasticsearchRef:
    name: logs

つまずきやすいのは次の2点です。

  • vm.max_map_count。Elasticsearch は mmap を多用するため、ノードのカーネル設定 vm.max_map_count を十分な値(公式ドキュメントでは 262144 以上)にする必要があります。ノードの設定を変えられないマネージド環境では、上の例のように node.store.allow_mmap: false で回避できますが、性能面の影響があるため本番では公式の推奨を確認してください。
  • 認証とTLS。Elasticsearch 8 系は既定でセキュリティ機能(認証と TLS)が有効です。ECK では logs-es-elastic-user という Secret に管理者パスワードが、logs-es-http-certs-public という Secret に CA 証明書が作られます。Fluentd からの接続にはこれらを使いますが、管理者ユーザーをそのまま使うのではなく、書き込み権限だけを持つ専用ユーザーを作るのが安全です。

起動確認は kubectl get elasticsearch -n logging で HEALTH が green になるか、Pod のログにエラーが出ていないかで行います。

手順2:Fluentd に必要な権限を与える

Fluentd がログに Pod 名やラベルを付けるには、Kubernetes API から Pod と Namespace の情報を読む権限が必要です。この処理は fluent-plugin-kubernetes_metadata_filter が担い、読み取り専用の ClusterRole で足ります。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluentd
  namespace: logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd
rules:
- apiGroups: [""]
  resources: ["pods", "namespaces"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluentd
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: fluentd
subjects:
- kind: ServiceAccount
  name: fluentd
  namespace: logging

権限が足りないと、ログ自体は届くのにメタデータが空になったり、Fluentd のログに 403 エラーが出たりします。

手順3:ログの形式に合ったパーサーを設定する

ここが最もつまずきやすい部分です。現在の多くのクラスタはコンテナランタイムに containerd や CRI-O を使っており、ログファイルは Docker 時代の JSON 形式ではなく、「時刻 ストリーム フラグ 本文」が並ぶ CRI 形式で書かれます。JSON 用のパーサーのままだと、パースエラーが大量に出てログが落ちます。

CRI 形式は fluent-plugin-parser-cri の cri パーサーで読めます。設定例です。

<source>
  @type tail
  path /var/log/containers/*.log
  pos_file /var/log/fluentd-containers.log.pos
  tag kubernetes.*
  read_from_head true
  <parse>
    @type cri
  </parse>
</source>

<filter kubernetes.**>
  @type kubernetes_metadata
</filter>

<match kubernetes.**>
  @type elasticsearch
  host logs-es-http.logging.svc
  port 9200
  scheme https
  ssl_verify true
  ca_file /etc/es-certs/ca.crt
  user fluentd_writer
  password "#{ENV['ES_PASSWORD']}"
  logstash_format true
  logstash_prefix k8s
  <buffer>
    @type file
    path /var/log/fluentd-buffers/k8s.buffer
    flush_interval 10s
    chunk_limit_size 8M
    retry_max_interval 30
    overflow_action block
  </buffer>
</match>

pos_file は「どこまで読んだか」を記録するファイルで、Fluentd が再起動しても同じログを二重に送らないために使います。バッファを file にしておくと、Elasticsearch が一時的に止まってもノード上に溜めて後から再送できます。パスワードは設定ファイルに直書きせず、Secret から環境変数で渡します。

設定を一から書く代わりに、fluent/fluentd-kubernetes-daemonset の Elasticsearch 向けイメージを使い、環境変数で接続先を指定する方法もあります。この場合も、CRI 形式向けのパーサー指定が必要かどうかをイメージの README で確認してください。

手順4:DaemonSet としてデプロイする

各ノードのログディレクトリをマウントして、DaemonSet で全ノードに配置します。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: logging
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      serviceAccountName: fluentd
      tolerations:
      - operator: Exists          # コントロールプレーン等のノードでも動かす場合
      containers:
      - name: fluentd
        image: your-registry/fluentd-with-es:your-tag
        env:
        - name: ES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: fluentd-es-credentials
              key: password
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            memory: 512Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: config
          mountPath: /fluentd/etc
        - name: es-certs
          mountPath: /etc/es-certs
          readOnly: true
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: config
        configMap:
          name: fluentd-config
      - name: es-certs
        secret:
          secretName: logs-es-http-certs-public

/var/log/containers/ の各ファイルは /var/log/pods/ 配下へのシンボリックリンクなので、/var/log ごとマウントしておけばリンク先も読めます。メモリの limit を低くしすぎると、ログが急増したときに OOMKilled で再起動を繰り返すため、kubectl top pod -n logging で実際の使用量を見ながら調整します。

手順5:Kibanaで確認し、保存期間を設定する

Kibana には ECK が作る Service 経由で接続します。動作確認だけなら kubectl port-forward -n logging svc/logs-kb-http 5601 でローカルから開けます。ログインしたら、k8s-* に一致するデータビュー(旧バージョンでは「インデックスパターン」と呼ばれていました)を作成し、Discover 画面で kubernetes.namespace_name や kubernetes.pod_name で絞り込めれば成功です。画面の名称や配置はバージョンで変わるため、見当たらない場合は管理メニューの中を探してください。

保存期間は ILM(インデックスライフサイクル管理)のポリシーで「一定日数を過ぎた索引を削除する」と設定します。ILM を設定しないまま運用すると、ディスクが満杯になった時点で Elasticsearch が書き込みを止め、ログが失われます。

ログが届かないときの確認順

  1. Fluentd の Pod ログを見る。kubectl logs -n logging ds/fluentd でパースエラー(pattern not matched など)が出ていれば、手順3のパーサー設定を見直します。
  2. 接続エラーを確認する。証明書の検証エラーや 401 が出ていれば、CA 証明書のマウントとユーザー・パスワードを確認します。
  3. Elasticsearch 側の状態を確認する。クラスタが red/yellow のまま、あるいはディスク使用率が高く書き込み禁止になっていないかを確認します。
  4. メタデータが空なら RBAC。手順2の ClusterRole と ServiceAccount の紐付けを確認します。

アプリケーションは標準出力にログを出すのが前提です。コンテナ内のファイルにだけ書いているアプリのログは、この構成では拾えません。その場合はサイドカーで標準出力に流すなどの工夫が必要です。

まとめ

  • Fluentd を DaemonSet で動かし、/var/log をマウントしてコンテナログを読む
  • containerd / CRI-O 環境では CRI 形式のパーサーを使う(JSON 用のままだとログが落ちる)
  • Elasticsearch 8 系は認証と TLS が既定で有効。書き込み専用ユーザーと Secret 経由のパスワードで接続する
  • ILM で保存期間を決め、ディスク溢れによる書き込み停止を防ぐ
  • 本番に入れる前にステージング用クラスタで、ログ量とリソース使用量を測っておく

お気軽にご相談ください

お見積りへ お問い合わせへ
▲