3. Kubernetes日志架构:kubelet日志管理、Pod日志生命周期、容器运行时日志路径

聊到Kubernetes日志,很多人第一反应就是ELK、Loki这些高大上的组件。但说实话,我见过太多团队在搭建集中式日志平台之前,连最基础的日志路径都搞不清楚。结果呢?日志采集器配了半天,发现路径写错了,数据根本没上来。

这一节,我们就从最底层开始,把Kubernetes日志架构的根儿摸清楚。你想想看,如果连日志存在哪儿、怎么轮转、生命周期怎么管理的都不知道,那故障排查的时候,你连日志都找不到,还排个啥?

3.1 kubelet日志管理:节点上的“管家”

kubelet是每个节点上的核心代理,它负责管理Pod的生命周期。同时,它也负责管理容器日志。我个人习惯把kubelet看作是节点上的“日志管家”。

kubelet本身并不直接产生应用日志,它管理的是容器运行时(比如Docker、containerd)输出的日志。它的核心职责有两个:

  • 日志轮转:防止日志把磁盘撑爆
  • 日志重定向:把容器标准输出/标准错误流重定向到宿主机文件

kubelet的日志管理策略,主要通过两个参数控制:

参数 默认值 说明
containerLogMaxSize 10Mi 单个日志文件的最大大小,超过后触发轮转
containerLogMaxFiles 5 最多保留多少个轮转后的日志文件
⚠️ 注意: 这两个参数在Kubernetes v1.21+才成为稳定特性。如果你还在用老版本,日志轮转可能依赖容器运行时的配置。我曾经在升级集群时踩过这个坑,升级后日志轮转策略变了,导致一些老节点的磁盘使用率飙升。

kubelet的日志管理流程,说白了就是:

  1. 容器运行时把日志写到标准输出
  2. kubelet通过CRI接口读取这些日志流
  3. kubelet把日志写入宿主机文件系统
  4. 当文件大小达到containerLogMaxSize,kubelet执行轮转
  5. 轮转后的旧文件保留containerLogMaxFiles个,超出的删除

3.2 Pod日志生命周期:从生到死

Pod的日志生命周期,和Pod本身的生命周期是绑定的。这一点非常重要,我见过不少新手以为Pod删了日志还在,结果排查历史故障时一脸懵。

Pod日志的生命周期分为三个阶段:

  • 运行阶段:Pod正常运行,日志持续写入
  • 重启阶段:Pod内的容器重启,旧日志保留,新日志继续写入
  • 删除阶段:Pod被删除,关联的日志文件也会被清理

核心要点: Pod删除后,其日志文件默认会被kubelet清理。如果你需要保留Pod的历史日志,必须使用集中式日志系统(如Loki、Elasticsearch)或配置hostPath持久化。

为什么会这样?因为Kubernetes的设计哲学是“不可变基础设施”。Pod是临时性的,它的日志自然也是临时的。你想想看,如果每个Pod的日志都永久保留在节点上,那节点磁盘早就爆了。

我记得有一次线上故障,一个Pod反复Crash,我们想查历史日志。结果发现Pod被Deployment自动重建了,旧Pod的日志已经被清理。从那以后,我养成了一个习惯:所有生产环境的Pod,必须配置日志采集器,把日志实时发送到远端存储。

3.3 容器运行时日志路径:/var/log/pods 和 /var/log/containers

这两个路径,是Kubernetes日志架构中最核心的两个目录。搞不清楚它们的区别,你连日志文件都找不到。

3.3.1 /var/log/pods:Pod级别的日志目录

这个目录是kubelet管理的,结构非常清晰:

/var/log/pods/
└── <namespace>_<pod-name>_<pod-uid>/
    └── <container-name>/
        ├── 0.log          # 当前正在写入的日志
        ├── 1.log          # 轮转后的旧日志
        └── 2.log          # 更旧的日志

这个路径的特点:

  • 以Pod为单位组织目录,包含命名空间、Pod名称和UID
  • 每个容器一个子目录
  • 日志文件按数字后缀轮转

我个人习惯在排查问题时,直接进这个目录。因为Pod UID是唯一的,即使有同名Pod被重建,UID不同,日志也不会混淆。

3.3.2 /var/log/containers:容器级别的符号链接

这个目录是/var/log/pods的符号链接集合,目的是为了方便人类阅读和工具采集:

/var/log/containers/
├── <pod-name>_<namespace>_<container-name>-<container-id>.log
└── ...

注意看,文件名格式是:Pod名称_命名空间_容器名称-容器ID.log。这个文件本身是一个符号链接,指向/var/log/pods下对应的日志文件。

💡 小技巧: 当你用kubectl logs命令查看Pod日志时,kubelet实际上就是从这个路径读取的。所以,如果你在节点上直接操作,用ls -l /var/log/containers/可以快速找到所有容器的日志文件。

3.3.3 两个路径的对比

特性 /var/log/pods /var/log/containers
组织方式 按Pod(含UID) 按容器(含ID)
文件类型 真实日志文件 符号链接
可读性 结构清晰,适合程序处理 文件名包含关键信息,适合人工查找
轮转影响 直接管理轮转后的文件 符号链接始终指向最新的日志文件

3.4 实战:如何快速定位日志文件

假设你接到一个告警,说某个Pod日志异常。你SSH到节点上,怎么快速找到日志?

我的做法是:

  1. 先查Pod的UID:kubectl get pod <pod-name> -o jsonpath='{.metadata.uid}'
  2. 然后直接进目录:cd /var/log/pods/<namespace>_<pod-name>_<uid>/<container-name>/
  3. tail -f 0.log实时查看最新日志

如果你不想查UID,也可以用符号链接:

ls -l /var/log/containers/ | grep <pod-name>
tail -f /var/log/containers/<pod-name>_<namespace>_<container-name>-*.log

⚠️ 避坑指南: 我曾经遇到过一个问题——用tail -f跟踪符号链接时,如果容器重启了,符号链接会指向新的容器ID,但tail命令可能还在跟踪旧的文件句柄。结果就是:你以为在看实时日志,其实看的是旧容器的日志。解决方案是:用tail -F(大写F),它会自动跟踪文件名的变化。

3.5 容器运行时的差异

不同的容器运行时,日志路径的底层实现略有不同。目前主流的是containerd和Docker。

  • containerd:Kubernetes v1.24+默认使用。日志直接由kubelet通过CRI管理,路径就是/var/log/pods
  • Docker:老版本Kubernetes使用。Docker有自己的日志驱动(如json-file),日志默认在/var/lib/docker/containers/<container-id>/<container-id>-json.log。kubelet会从这个路径读取并重定向到/var/log/pods

嗯,这里要注意:如果你还在用Docker作为容器运行时,排查日志时可能会发现两个地方都有日志文件。但记住,/var/log/pods为准,因为kubelet管理的日志才是Kubernetes视角下的“官方”日志。

我个人建议,新集群一律使用containerd。Docker的日志驱动配置复杂,而且多了一层转发,排查问题时容易混淆。

3.6 小结

这一节我们聊了Kubernetes日志架构的底层逻辑。说白了,就是三件事:

  • kubelet负责日志的轮转和管理
  • Pod日志的生命周期和Pod绑定,Pod删了日志就没了
  • /var/log/pods是真实日志目录,/var/log/containers是符号链接

搞清楚了这些,你再去配置日志采集器、排查日志丢失问题,心里就有底了。下一节,我们会聊如何用kubectl logs命令高效排查Pod故障,包括多容器Pod、历史日志查看等实战技巧。