fluentd采集K8S日志
1 前言
发现fluentd是一个不错的日志采集转换工具,也非常的轻量。
支持输入(Input)、过滤(Filter)、解析(Parser)、输出(Output)的灵活扩展。
说白话就是功能比filebeat丰富,比logstash轻量,从内存使用情况就能明显发现
那么就开始使用fluentd采集K8S的日志吧,原理docker的日志默认输出到了
[root@k8s-node04 ~]# head -100 /var/lib/docker/containers/feb5be2528d96c21d6e77508d0e5e25d9fc5aada95a8cea8191fcc77f3154dc1/feb5be2528d96c21d6e77508d0e5e25d9fc5aada95a8cea8191fcc77f3154dc1-json.log
{"log":"2025-05-17 09:47:20.649 INFO 1 --- [nio-8081-exec-3] com.willlink.web.core.mvc.WebLogAspect : 参数 : [1, 10, null, null, null, null]\n","stream":"stdout","time":"2025-05-17T01:47:20.65052207Z"}
{"log":" Creating a new SqlSession\n","stream":"stdout","time":"2025-05-17T01:47:20.650531313Z"}
{"log":" SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@47925525] was not registered for synchronization because synchronization is not active\n","stream":"stdout","time":"2025-05-17T01:47:20.650532873Z"}
{"log":" JDBC Connection [HikariProxyConnection@2075305405 wrapping com.mysql.jdbc.JDBC4Connection@2ddf3e2e] will not be managed by Spring\n","stream":"stdout","time":"2025-05-17T01:47:20.650534233Z"}
{"log":" ==\u003e Preparing: SELECT COUNT(*) AS total FROM t_pile_type WHERE flag = 1\n","stream":"stdout","time":"2025-05-17T01:47:20.650535501Z"}
{"log":" ==\u003e Parameters:\n","stream":"stdout","time":"2025-05-17T01:47:20.650537251Z"}
2 日志整理需求
日志整理需求
-
日志是json格式,分为3个字段,log是程序的原始日志,最后的
\n
是docker自己加上去的,第二个字段stream是程序的标准输出,time是UTC时间,注意这个时间是要处理的,不然会比北京时间慢8个小时。这个日期与日期、宿主机的时间无关,哪怕你的时间都设置对了,最后日志的时间都是不正确的。因此后期使用fluentd加上8个小时 -
还有这里需要多行合并,因为这里的日志首行是已日期开头的,其他行都是信息打印,例如java的错误日志,数据库查询结果都要往上合并,所以合并规则就是log是否包含日期(或已日期开头)
-
增加log_level字段,用于标识这条日志的日志级别,例如INFO ERROR WARN …这些在log字段内有
-
增加K8S元数据信息,例如这个容器的docker ID,镜像地址,pod ID,pod IP,名称空间,容器IP…,默认会把所有的K8S元信息添加进去,因此需要进一步的去除无关信息
-
fluentd写入的日志统一命名为k8s-pod-%Y-%m-%d,索引按照日期滚动
3 创建pod
3.1 创建elasticsearch
1.准备elastice。前置条件需要配置动态存储类SC。如果你不想吧ES部署到K8S,也可以使用传统部署es的方式
apiVersion: apps/v1
kind: StatefulSet
metadata:name: es-clusternamespace: kube-logging
spec:serviceName: elasticsearchreplicas: 3selector:matchLabels:app: elasticsearchtemplate:metadata:labels:app: elasticsearchspec:containers:- name: elasticsearchimage: elasticsearch:7.17.1imagePullPolicy: IfNotPresentresources:limits:cpu: 2000mrequests:cpu: 500mports:- containerPort: 9200name: restprotocol: TCP- containerPort: 9300name: inter-nodeprotocol: TCPvolumeMounts:- name: datamountPath: /usr/share/elasticsearch/data- name: localtimereadOnly: truemountPath: /etc/localtimeenv:- name: cluster.namevalue: k8s-logs- name: node.namevalueFrom:fieldRef:fieldPath: metadata.name- name: discovery.seed_hostsvalue: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"- name: cluster.initial_master_nodesvalue: "es-cluster-0,es-cluster-1,es-cluster-2"- name: ES_JAVA_OPTSvalue: "-Xms512m -Xmx512m"initContainers:- name: fix-permissionsimage: busyboximagePullPolicy: IfNotPresentcommand: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]securityContext:privileged: truevolumeMounts:- name: datamountPath: /usr/share/elasticsearch/data- name: increase-vm-max-mapimage: busyboximagePullPolicy: IfNotPresentcommand: ["sysctl", "-w", "vm.max_map_count=262144"]securityContext:privileged: true- name: increase-fd-ulimitimage: busyboximagePullPolicy: IfNotPresentcommand: ["sh", "-c", "ulimit -n 65536"]securityContext:privileged: truevolumes:- name: localtimehostPath:path: /etc/localtime volumeClaimTemplates:- metadata:name: datalabels:app: elasticsearchspec:accessModes: [ "ReadWriteOnce" ]storageClassName: nfs-storageresources:requests:storage: 100Gi
3.2 创建fluentd pod
准备configmap
1.创建configmap也就是fluetnd的配置文件
kind: ConfigMap
apiVersion: v1
metadata:name: fluentd-confignamespace: kube-logging
data:containers.input.conf: |-<source>@id fluentd-containers.log@type tail# 使用了hostpath存储,所以可以访问到pod的日志path "/var/log/containers/*.log"pos_file /var/log/es-containers.log.postag raw.kubernetes.*# 从日志文件的第一行开始读取,false表示只读取增量日志,不会从头读read_from_head false<parse>@type multi_format<pattern># 以json格式匹配日志format jsontime_key timetime_format %Y-%m-%dT%H:%M:%S.%N%zkeep_time_key false</pattern># 如果不是json格式日志的兜底匹配方案<pattern>format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/time_format %Y-%m-%dT%H:%M:%S.%N%z</pattern></parse></source><match raw.kubernetes.**>@id raw.kubernetes@type detect_exceptionsremove_tag_prefix rawmessage logstream streammultiline_flush_interval 5max_bytes 500000max_lines 1000</match><filter **>@id filter_concat@type concatkey log# 多行合并规则,看是否有日志出现multiline_start_regexp /(\d{4}-\d{2}-\d{2}|==>|<==)/separator ""flush_interval 5s</filter><filter kubernetes.**>@id filter_kubernetes_metadata@type kubernetes_metadataskip_container_metadata trueskip_labels trueskip_master_url true</filter><filter kubernetes.**>@id filter_k8s_cleanup@type record_transformer# 移除不需要的K8S信息(可选)remove_keys $.kubernetes.pod_name,$.kubernetes.namespace_id,$.docker,$.stream</filter><filter kubernetes.**># CHANGED: 新增 - 将kubernetes字段提升一级@id filter_promote_k8s_fields@type record_transformerenable_ruby true<record>container_name ${record['kubernetes']['container_name']}namespace_name ${record['kubernetes']['namespace_name']}pod_id ${record['kubernetes']['pod_id']}pod_ip ${record['kubernetes']['pod_ip']}host ${record['kubernetes']['host']}</record>renew_record false # CHANGED: 保留原始字段,非覆盖remove_keys $.kubernetes</filter><filter kubernetes.**> # CHANGED: 新增 - 提取log等级@id filter_log_level@type record_transformerenable_ruby true<record>log_level ${record['log'] =~ /(INFO|DEBUG|ERROR|WARN|error|debug|info|warning)/ ? $1 : 'UNKNOWN'}</record></filter>output.conf: |-<match **>@id elasticsearch@type elasticsearch@log_level errortype_name _doc# 如果你的elasticsearch在k8s内host elasticsearchport 9200# 如果es设置了访问密码#user elastic#password ****# 不是勇logstash默认的索引名称logstash_format falseindex_name k8s-pod-%Y.%m.%dtime_key timetime_key_format %Y-%m-%dT%H:%M:%S%zinclude_timestamp true<buffer time>@type filepath /var/log/fluentd-buffers/kubernetes.system.bufferflush_mode intervalretry_type exponential_backoffflush_thread_count 2flush_interval 5sretry_foreverretry_max_interval 30chunk_limit_size 2Mtotal_limit_size 500Moverflow_action blocktimekey 1dtimekey_use_utc false</buffer></match>fluent.conf: |-<system>log_level error</system><label @FLUENT_LOG><match fluent.*>@type stdout</match></label>
设置RBAC
因为获取日志的元信息是从api-server组件获取的,需要配置授权service account
apiVersion: v1
kind: ServiceAccount
metadata:name: fluentd-esnamespace: kube-logginglabels:k8s-app: fluentd-esaddonmanager.kubernetes.io/mode: Reconcile
---kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: fluentd-eslabels:k8s-app: fluentd-esaddonmanager.kubernetes.io/mode: Reconcile
rules:
- apiGroups:- ""resources:- "namespaces"- "pods"verbs:- "get"- "watch"- "list"
---kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: fluentd-eslabels:k8s-app: fluentd-esaddonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccountname: fluentd-esnamespace: kube-loggingapiGroup: ""
roleRef:kind: ClusterRolename: fluentd-esapiGroup: ""
创建daemonset
apiVersion: apps/v1
kind: DaemonSet
metadata:name: fluentd-es-v3.1.0namespace: kube-logginglabels:k8s-app: fluentd-esversion: v3.1.0addonmanager.kubernetes.io/mode: Reconcile
spec:selector:matchLabels:k8s-app: fluentd-esversion: v3.1.0template:metadata:labels:k8s-app: fluentd-esversion: v3.1.0spec:securityContext:seccompProfile:type: RuntimeDefaultpriorityClassName: system-node-criticalserviceAccountName: fluentd-escontainers:- name: fluentd-es#image: quay.io/fluentd_elasticsearch/fluentd:v3.1.0image: quay.io/fluentd_elasticsearch/fluentd:v4.7.2imagePullPolicy: IfNotPresentenv:- name: FLUENTD_ARGSvalue: --no-supervisor -qresources:limits:memory: 1024Micpu: 800mrequests:cpu: 100mmemory: 200MivolumeMounts:- name: varlogmountPath: /var/log- name: varlibdockercontainersmountPath: /var/lib/docker/containersreadOnly: true- name: config-volumemountPath: /etc/fluent/fluent.confsubPath: fluent.conf- name: config-volumemountPath: /etc/fluent/config.d/containers.input.confsubPath: containers.input.conf- name: config-volumemountPath: /etc/fluent/config.d/output.confsubPath: output.conf- name: localtimemountPath: /etc/localtimeports:- containerPort: 24231name: prometheusprotocol: TCPlivenessProbe:tcpSocket:port: prometheusinitialDelaySeconds: 5timeoutSeconds: 10readinessProbe:tcpSocket:port: prometheusinitialDelaySeconds: 5timeoutSeconds: 10terminationGracePeriodSeconds: 30volumes:- name: varloghostPath:path: /var/log- name: varlibdockercontainershostPath:path: /var/lib/docker/containers- name: config-volumeconfigMap:name: fluentd-configmap- name: localtimehostPath:path: /etc/localtime
4 查看日志
这是运行了一段时间的采集结果,分片和副本可以自己在kibana设置索引模板
查看json字段,只保留了必要的K8S信息
{"_index": "k8s-pod-2025-05-19","_type": "_doc","_id": "Eh__55YB_RI1PaU21I5o","_version": 1,"_score": 1,"_source": {"log": "2025-05-19 18:03:40.819 [nioEventLoopGroup-6-31] INFO c.c.n.k.CustomDelimiterDecoder - 解析报文完成,channelId = 6adbc0e1, oldReadableBytes = 1115,nowReadableBytes = 0\n","datetime": "2025-05-19T18:03:40.820231799+0800","container_name": "automotive-netty","namespace_name": "automotive-dev","pod_id": "dca185db-e1a4-4392-9d84-53b4971c2b93","pod_ip": "172.17.217.71","host": "k8s-node04","log_level": "INFO","@timestamp": "2025-05-19T18:03:40.820233305+08:00"},"fields": {"log": ["2025-05-19 18:03:40.819 [nioEventLoopGroup-6-31] INFO c.c.n.k.CustomDelimiterDecoder - 解析报文完成,channelId = 6adbc0e1, oldReadableBytes = 1115,nowReadableBytes = 0\n"],"pod_ip": ["172.17.217.71"],"log_level": ["INFO"],"pod_ip.keyword": ["172.17.217.71"],"log_level.keyword": ["INFO"],"container_name.keyword": ["automotive-netty"],"namespace_name": ["automotive-dev"],"pod_id.keyword": ["dca185db-e1a4-4392-9d84-53b4971c2b93"],"datetime": ["2025-05-19T10:03:40.820Z"],"@timestamp": ["2025-05-19T10:03:40.820Z"],"container_name": ["automotive-netty"],"host": ["k8s-node04"],"log.keyword": ["2025-05-19 18:03:40.819 [nioEventLoopGroup-6-31] INFO c.c.n.k.CustomDelimiterDecoder - 解析报文完成,channelId = 6adbc0e1, oldReadableBytes = 1115,nowReadableBytes = 0\n"],"namespace_name.keyword": ["automotive-dev"],"host.keyword": ["k8s-node04"],"pod_id": ["dca185db-e1a4-4392-9d84-53b4971c2b93"]}
}