# 安裝 Fluentd on K8s(Kubeadm) use DaemonSet & 串接 Elasticsearch ## 部署 Fluentd ``` $ mkdir -p wulin/fluentd ``` * 建立 fluentd RBAC ``` $ echo 'apiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: kube-system' > wulin/fluentd/rbac.yaml $ kubectl apply -f wulin/fluentd/rbac.yaml ``` * 建立 ConfigMap - `path /var/log/containers/*.log` 設定 fluentd 要收集哪裡的 log,這裡是 fluentd pod 肚子里的目錄,所以要改變目錄要去設定 fluentd DaemonSet 的 hostpath。 ``` $ echo 'apiVersion: v1 kind: ConfigMap metadata: name: fluentd-cm namespace: kube-system data: kubernetes.conf: |- # 要收集的資料來源 <source> @id audit_proxy_buffer_logs @type tail path /var/log/containers/*.log pos_file /var/log/fluentd-containers.pos tag kubernetes.* read_from_head true <parse> @type cri </parse> </source> # 避免 Fluentd 重複收集自己的 log,所以要丟棄 Fluentd 自己的 log <match kubernetes.var.log.containers.**fluentd-ds**.log> @type null </match> # kubernetes_metadata 插件會把 Kubernetes 的 metadata(namespace、pod、container、labels、annotations、pod_ip、pod_id 等)加回到每一筆 record,後面才可以篩選 k8s 的這些資源名稱 <filter kubernetes.**> @type kubernetes_metadata </filter> # 用 rewrite_tag_filter 把 tag 改成 namespace,意思就是後面可以透過指定 namespace 名稱來篩選 log <match kubernetes.**> @type rewrite_tag_filter <rule> key $.kubernetes.namespace_name pattern ^(.+)$ tag $1 </rule> </match> # <match demo> — 指定只收集 demo namespace 的所有 pod log,把 demo namespace 的 logs 拋轉到 Elasticsearch <match demo> @id elasticsearch @type elasticsearch @log_level info include_tag_key true host "#{ENV['FLUENT_ELASTICSEARCH_HOST']}" port "#{ENV['FLUENT_ELASTICSEARCH_PORT']}" scheme "#{ENV['FLUENT_ELASTICSEARCH_SCHEME']}" user "#{ENV['FLUENT_ELASTICSEARCH_USER']}" password "#{ENV['FLUENT_ELASTICSEARCH_PASSWORD']}" logstash_format true logstash_prefix ${tag} ssl_verify false reload_connections false reload_on_failure false request_timeout 30s <buffer> @type file path /var/log/fluentd-buffers/container.buffer flush_mode interval retry_type exponential_backoff flush_thread_count 2 flush_interval 5s retry_forever true retry_max_interval 30 queue_limit_length 8 overflow_action block chunk_limit_size 20M chunk_limit_records 300 retry_max_times 3 </buffer> </match>' > wulin/fluentd/fluentd-cm.yaml $ kubectl apply -f wulin/fluentd/fluentd-cm.yaml ``` * 建立 fluentd DaemonSet,他負責收集 log 以及拋轉到 Elasticsearch ``` $ echo 'apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: kube-system labels: k8s-app: fluentd-logging version: v1 spec: selector: matchLabels: k8s-app: fluentd-logging version: v1 template: metadata: labels: k8s-app: fluentd-logging version: v1 spec: serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/control-plane effect: NoSchedule - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1-debian-elasticsearch env: - name: K8S_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName # 指定 elasticsearch 位置 - name: FLUENT_ELASTICSEARCH_HOST value: "elasticsearch-es-http.elastic.svc.cluster.local" - name: FLUENT_ELASTICSEARCH_PORT value: "9200" - name: FLUENT_ELASTICSEARCH_SCHEME value: "https" # 指定 elasticsearch 帳號及密碼 - name: FLUENT_ELASTICSEARCH_USER value: "elastic" - name: FLUENT_ELASTICSEARCH_PASSWORD value: "5CCh5Nv659yDkk935bYCU17I" - name: FLUENT_UID value: "0" - name: FLUENTD_CONF value: "kubernetes.conf" resources: limits: memory: 500Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log - name: dockercontainerlogdirectory mountPath: /var/log/pods readOnly: true - name: config mountPath: /fluentd/etc/kubernetes.conf subPath: kubernetes.conf terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: dockercontainerlogdirectory hostPath: path: /var/log/pods - name: config configMap: name: fluentd-cm' > wulin/fluentd/fluentd-ds.yaml $ kubectl apply -f wulin/fluentd/fluentd-ds.yaml ``` ## 測試 ``` $ kubectl create ns demo $ echo 'apiVersion: v1 kind: Pod metadata: name: my-logging-pod namespace: demo spec: restartPolicy: Always containers: - name: sak image: rancherlabs/swiss-army-knife:latest command: ["/bin/sh"] args: - -c - | i=0 while true; do ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ") echo "{\"@timestamp\":\"${ts}\",\"level\":\"INFO\",\"message\":\"hello-demo\",\"seq\":${i}}" i=$((i+1)) sleep 1 done' | kubectl apply -f - ``` ``` $ kubectl -n demo logs my-logging-pod --tail 10 {"@timestamp":"2025-09-11T01:58:05Z","level":"INFO","message":"hello-demo","seq":61713} {"@timestamp":"2025-09-11T01:58:06Z","level":"INFO","message":"hello-demo","seq":61714} {"@timestamp":"2025-09-11T01:58:07Z","level":"INFO","message":"hello-demo","seq":61715} {"@timestamp":"2025-09-11T01:58:08Z","level":"INFO","message":"hello-demo","seq":61716} {"@timestamp":"2025-09-11T01:58:09Z","level":"INFO","message":"hello-demo","seq":61717} {"@timestamp":"2025-09-11T01:58:10Z","level":"INFO","message":"hello-demo","seq":61718} {"@timestamp":"2025-09-11T01:58:11Z","level":"INFO","message":"hello-demo","seq":61719} {"@timestamp":"2025-09-11T01:58:12Z","level":"INFO","message":"hello-demo","seq":61720} {"@timestamp":"2025-09-11T01:58:13Z","level":"INFO","message":"hello-demo","seq":61721} {"@timestamp":"2025-09-11T01:58:14Z","level":"INFO","message":"hello-demo","seq":61722} ``` ## 進入 kibana ### 建立 kibana view data * Management -> Stack Management -> Data Views ![image](https://hackmd.io/_uploads/HJTTj865ex.png) * Kibana -> Data Views ![image](https://hackmd.io/_uploads/Bkex38a5xg.png) * 建立 Data Views ![image](https://hackmd.io/_uploads/SkNAx2Jogx.png) * index pattern 填寫 `demo*`,設定好後點選 Save data view to Kibana ![image](https://hackmd.io/_uploads/BkGebn1oex.png) ### 查詢 log * Dev Tools -> Console * 查詢 demo index 下的所有資料 ``` POST /demo*/_search { "query": { "match_all": {} } } ``` ![image](https://hackmd.io/_uploads/S1nIZ31ixg.png) * 使用字串查詢,這會查所有 index 的 log ``` GET _search { "query": { "query_string": { "query": "my-logging-pod" } }, "size": 10 } ``` * 使用 k8s namespace 名稱查詢 pod log ``` GET _search { "query": { "match": { "kubernetes.namespace_name": "demo" } }, "size": 50 } ``` * 查詢所有 index ``` GET _cat/indices?v&s=index ``` * 指定查詢 log,只要看到 `message`、`pod 名稱`、`namespace 名稱`、`k8s 節點名稱`、`container 名稱`、`時間` ``` GET /demo*/_search { "_source": [ "message", "kubernetes.pod_name", "kubernetes.namespace_name", "kubernetes.host", "kubernetes.container_name", "@timestamp" ], "query": { "match": { "kubernetes.namespace_name": "demo" } }, "size": 50 } ``` ## 參考 https://docs.fluentd.org/container-deployment/kubernetes