小Cの已经记不起来的博客

Kubernetes 日志采集 配置避坑指南

最近给集群上日志采集,本来以为写个 DaemonSet 跑个 Fluent Bit 就完事儿了,结果日志不是缺、就是乱、要么就是疯狂重复,前前后后折腾了快一个礼拜... 把踩过的坑都记在这了,配置全部实测能跑,直接抄就行,不谢。

先搞清楚运行时是啥,别拿 docker 的解析规则硬套

最开始在测试环境一切正常,因为测试集群跑的还是 docker,日志文件是标准的 json 格式,{"log":"xxx","stream":"stdout","time":"..."},配个 json parser 就搞定。后来推到生产环境,日志全部解析失败,进节点一看,生产用的是 containerd,日志长这样:

2024-05-20T08:10:11.123456789Z stdout F this is a log line

之前配的 json parser 直接歇菜,所有日志全进了死信队列。另外中间那个 F 不是日志内容,是 CRI 的标志位,F 代表完整一行,P 代表半行,单行超过 16K 会被切开,前半段标 P,最后一段才标 F,不做处理的话一条日志就碎成好几条了。

Fluent Bit 配个 CRI 的 parser:

[PARSER]
    Name        cri
    Format      regex
    Regex       ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
    Time_Key    time
    Time_Format %Y-%m-%dT%H:%M:%S.%L%z

要是集群里 docker 和 containerd 混着跑,那就两套 parser 按节点分别指定,别指望一套配置走天下,没准儿下个集群又不一样了。

DaemonSet 挂载少一个目录,日志就集体消失

推上生产后发现一大半 pod 没日志,采集器进程活得好好的,手动 tail 宿主机上的日志文件也有内容,就是采不到。查了半天,原因特别蠢:/var/log/containers/ 下面全是软链接,真正的文件在 /var/log/pods/ 里,而我只挂了前者,进容器一看,软链接全断了。

老实把两个都挂上:

volumeMounts:
  - name: varlogcontainers
    mountPath: /var/log/containers
    readOnly: true
  - name: varlogpods
    mountPath: /var/log/pods
    readOnly: true
volumes:
  - name: varlogcontainers
    hostPath:
      path: /var/log/containers
  - name: varlogpods
    hostPath:
      path: /var/log/pods

如果节点还是 docker 运行时,还得额外挂一个 /var/lib/docker/containers,不然软链照样断。

Java 堆栈,采集完碎成渣

Java 应用一抛异常就是十几行的堆栈,采集完变成十几条独立日志,ES 里翻日志翻得人头晕。得配多行合并,把不以外层时间戳开头的行拼回上一条:

[MULTILINE_PARSER]
    Name          java_stack
    Type          regex
    Flush_Timeout 1000
    Rule "start_state" "/^\d{4}-\d{2}-\d{2}/" "cont"
    Rule "cont" "/^\s+(at|\.\.\.|Caused by)/" "cont"

用的时候在 INPUT 里加一行 multiline.parser java_stack 就行。注意这玩意儿吃内存,limit 给小了,赶上日志洪峰直接 OOMKill。更坑的是 kubelet 默认单文件 10Mi、最多留 5 个文件就轮转,采集器挂一会儿没接上,轮转一圈日志就真没了,找都没地方找去。

采集器把自己采集了,无限套娃

某天发现 ES 磁盘增速不太对劲,一查全是同一条 fluent-bit 的日志在翻倍。原因也很离谱:采集器自己的日志打到 stdout,然后被自己采集了,采完又打一条处理成功的日志... 开始无限套娃。直接在 INPUT 里把自家的日志排除:

[INPUT]
    Name         tail
    Path         /var/log/containers/*.log
    Exclude_Path /var/log/containers/*_logging_*.log

我这边采集器放在 logging 这个 namespace,所以这么写就够了,你们按自己的 namespace 改。顺带一提,采集器的日志级别别开 debug,真顶不住。

时区,永远是时区

日志时间全按 UTC 存的,上午十点出的问题,去 ES 里搜 10:00 搜了半天啥也没有,因为日志里写的是凌晨两点... 要么查询前自己心里换算,要么采集端直接转掉。Fluent Bit 里加个偏移就完事:

[INPUT]
    Name        tail
    Path        /var/log/containers/*.log
    Time_Offset +0800

控制平面节点的日志,默认是没人采的

有次要排查 apiserver 的问题,翻了一圈发现控制平面节点上压根没有采集器在跑。原因也简单,master 默认带 taint,DaemonSet 默认容忍不了,加个 tolerations 就好:

tolerations:
  - operator: Exists

全容忍虽然糙了点,但是省心,免得以后集群加了个带污点的节点又忘掉这茬。

最后

以上坑都是挨个踩出来的,配置抄了就能用。日志稳定灌进 ES 的那一刻,感觉这礼拜没白熬... 然后两周后磁盘满了,那就是另一个故事了。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放