日志架构
应用程序日志可以帮助您了解应用程序内部发生的情况。这些日志对于调试问题和监控集群活动特别有用。大多数现代应用程序都有某种日志记录机制。同样,容器引擎被设计为支持日志记录。容器化应用程序最简单且最常用的日志记录方法是将内容写入标准输出和标准错误流。
然而,容器引擎或运行时提供的本机功能通常不足以提供完整的日志记录解决方案。
例如,您可能希望在容器崩溃、Pod 被驱逐或节点发生故障时访问应用程序的日志。
在集群中,日志应该具有与节点、Pod 或容器无关的单独存储和生命周期。这个概念称为集群级别日志记录。
集群级别日志记录架构需要一个单独的后端来存储、分析和查询日志。Kubernetes 不提供日志数据的本机存储解决方案。相反,有许多与 Kubernetes 集成的日志记录解决方案。以下部分描述了如何在节点上处理和存储日志。
Pod 和容器日志
Kubernetes 捕获正在运行的 Pod 中每个容器的日志。
此示例使用一个 Pod 清单,其中一个容器每秒将文本写入标准输出流一次。
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args: [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']
要运行此 Pod,请使用以下命令
kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml
输出如下:
pod/counter created
要获取日志,请使用 kubectl logs 命令,如下所示
kubectl logs counter
输出类似于
0: Fri Apr 1 11:42:23 UTC 2022
1: Fri Apr 1 11:42:24 UTC 2022
2: Fri Apr 1 11:42:25 UTC 2022
您可以使用 kubectl logs --previous 来检索容器先前实例的日志。如果您的 Pod 具有多个容器,请通过使用 -c 标志附加容器名称来指定要访问哪个容器的日志,如下所示
kubectl logs counter -c count
容器日志流
Kubernetes v1.32 [alpha](默认禁用)作为一个 alpha 特性,kubelet 可以将容器产生的两个标准流的日志拆分出来:标准输出 和 标准错误。要使用此行为,您必须启用 PodLogsQuerySplitStreams 特性门控。启用该特性门控后,Kubernetes 1.35 允许通过 Pod API 直接访问这些日志流。您可以通过指定流名称(Stdout 或 Stderr),使用 stream 查询字符串来获取特定的流。您必须有权读取该 Pod 的 log 子资源。
要演示此特性,您可以创建一个 Pod,该 Pod 会定期将文本写入标准输出和错误流。
apiVersion: v1
kind: Pod
metadata:
name: counter-err
spec:
containers:
- name: count
image: busybox:1.28
args: [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date)"; echo "$i: err" >&2 ; i=$((i+1)); sleep 1; done']
要运行此 Pod,请使用以下命令
kubectl apply -f https://k8s.io/examples/debug/counter-pod-err.yaml
要仅获取 stderr 日志流,您可以运行
kubectl get --raw "/api/v1/namespaces/default/pods/counter-err/log?stream=Stderr"
有关更多详细信息,请参阅 kubectl logs 文档。
节点如何处理容器日志

容器运行时处理并重定向容器化应用程序的 stdout 和 stderr 流生成的任何输出。不同的容器运行时以不同的方式实现此功能;但是,与 kubelet 的集成标准化为CRI 日志格式。
默认情况下,如果容器重新启动,kubelet 会保留一个已终止的容器及其日志。如果 Pod 从节点驱逐,所有相应的容器也会被驱逐,以及它们的日志。
kubelet 通过 Kubernetes API 的一个特殊功能使日志可供客户端使用。通常通过运行 kubectl logs 来访问此功能。
日志轮换
Kubernetes v1.21 [稳定]kubelet 负责轮换容器日志并管理日志目录结构。kubelet 通过 CRI 将此信息发送给容器运行时,运行时将容器日志写入给定的位置。
您可以使用两个 kubelet 配置设置,containerLogMaxSize(默认 10Mi)和 containerLogMaxFiles(默认 5),使用 kubelet 配置文件。这些设置允许您配置每个日志文件的最大大小以及每个容器允许的最大文件数。
为了在工作负载生成的日志量很大的集群中执行有效的日志轮换,kubelet 还提供了一种机制来调整日志轮换的方式,包括可以执行多少个并发日志轮换以及监控和轮换日志的频率。您可以使用两个 kubelet 配置设置,containerLogMaxWorkers 和 containerLogMonitorInterval 使用 kubelet 配置文件。
当您运行 kubectl logs 如基本的日志记录示例中所示时,节点上的 kubelet 处理请求并直接从日志文件读取。kubelet 返回日志文件的内容。
说明
只有最新的日志文件的内容可通过 kubectl logs 获取。
例如,如果 Pod 写入 40 MiB 的日志,并且 kubelet 在 10 MiB 后轮换日志,则运行 kubectl logs 返回最多 10MiB 的数据。
系统组件日志
有两种类型的系统组件:通常在容器中运行的组件,以及直接参与运行容器的组件。例如
- kubelet 和容器运行时不在容器中运行。kubelet 运行您的容器(分组在 pods 中)
- Kubernetes 调度器、控制器管理器和 API 服务器在 Pod 中运行(通常是 静态 Pod)。etcd 组件在控制平面中运行,并且最常见地也作为静态 Pod。如果您的集群使用 kube-proxy,通常将其作为
DaemonSet运行。
日志位置
kubelet 和容器运行时写入日志的方式取决于节点使用的操作系统
在 Linux 节点上使用 systemd 时,kubelet 和容器运行时默认写入 journald。您使用 journalctl 读取 systemd 日志;例如:journalctl -u kubelet。
如果不存在 systemd,kubelet 和容器运行时会将内容写入 /var/log 目录中的 .log 文件。如果您希望将日志写入其他位置,您可以间接通过一个辅助工具 kube-log-runner 运行 kubelet,并使用该工具将 kubelet 日志重定向到您选择的目录。
默认情况下,kubelet 会指示你的容器运行时将日志写入 /var/log/pods 目录中的目录。
有关 kube-log-runner 的更多信息,请阅读 系统日志。
默认情况下,kubelet 将日志写入 C:\var\logs 目录中的文件(请注意,这与 C:\var\log 不同)。
虽然 C:\var\log 是 Kubernetes 为这些日志设置的默认位置,但有几个集群部署工具会将 Windows 节点的日志设置为写入 C:\var\log\kubelet。
如果你希望将日志写入其他位置,你可以通过一个辅助工具 kube-log-runner 间接运行 kubelet,并使用该工具将 kubelet 日志重定向到你选择的目录。
但是,默认情况下,kubelet 会指示你的容器运行时将日志写入 C:\var\log\pods 目录中。
有关 kube-log-runner 的更多信息,请阅读 系统日志。
对于在 Pod 中运行的 Kubernetes 集群组件,它们会将日志写入 /var/log 目录中的文件,绕过默认日志记录机制(这些组件不会写入 systemd journal)。你可以使用 Kubernetes 的存储机制将持久化存储映射到运行该组件的容器中。
Kubelet 允许将 Pod 日志目录从默认的 /var/log/pods 更改为自定义路径。可以通过在 kubelet 的配置文件中配置 podLogsDir 参数来完成此调整。
注意
重要的是要注意,默认位置 /var/log/pods 已使用很长时间,某些进程可能会隐式假定此路径。因此,更改此参数必须谨慎进行,并自行承担风险。
另一个需要记住的注意事项是,kubelet 支持该位置位于与 /var 相同的磁盘上。否则,如果日志位于与 /var 不同的文件系统上,那么 kubelet 将不会跟踪该文件系统的使用情况,如果文件系统已满,可能会导致问题。
有关 etcd 及其日志的详细信息,请查看 etcd 文档。同样,你可以使用 Kubernetes 的存储机制将持久化存储映射到运行该组件的容器中。
说明
如果你将 Kubernetes 集群组件(例如调度器)部署到从父节点共享的卷中进行日志记录,则需要考虑并确保这些日志被轮转。Kubernetes 不管理该日志轮转。
你的操作系统可能会自动实现一些日志轮转——例如,如果你将 /var/log 目录共享到组件的静态 Pod 中,节点级别的日志轮转会将该目录中的文件与 Kubernetes 外部的任何组件写入的文件视为相同。
一些部署工具会考虑到这种日志轮转并自动执行它;而另一些工具则将此作为你的责任。
集群级别日志架构
虽然 Kubernetes 不提供集群级别日志记录的本机解决方案,但你可以考虑几种常见的方法。以下是一些选项
- 使用在每个节点上运行的节点级别日志代理。
- 在应用程序 Pod 中包含一个专用的 sidecar 容器用于日志记录。
- 直接从应用程序中将日志推送到后端。
使用节点日志代理

你可以通过在每个节点上包含一个节点级别日志代理来实现集群级别日志记录。日志代理是一个专用工具,用于暴露日志或将日志推送到后端。通常,日志代理是一个容器,可以访问包含该节点上所有应用程序容器的日志文件的目录。
由于日志代理必须在每个节点上运行,建议将其作为 DaemonSet 运行。
节点级别日志记录仅为每个节点创建一个代理,并且不需要对节点上运行的应用程序进行任何更改。
容器写入 stdout 和 stderr,但没有商定的格式。节点级别代理收集这些日志并将其转发进行聚合。
使用带有日志代理的 sidecar 容器
你可以通过以下方式使用 sidecar 容器
- sidecar 容器将应用程序日志流式传输到自己的
stdout。 - sidecar 容器运行一个日志代理,该代理配置为从应用程序容器中获取日志。
流式 sidecar 容器

通过让你的 sidecar 容器写入自己的 stdout 和 stderr 流,你可以利用已经在每个节点上运行的 kubelet 和日志代理。sidecar 容器从文件、套接字或 journald 读取日志。每个 sidecar 容器都会将其日志打印到自己的 stdout 或 stderr 流。
这种方法允许你将来自应用程序不同部分的几个日志流分离出来,其中一些可能缺乏对写入 stdout 或 stderr 的支持。重定向日志的逻辑最少,因此开销并不大。此外,由于 stdout 和 stderr 由 kubelet 处理,你可以使用内置工具,如 kubectl logs。
例如,一个 Pod 运行一个容器,并且该容器使用两种不同的格式写入两个不同的日志文件。这是 Pod 的清单
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
即使你设法将这两个组件重定向到容器的 stdout 流,也不建议将具有不同格式的日志条目写入同一个日志流。相反,你可以创建两个 sidecar 容器。每个 sidecar 容器可以从共享卷中 tail 某个日志文件,然后将其日志重定向到自己的 stdout 流。
这是具有两个 sidecar 容器的 Pod 的清单
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-1
image: busybox:1.28
args: [/bin/sh, -c, 'tail -n+1 -F /var/log/1.log']
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-2
image: busybox:1.28
args: [/bin/sh, -c, 'tail -n+1 -F /var/log/2.log']
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
现在当你运行此 Pod 时,你可以通过运行以下命令单独访问每个日志流
kubectl logs counter count-log-1
输出类似于
0: Fri Apr 1 11:42:26 UTC 2022
1: Fri Apr 1 11:42:27 UTC 2022
2: Fri Apr 1 11:42:28 UTC 2022
...
kubectl logs counter count-log-2
输出类似于
Fri Apr 1 11:42:29 UTC 2022 INFO 0
Fri Apr 1 11:42:30 UTC 2022 INFO 0
Fri Apr 1 11:42:31 UTC 2022 INFO 0
...
如果你在集群中安装了节点级别代理,则该代理会自动获取这些日志流,无需进一步配置。如果你愿意,可以配置代理根据源容器解析日志行。
即使对于只有低 CPU 和内存使用量的 Pod(CPU 顺序为几毫核,内存顺序为几兆字节),将日志写入文件然后将其流式传输到 stdout 也会使节点上所需的存储空间增加一倍。如果你的应用程序写入单个文件,建议将 /dev/stdout 设置为目标,而不是实现流式 sidecar 容器方法。
sidecar 容器也可以用于轮转应用程序本身无法轮转的日志文件。这种方法的示例是运行 logrotate 的小型容器,定期运行。但是,直接使用 stdout 和 stderr 并将轮转和保留策略留给 kubelet 更加简单。
带有日志代理的 sidecar 容器

如果节点级别日志代理对你的情况不够灵活,你可以创建一个带有单独日志代理的 sidecar 容器,该代理已配置为专门与你的应用程序一起运行。
说明
在 sidecar 容器中使用日志代理可能会导致大量资源消耗。此外,你将无法使用kubectl logs 访问这些日志,因为它们不受 kubelet 控制。以下是你可以用来实现带有日志代理的 sidecar 容器的两个示例清单。第一个清单包含一个 ConfigMap 来配置 fluentd。
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
data:
fluentd.conf: |
<source>
type tail
format none
path /var/log/1.log
pos_file /var/log/1.log.pos
tag count.format1
</source>
<source>
type tail
format none
path /var/log/2.log
pos_file /var/log/2.log.pos
tag count.format2
</source>
<match **>
type google_cloud
</match>
说明
在示例配置中,你可以用任何日志代理替换 fluentd,从应用程序容器内的任何来源读取。第二个清单描述了一个带有运行 fluentd 的 sidecar 容器的 Pod。该 Pod 挂载一个卷,fluentd 可以从中获取其配置数据。
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-agent
image: registry.k8s.io/fluentd-gcp:1.30
env:
- name: FLUENTD_ARGS
value: -c /etc/fluentd-config/fluentd.conf
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config-volume
mountPath: /etc/fluentd-config
volumes:
- name: varlog
emptyDir: {}
- name: config-volume
configMap:
name: fluentd-config
直接从应用程序暴露日志

从每个应用程序暴露或推送日志的集群日志记录超出了 Kubernetes 的范围。