Fluentd を利用してKubernetes コンテナログに Pod、Component、Namespace、Container 情報をRecordで追加

-----

## Kubernetesログ活用

こんにちは！Kubernetes 環境でログ管理は常に重要です。ここでは Fluentd を利用して Kubernetes コンテナログに **Pod、Component、Namespace、Container 情報を自動で追加**し、ログをより簡単に探し、分析できるようにする主要な設定をご紹介します。

-----

概要：K8sコンテナログについて
Kubernetes 環境では、各コンテナの標準出力 (stdout) と標準エラー出力 (stderr) がノードのファイルシステムにログとして保存されます。これらのログファイルは通常、/var/log/containers/ パス以下に以下の命名規則で生成されます。

<Pod名>_<Namespace名>_<コンテナ名>-<コンテナID>.log

例えば、`counter-deployment-6c79d9f4c-22kqc_default_count-608e17a890d8c4114e56d4f6c3b6cb6cd4535899b704ef9454ea84fa726185be.log` というファイル名の場合、次のように読み解けます。

Pod名: `counter-deployment-6c79d9f4c-22kqc`
Namespace名: `default`
コンテナ名: `count`
コンテナID: `608e17a890d8c4114e56d4f6c3b6cb6cd4535899b704ef9454ea84fa726185be` (Docker/containerd などで付与される一意のID)
このファイルには、`counter-deployment-6c79d9f4c-22kqc` Pod の `default` ネームスペースにある `count` コンテナが出力した全てのログが時系列順に記録されています。しかし、ファイル名だけでは情報が限定的であり、複数のコンテナやPodのログが混在する大規模環境では、必要な情報を効率的に抽出するのが難しいという課題があります。

ここで紹介する Fluentd の設定は、このファイル名から重要なメタデータを抽出し、個々のログエントリーに付加することで、ログの可視性と検索性を飛躍的に向上させます。


-----

### なぜこの設定が必要なのか？

既存のログは単純です。しかし、この設定を適用すると、ログに次のような**メタデータ**が付加されます。

  * **`pod`**: どの Pod から発生したログなのか。
  * **`component`**: Pod 内のどのサービスやコンポーネントなのか。
  * **`namespace`**: どのネームスペースの Pod なのか。
  * **`container`**: どのコンテナから発生したログなのか。

これにより、**問題発生時に原因を素早く特定し、特定のサービスのログだけを手軽にフィルタリング**して見ることができるため、運用効率が大幅に向上します。

-----

### 主要な設定ポイント

私が使用した設定は、`ClusterRole`、`ServiceAccount`、`ConfigMap` (Fluentd 設定)、`Pod` (Fluentd およびログクリーナー) で構成されています。各部分の**核となるポイント**だけを説明します。

#### 1\. 権限付与 (RBAC)

Fluentd が Pod、Namespace、Node 情報を読み取るためには**読み取り権限**が必要です。以下の `ClusterRole` と `ClusterRoleBinding` で `fluentd-serviceaccount` に必要な権限を付与します。

```yaml
# ClusterRole: pods, namespaces, nodes の参照権限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluentd-cluster-role
rules:
- apiGroups: [""]
  resources: ["pods", "namespaces", "nodes", "nodes/proxy"]
  verbs: ["get", "list", "watch"]
---
# ClusterRoleBinding: ServiceAccount に ClusterRole を連携
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluentd-cluster-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: fluentd-cluster-role
subjects:
- kind: ServiceAccount
  name: fluentd-serviceaccount
  namespace: kube-system # Fluentd の ServiceAccount が配置されるネームスペース
```

#### 2\. Fluentd 設定 (ConfigMap)

ここが最も重要です。`fluent.conf` ファイルでは、`/var/log/containers/*.log` パスの Kubernetes ログを読み込んだ後、**`record_transformer` フィルター**を使用してログファイルパス (`log_source_path`) から Pod、Component、Namespace、Container 情報を**正規表現で抽出し**、ログレコードに追加します。

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
  namespace: kube-system
data:
  fluent.conf: |
    <source>
      @type tail
      path /var/log/containers/*.log # コンテナログのパス
      tag kubernetes
      <parse>
        @type none # 初期パースなし
      </parse>
      path_key log_source_path # ログファイルのパスをフィールドとして追加
    </source>

    <filter kubernetes.**>
      @type record_transformer
      enable_ruby true
      <record>
        # 正規表現で pod, component, namespace, container 情報を抽出
        pod ${record["log_source_path"].match(/\/var\/log\/containers\/(.*?)(?:_.*)?\.log/)&.[](1)}
        component ${record["log_source_path"].match(/\/var\/log\/containers\/(.*?)(?:_.*)?\.log/)&.[](1).match(/^(.*?)(?:-[0-9a-f]{5,10}-[0-9a-z]{5})?$/)[1]}
        namespace ${record["log_source_path"].match(/\/var\/log\/containers\/.*?_(.*?)_.*?\.log/)[1]}
        container ${record["log_source_path"].match(/_[^_]+_([^_]+?)(?:-|_|$)/)[1]}
      </record>
    </filter>

    <match **>
      @type file
      path /fluentd/log/${tag}.log # 抽出されたタグを利用してファイル名を生成 (例: /fluentd/log/kubernetes.pod-name.container-name.log)
      append true
      <buffer tag, time>
        timekey 60
        timekey_wait 15s
        flush_mode immediate
      </buffer>
    </match>
```

これにより、ログが保存される際に `pod-name.container-name.log` のように**ファイル名自体に必要な情報**が含まれるため、検索が容易になります。

#### 3\. Fluentd Pod デプロイとログクリーナー

最後に、Fluentd コンテナを実行する `Pod` と、ログを定期的に整理する `log-cleaner` コンテナです。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: fluentd-pod
  namespace: kube-system
spec:
  serviceAccountName: fluentd-serviceaccount
  tolerations: # マスターノードでも実行を許可
  - key: node-role.kubernetes.io/master
    operator: Exists
    effect: NoSchedule
  containers:
  - name: fluentd
    securityContext:
      privileged: true # ホストログへのアクセス権限 (セキュリティに注意！)
      runAsUser: 0
    image: fluent/fluentd
    volumeMounts:
    - name: varlog
      mountPath: /var/log # ホストの /var/log をマウント
    - name: fluentd-config-volume
      mountPath: /fluentd/etc # ConfigMap をマウント
    - name: fluentd-log-volume
      mountPath: /fluentd/log # Fluentd ログの保存ディレクトリ
  - name: log-cleaner # 5分以上経過したログファイルを自動削除
    image: busybox
    command: ["/bin/sh", "-c", "while true; do find /fluentd/log -name \"*.log\" -type f -mmin +5 -print -exec rm {} \\; && sleep 60; done"]
    volumeMounts:
      - name: fluentd-log-volume
        mountPath: /fluentd/log
  volumes:
  - name: varlog
    hostPath:
      path: /var/log
  - name: fluentd-config-volume
    configMap:
      name: fluentd-config
  - name: fluentd-log-volume
    emptyDir: {} # Fluentd ログ用の一時ディレクトリ
```

`privileged: true` 設定はホストログへのアクセスに必要なものですが、**セキュリティ上の注意**が必要です。`log-cleaner` は `emptyDir` に蓄積されるログファイルが過度に大きくなるのを防ぐため、**定期的に古いファイルを削除**します。

-----
この設定で生成されるログの例
Fluentd が処理して生成するログは、以下のような形式になります。

```JSON

{
  "message":"2025-06-23T09:40:36.053462675Z stdout F 22658: Mon Jun 23 09:40:36 UTC 2025",
  "log_source_path":"/var/log/containers/counter-deployment-6c79d9f4c-22kqc_default_count-608e17a890d8c4114e56d4f6c3b6cb6cd4535899b704ef9454ea84fa726185be.log",
  "pod":"counter-deployment-6c79d9f4c-22kqc",
  "component":"counter-deployment",
  "namespace":"default",
  "container":"count"
}
```
この例では、元の message に加えて、log_source_path から抽出された pod、component、namespace、container フィールドが追加されているのが確認できます。