应用日志,是指 pod 中业务程序打出来的日志。
这是实现采集容器 stdout 日志功能时的调研,重点在于:
- pod 日志打到了哪里
- 如何配置 filebeat 来做采集
查看日志
如果日志打到 stdout,那么 kubectl logs
可以看到。如果 pod 经历过重启,kubectl logs --previous
可以看之前的日志。如果 pod 被销毁,这些日志会丢失。
如果日志打到容器内的文件中,可以运行 kubectl exec <pod> cat <logfile>
,或者 tail -n
等命令查看。
kubectl cp
提供了在容器内外复制文件的能力:
kubectl cp foo-pod:/var/log/foo.log foo.log
kubectl cp localfile foo-pod:/etc/remotefile
Kubernetes 的 pod 日志收集机制
K8S 利用了 Docker 提供的 logging driver 机制来采集容器日志(来源)。Docker 默认将各容器的 stdout 和 stderr 重定向到磁盘文件中,最终变成一个 JSON 文件。在 node 节点上的一个例子(裁剪了 ID 长度以提升可读性):
[root@cluster1node1 pods]# tree /var/log/pods
/var/log/pods
|-- 510cb18b-3b2c-11e9-85ab-525400b56f21
| `-- ake-add-node
| `-- 0.log -> /var/lib/docker/containers/36ce55/36ce55-json.log
|-- c68f45d9-3b09-11e9-85ab-525400b56f21
| `-- coredns
| `-- 0.log -> /var/lib/docker/containers/3336f5/3336f5-json.log
`-- fcbc3dd0-3b2a-11e9-85ab-525400b56f21
`-- ake-add-node
`-- 0.log -> /var/lib/docker/containers/2b77c9/2b77c9-json.log
JSON 日志内容节选,带 log
, stream
及 time
三个参数:
{"log":" \"capabilities\":{\n","stream":"stderr","time":"2019-02-28T03:34:58.383595946Z"}
{"log":" \"por{"log":" }\n","stream":"stderr","time":"2019-02-28T03:34:58.383600746Z"}
Pod 重启时,K8S 默认保留一份已停止的容器的日志。
K8S 通过 Docker Engine 接口去创建容器,在 HostConfig 中指定了 json-file
的文件格式(猜测如此,代码中没找到),因此 /var/log/pods
目录可以保证存在。/var/log/pods
下的具体日志内容是由 Docker 写入。
注意 1.10 及以下版本可能没有这个目录:
如果您有特殊需求,那么可以在集群中设置您自己的日志记录解决方案。在运行 Kubernetes V1.11 或更高版本的集群中,可以从 /var/log/pods/ 路径中收集容器日志。在运行 Kubernetes V1.10 或更低版本的集群中,可以从 /var/lib/docker/containers/ 路径中收集容器日志。
从这个 issue 中的信息能看到,1.10 起 /var/log/pods
下的路径发生变化:
# 1.9: /var/log/pods/<UID>/<container>_<0-based-restart-count>.log
# 1.10 and newer: /var/log/pods/<UID>/<container>/<0-based-restart-count>.log
这个 issue 体现出 1.6 版本的 K8S 已经有 /var/log/pods
日志了。
1.9.6 版本的 K8S 的一例 log 文件路径:
# Symlink: /var/log/pods/<pod-uid>/<container-name>_<0-based-restart-count>.log => /var/log/containers/<pod-name>_<namespace>_<container-name-container-id>.log
/var/log/pods/37f5169f-3e63-11e9-be9d-525400dec72d/onlyice-consumer_5.log
└ pod uid | |
└ container name |
└ restart count (6th run)
其中 pod uid 可以通过这几种方式取得:
- 通过
kubectl get pods/<pod-name> -n <namespace> -o yaml
,观察metadata.uid
字段 - 通过 Downward API 通过
fieldRef
从metadata.uid
中获得
如果想通过 Kubernetes 来获得 pod 中容器的 stdout / stderr,可以使用 /var/log/pods
中的日志,但是官方并没有文档描述这个路径的 pattern,也没有承诺这个路径不变。而且直接采集 /var/log/pods
中的日志有可能丢数据(因为日志滚动)。
可能的更好方式是使用 kubectl logs
或者 kubeapi-server 的 read log 接口。这两者不依赖 /var/log/pods
下的日志。kubectl logs
最终调用的是 kubeapi-server,而 kubeapi-server 又会调用 Docker Engine API 中的 /containers/(id or name)/logs
接口。这个接口使你不用关心 /var/log/pods
的路径变化。但是有 issue (1, 2) 反馈,docker logs -f
在 JSON 日志发生 rotation 时会有异常。我实测是 docker logs -f
及 kubectl logs -f
都会在那一刻退出,不再显示日志。这就使得这个方法也变得鸡肋。
filebeat 采集 /var/log/pods
下日志
观察到 log-opts
仅配置单文件的情况,一旦日志达到限制的大小,会 close_write
再 open
,没有删除及新建过程发生,因此 inode 也没有发生变化:
$ inotifywatch ./53f5db77572eca11f6af33f89e3d0972298e1ab1743ebc7f1ce5d1e807d90a04-json.log
Establishing watches...
Finished establishing watches, now collecting statistics.
^C
total modify close_write open filename
108588 108580 4 4 ./53f5db77572eca11f6af33f89e3d0972298e1ab1743ebc7f1ce5d1e807d90a04-json.log
根据这个 帖子,对于快速被 truncate 的文件,filebeat 可能会漏读日志。可以设置 backoff
参数来让 filebeat 更激进地去检测文件内容变化,来减少漏读的概率。但是本质上无法避免。
参考:
不同容器平台的 Docker 日志配置(不影响 k8s 拉起的 pod)
视 K8S 集群部署工具的不同,可能会带上不同的 --log-opt
参数来控制日志文件的大小和数量,如 Google 的 Container-VM Image 配置 了 10MiB 及最多 5 个文件,并最终作为环境变量写入 /etc/default/docker
文件中。
部分容器平台实现调研:
- 灵雀 2.x 版本,在节点上配置了(
/etc/docker/daemon.json
)单文件最多 20MiB 的配置 - TKE 公有云版本,在节点上配置了(
/etc/docker/dockerd
)单文件上限 100MiB,最多 10 个文件
docker inspect -f '{{.HostConfig.LogConfig.Type}}' <container-id>
可以看到相应的容器运行在哪种 logging driver 上。