日志架构
应用程序日志可以帮助你了解应用程序内部发生的事情。日志对于调试问题和监控集群活动特别有用。大多数现代应用程序都具有某种日志记录机制。同样,容器引擎也设计为支持日志记录。对于容器化应用程序来说,最简单、最常用的日志记录方法是写入标准输出和标准错误流。
但是,容器引擎或运行时提供的原生功能通常不足以构成完整的日志记录解决方案。
例如,如果容器崩溃、Pod 被驱逐或节点宕机,你可能希望访问应用程序的日志。
在集群中,日志应该具有独立于节点、Pod 或容器的单独存储和生命周期。此概念称为集群级日志记录。
集群级日志记录架构需要一个单独的后端来存储、分析和查询日志。Kubernetes 不为日志数据提供原生存储解决方案。相反,有许多日志记录解决方案可以与 Kubernetes 集成。以下部分介绍如何在节点上处理和存储日志。
Pod 和容器日志
Kubernetes 会捕获运行中 Pod 中每个容器的日志。
此示例使用一个 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
有关更多详细信息,请参阅kubectl logs
文档。
节点如何处理容器日志
容器运行时会处理并重定向生成到容器化应用程序的 stdout
和 stderr
流的任何输出。不同的容器运行时以不同的方式实现这一点;但是,与 kubelet 的集成已标准化为*CRI 日志记录格式*。
默认情况下,如果容器重新启动,kubelet 会保留一个已终止的容器及其日志。如果 Pod 从节点中被驱逐,则所有相应的容器及其日志也会被驱逐。
kubelet 通过 Kubernetes API 的一项特殊功能向客户端提供日志。访问此功能的常用方法是运行 kubectl logs
。
日志轮转
Kubernetes v1.21 [稳定]
kubelet 负责轮转容器日志和管理日志目录结构。kubelet 将此信息发送到容器运行时(使用 CRI),运行时将容器日志写入给定的位置。
你可以使用kubelet 配置文件配置两个 kubelet 配置设置,即 containerLogMaxSize
(默认值为 10Mi)和 containerLogMaxFiles
(默认值为 5)。这些设置允许你分别配置每个日志文件的最大大小和每个容器允许的最大文件数。
为了在工作负载生成的日志量很大的集群中执行高效的日志轮转,kubelet 还提供了一种机制来调整日志轮转的方式,即可以执行多少个并发日志轮转以及监控和轮转日志的间隔(如果需要)。你可以使用kubelet 配置文件配置两个 kubelet 配置设置,即 containerLogMaxWorkers
和 containerLogMonitorInterval
。
当你像在基本日志记录示例中那样运行kubectl logs
时,节点上的 kubelet 会处理请求并直接从日志文件中读取。kubelet 返回日志文件的内容。
注意
只有最新日志文件的内容可以通过 kubectl logs
获取。
例如,如果 Pod 写入 40 MiB 的日志,并且 kubelet 在 10 MiB 后轮转日志,则运行 kubectl logs
最多返回 10MiB 的数据。
系统组件日志
系统组件有两种类型:通常在容器中运行的组件,以及直接参与运行容器的组件。例如
- kubelet 和容器运行时不在容器中运行。kubelet 运行你的容器(分组在Pod中)
- Kubernetes 调度器、控制器管理器和 API 服务器在 Pod 中运行(通常是静态 Pod)。etcd 组件在控制平面中运行,最常见的是也作为静态 Pod 运行。如果你的集群使用 kube-proxy,你通常将其作为
DaemonSet
运行。
日志位置
kubelet 和容器运行时写入日志的方式取决于节点使用的操作系统
在使用 systemd 的 Linux 节点上,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 日志)。你可以使用 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 中包含用于日志记录的专用边车容器。
- 将日志直接从应用程序推送到后端。
使用节点日志代理
您可以通过在每个节点上包含一个*节点级日志代理*来实现集群级日志记录。日志代理是一种专用工具,用于公开日志或将日志推送到后端。通常,日志代理是一个容器,可以访问包含该节点上所有应用程序容器的日志文件的目录。
由于日志代理必须在每个节点上运行,因此建议将代理作为 DaemonSet
运行。
节点级日志记录每个节点仅创建一个代理,并且不需要对节点上运行的应用程序进行任何更改。
容器写入 stdout 和 stderr,但没有约定的格式。节点级代理会收集这些日志并转发它们以进行聚合。
将边车容器与日志代理一起使用
您可以通过以下方式之一使用边车容器
- 边车容器将应用程序日志流式传输到其自己的
stdout
。 - 边车容器运行一个日志代理,该代理配置为从应用程序容器中获取日志。
流式边车容器
通过让边车容器写入其自己的 stdout
和 stderr
流,您可以利用已在每个节点上运行的 Kubelet 和日志代理。边车容器从文件、套接字或 journald 读取日志。每个边车容器都会将其日志打印到其自己的 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
流也是如此。相反,您可以创建两个边车容器。每个边车容器都可以从共享卷中获取特定日志文件,然后将其日志重定向到其自己的 stdout
流。
以下是具有两个边车容器的 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
设置为目标,而不是实现流式边车容器方法。
边车容器还可用于轮换应用程序本身无法轮换的日志文件。这种方法的一个示例是一个定期运行 logrotate
的小容器。但是,直接使用 stdout
和 stderr
并将轮换和保留策略留给 Kubelet 会更简单。
带有日志代理的边车容器
如果节点级日志代理对您的情况不够灵活,您可以创建一个带有单独日志代理的边车容器,该代理专门配置为与您的应用程序一起运行。
注意
在边车容器中使用日志代理会导致大量的资源消耗。此外,您将无法使用kubectl logs
访问这些日志,因为它们不受 Kubelet 控制。以下是两个清单示例,您可以使用它们来实现带有日志代理的边车容器。第一个清单包含一个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 替换为任何日志代理,从应用程序容器内的任何源读取。第二个清单描述了一个 Pod,该 Pod 具有一个运行 fluentd 的边车容器。该 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 的范围。
下一步
- 阅读有关Kubernetes 系统日志的信息
- 了解Kubernetes 系统组件的跟踪
- 了解如何在 Pod 失败时自定义 Kubernetes 记录的终止消息