Featured image of post Kubernetes Pod 删除与容器重启时日志到底去哪了?

Kubernetes Pod 删除与容器重启时日志到底去哪了?

从 kubelet、CRI 日志路径、Pod UID、容器重启实例、日志轮转和 kubectl logs --previous 角度,解释 Pod 删除与容器重启时日志的保留边界。

先给结论

如果只看 Kubernetes 节点本地日志,结论可以这样记:

场景Pod 对象还在吗本地日志是否还在kubectl logs 能否查看
容器正常运行可以,默认看当前容器日志
容器重启,Pod 还在通常保留当前和上一次容器实例日志当前日志用 kubectl logs,上一次用 kubectl logs --previous
Pod 被删除不在通常会被 kubelet 清理通常不能再通过 kubectl logs 查看
Pod 被重建,同名但新 UID新 Pod 在旧 Pod 日志不会自动回来只能看新 Pod 日志
已接入 Loki、ELK、云日志等外部系统不一定节点本地可能没了可以去外部日志系统查

所以问题的直接答案是:

  1. Pod 删除后,本地日志通常会消失。
  2. 只是容器重启、Pod 还在时,上一轮容器实例的日志通常还能通过 kubectl logs --previous 查看。

但这两个结论背后有几个容易混淆的点:Pod 名字不等于 Pod 身份,容器重启不等于 Pod 删除,0.log / 1.log 也不完全等同于“永久历史日志”。

Kubernetes 里的日志是谁在管?

大多数业务容器写日志,推荐写到标准输出和标准错误:

1
2
stdout
stderr

容器运行时会把这些输出落到节点文件系统里,kubelet 再负责管理这些容器日志文件,并通过 Kubernetes API 暴露给 kubectl logs

也就是说,当你执行:

1
kubectl logs nginx-xxx

大致链路是:

1
2
3
4
5
kubectl
  -> apiserver
  -> kubelet
  -> 节点上的容器日志文件
  -> 返回给用户

Kubernetes 官方文档也明确说明,kubelet 默认会让容器运行时把容器日志写到 /var/log/pods 目录下,同时 kubelet 负责容器日志轮转和日志目录结构管理。

典型路径长这样:

1
/var/log/pods/<namespace>_<pod-name>_<pod-uid>/<container-name>/<n>.log

同时你经常还会看到:

1
/var/log/containers/<pod-name>_<namespace>_<container-name>-<container-id>.log

这个路径通常是指向 /var/log/pods/... 下实际日志文件的软链接,方便日志采集器按容器维度扫描。

Pod 删除后日志为什么会没?

Pod 的真实身份不是名字,而是 UID。

例如你看到的 Pod 叫:

1
nginx-7c9f6d8b9d-abcde

它在节点上的日志目录并不是只按名字组织,而是带上 Pod UID:

1
/var/log/pods/default_nginx-7c9f6d8b9d-abcde_7f3c.../

这个 UID 很关键。即使 Deployment 后面又拉起一个同名风格的新 Pod,新 Pod 也会有新的 UID,对应新的日志目录。

当 Pod 被删除后,kubelet 会逐步清理这个 Pod 在节点上的资源,包括容器、挂载目录、日志目录等。清理完成后,本地节点上的这份 Pod 日志就不再可靠可查。

所以:

1
kubectl delete pod nginx-7c9f6d8b9d-abcde

之后再执行:

1
kubectl logs nginx-7c9f6d8b9d-abcde

通常会得到类似结果:

1
Error from server (NotFound): pods "nginx-7c9f6d8b9d-abcde" not found

即使 Deployment 很快创建了一个新的 Pod,也不是原来的 Pod:

1
2
旧 Pod: nginx-7c9f6d8b9d-abcde, UID=A
新 Pod: nginx-7c9f6d8b9d-fghij, UID=B

新 Pod 的日志目录是新的,旧 Pod 的日志不会自动迁移过去。

容器重启时日志会保留吗?

会,但要加上限定条件:Pod 还在,且 kubelet / 容器运行时还没有清理对应的上一轮容器实例日志。

当 Pod 没删,只是里面的容器因为异常退出、探针失败、OOM、进程崩溃等原因重启时,Pod 还是同一个 Pod:

1
2
3
Pod UID 不变
容器实例变了
restartCount 增加

这时:

1
kubectl logs <pod-name>

看的是当前正在运行的容器实例日志。

而:

1
kubectl logs <pod-name> --previous

看的是上一次已经终止的容器实例日志。

多容器 Pod 要指定容器名:

1
kubectl logs <pod-name> -c <container-name> --previous

这就是排查 CrashLoopBackOff 时最常用的命令。因为当前容器可能刚启动,还没打印出关键错误;真正的错误通常在上一轮崩溃前的日志里。

0.log1.log 到底怎么理解?

在节点上你可能会看到类似目录:

1
2
3
/var/log/pods/default_demo_abc123/app/
  0.log
  1.log

这很容易让人理解成:

1
2
3
0.log = 当前日志
1.log = 上一次重启日志
2.log = 上上次重启日志

这个理解在很多场景下接近现象,但不要把它当成稳定的应用层契约。

更准确的说法是:

  1. kubelet 和容器运行时会按 CRI 日志机制维护容器日志文件。
  2. 容器重启会产生新的容器实例,上一轮实例的日志可能以另一个文件保留。
  3. kubectl logs --previous 面向的是“上一次终止的容器实例”,不是任意历史版本。
  4. 日志文件还会受到 kubelet 日志轮转配置影响。

也就是说,你可以在节点上观察 0.log1.log 这类文件,但日常排障不要依赖“手动猜编号”。优先使用:

1
2
kubectl logs <pod> -c <container>
kubectl logs <pod> -c <container> --previous

如果要查更久以前的日志,不应该指望节点本地文件,而应该依赖外部日志系统。

还有一种“轮转”:不是重启,而是文件太大

容器一直不重启,日志文件也不可能无限增长。kubelet 会对容器日志做轮转。

常见默认值是:

1
2
containerLogMaxSize: 10Mi
containerLogMaxFiles: 5

含义是:

  • 单个容器日志文件达到一定大小后会触发轮转
  • 每个容器最多保留一定数量的日志文件

这和“容器重启产生上一轮日志”不是同一个概念。

可以把它们分开理解:

类型触发条件目的
容器实例切换容器退出后被 kubelet 重新拉起让当前实例和上一轮实例日志可区分
日志大小轮转单个日志文件达到上限防止节点磁盘被日志打满

官方文档也提醒:通过 kubectl logs 通常只能拿到最新日志文件的内容。如果容器写了很多日志,旧的轮转文件不一定会通过普通 kubectl logs 全量返回。

所以不要误以为:

1
kubectl logs <pod>

一定能拿到容器从启动到现在的所有日志。它拿到的是 kubelet 当前能通过日志接口返回的内容。

三个典型场景

场景一:Deployment 滚动发布

Deployment 发布新版本时,旧 ReplicaSet 下的 Pod 会被逐步删除,新版本 Pod 会被创建。

这个过程不是“容器重启”,而是:

1
2
旧 Pod 删除
新 Pod 创建

结果是:

  • 旧 Pod 本地日志会随 Pod 清理而消失
  • 新 Pod 是新 UID,只能看到新日志
  • 想查旧版本发布前后的日志,必须依赖外部日志系统

如果线上问题发生在发布瞬间,只靠 kubectl logs 很容易错过现场。

场景二:CrashLoopBackOff

容器反复崩溃,但 Pod 对象还在。

这时不要只看:

1
kubectl logs <pod>

还要看:

1
kubectl logs <pod> --previous

如果是多容器:

1
kubectl logs <pod> -c <container> --previous

排查顺序建议是:

1
2
3
4
kubectl describe pod <pod>
kubectl logs <pod> -c <container>
kubectl logs <pod> -c <container> --previous
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].restartCount}'

describe 看事件和退出原因,logs --previous 看上一轮进程退出前的业务日志。

场景三:节点磁盘被日志打满

如果业务疯狂输出日志,Pod 不删除、不重启,节点磁盘也可能被打满。

这时要关注:

  • kubelet 的 containerLogMaxSize
  • kubelet 的 containerLogMaxFiles
  • 节点 /var/log/pods/var/log/containers 使用量
  • 日志采集器是否及时采集并上报
  • 是否有应用把日志写进容器文件系统或 emptyDir

生产环境里,容器 stdout/stderr 只适合作为采集入口,不适合作为长期存储。

为什么“Pod 删除日志就没了”是合理设计?

Kubernetes 的设计重点是调度和编排,不是日志长期存储。

Pod 是临时对象,可能因为很多原因消失:

  • Deployment 滚动发布
  • 节点驱逐
  • HPA 缩容
  • Job 执行完成
  • 手动删除
  • 节点故障
  • 镜像升级

如果 Kubernetes 默认把每个已经删除的 Pod 日志都长期保存在节点上,会带来几个问题:

  1. 节点磁盘不可控
  2. Pod 被调度到不同节点后日志分散
  3. 节点故障时日志仍然可能丢
  4. 多租户环境下清理和隔离更复杂

所以 Kubernetes 只提供基本日志访问能力。真正的“日志持久化”和“跨 Pod 查询”应该交给集群级日志系统。

生产环境应该怎么做?

如果日志有排障、审计、交易链路追踪等价值,不要依赖节点本地日志。

推荐做法是接入集群级日志采集:

1
2
3
4
5
应用 stdout/stderr
  -> /var/log/pods
  -> 节点日志采集 DaemonSet
  -> Loki / Elasticsearch / OpenSearch / 云日志服务
  -> 查询、告警、归档

常见方案:

  • Fluent Bit / Fluentd
  • Vector
  • Filebeat
  • Promtail + Loki
  • OpenTelemetry Collector
  • 云厂商托管日志服务

关键原则:

  1. 应用日志写 stdout/stderr
  2. 节点上部署 DaemonSet 采集器
  3. 日志进入远端存储后再做查询和告警
  4. 本地日志只当短期缓冲,不当长期存储

常用命令整理

查看当前容器日志:

1
kubectl logs <pod>

查看指定容器日志:

1
kubectl logs <pod> -c <container>

查看上一次终止的容器实例日志:

1
kubectl logs <pod> -c <container> --previous

查看 Pod 事件和容器退出原因:

1
kubectl describe pod <pod>

查看重启次数:

1
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].restartCount}'

查看 Pod UID:

1
kubectl get pod <pod> -o jsonpath='{.metadata.uid}'

在节点上观察日志目录:

1
2
ls -lah /var/log/pods/
ls -lah /var/log/containers/

查看 kubelet 日志轮转配置,具体路径取决于集群安装方式:

1
cat /var/lib/kubelet/config.yaml | grep -E 'containerLogMaxSize|containerLogMaxFiles'

最后总结

这两个问题可以这样回答:

Pod 删除,日志通常就没了。

因为 Pod 删除后 kubelet 会清理该 Pod 在节点上的相关资源,本地 /var/log/pods/<namespace>_<pod>_<uid>/ 不应被当成持久化日志存储。后续即使出现同名 Pod,也已经是新 UID、新日志目录。

容器只是重启,上一轮日志通常还能看。

因为 Pod 还在,kubelet 通常会保留上一轮终止容器实例的日志,可以通过:

1
kubectl logs <pod> --previous

查看。但它不是完整历史日志系统,也不是无限保留。它受容器实例生命周期、kubelet 清理策略和日志轮转配置影响。

真正可靠的结论是:kubectl logs 适合临时排障,生产环境日志留存必须依赖外部日志系统。

参考资料

本文采用 CC BY-NC-SA 4.0 协议,转载请注明出处。
记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计