在 k8s 集群、云基础架构或是网络设备上我们常常需要用 fluent bit、fluentd 之类的工具来收集日志。其中一种架构是将收集日志的 agent 运行在宿主机上,我们自己的服务写日志,agent 收集日志转发到 elastic search 之类的处理后端上。

如果 agent 和我们自己的服务都是以 pod 的形式运行在 k8s 集群上,我们就需要让他们一个读一个写同一个文件,就都需要挂载同一个目录。而当我们有多个 pod 可能有相同的日志路径时,我们就要保证能区别出不同的 pod 的日志。

挂载时映射到不同路径

一种方法是直接写日志时,写到包含 $POD_NAME 这类环境变量的路径下。但我想在挂载目录时就映射到宿主机包含 $POD_NAME 的目录下,于是就考虑 SubPathExpr,这个是 Kubernetes 1.17 后有的功能。

大概的用法如下

apiVersion: apps/v1
kind: Deployment
...
spec:
spec:
containers:
- name: asr
...
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
volumeMounts:
- name: log
mountPath: /log
subPathExpr: $(POD_NAME)
volumes:
- name: log
hostPath:
path: /tmp/log

然而执行

kubectl apply -f deployment.yaml

后,并没有成功运行起来,

kubectl describe pod my-pod-xxx -n mynamespace

发现报错

Error: stat /tmp/log: no such file or directory

非常地不解,也只好先加上 type:

volumes:
- name: log
hostPath:
path: /tmp/log
type: DirectoryOrCreate

这下是运行起来了,但是本地怎么就不见 /tmp/log 里有新的文件夹呢?

到容器里执行

mount | grep "/tmp/log"

得到

overlay on /tmp/log type overlay (rw,relatime,lowerdir=/data00/docker/lib/overlay2/l/CCC:....省略...:/data00/docker/lib/overlay2/l/NNN,upperdir=/data00/docker/lib/overlay2/eee/diff,workdir=/data00/docker/lib/overlay2/eee/work)

而其它 hostPath 挂载的长这样

/dev/vda1 on /opt/tmp/xxx type ext4 (rw,relatime,errors=remount-ro,data=ordered)

后来找到了这个 issue:

​https://github.com/kubernetes/kubernetes/issues/61456​

(实际上先搜到的是一个翻译文 ​​ddeevv.com/question/kubernetes-kubernetes-61456.html​​)

原来是因为早期 k8s 不会对 subPath 做检查,于是就存在一个漏洞,用户可以搞一个软链接,让容器可以访问任何宿主机上的目录,后来修复了这个漏洞 ​​https://kubernetes.io/blog/2018/04/04/fixing-subpath-volume-vulnerability/,​

就导致容器方式(containerized)运行的 kubelet,用 subPath (或 subPathExpr)后创建的目录就跑到 kubelet 的容器里了。

那要怎么办呢,如果 kubelet 是你自己部署的,那可以把 hostPath 对应的路径给挂载到 kubelet 的容器里,不然就没办法了。

其实还有办法,就是不用 subPath(subPathExpr 同),而是搞个 initContainer 来创建目录。

修改写日志的路径

或者绕过去,修改写日志的路径,由于我们有多个日志要写,统一用配置文件来配置这些日志写的路径,所以就可以搞一个 configmap 来存配置文件。

kind: ConfigMap
apiVersion: v1
metadata:
name: log-config
namespace: development
data:
log.conf: >+
xxxxxx
xxxxxx.File=/log/${POD_NAME}/xxxx.log
xxxxxx

然后

kubectl apply -f configmap.yml

还要编辑 deployment:

- name: log
mountPath: /log/
# subPathExpr: $(POD_NAME)
- name: log-config
mountPath: /conf
readOnly: true
volumes:
- name: log
hostPath:
path: /my/log
- name: log-config
configMap:
name: log-config

kubectl apply -f development.yml




┆凉┆暖┆降┆等┆幸┆我┆我┆里┆将┆ ┆可┆有┆谦┆戮┆那┆ ┆大┆始┆ ┆然┆

┆薄┆一┆临┆你┆的┆还┆没┆ ┆来┆ ┆是┆来┆逊┆没┆些┆ ┆雁┆终┆ ┆而┆

┆ ┆暖┆ ┆如┆地┆站┆有┆ ┆也┆ ┆我┆ ┆的┆有┆精┆ ┆也┆没┆ ┆你┆

┆ ┆这┆ ┆试┆方┆在┆逃┆ ┆会┆ ┆在┆ ┆清┆来┆准┆ ┆没┆有┆ ┆没┆

┆ ┆生┆ ┆探┆ ┆最┆避┆ ┆在┆ ┆这┆ ┆晨┆ ┆的┆ ┆有┆来┆ ┆有┆

┆ ┆之┆ ┆般┆ ┆不┆ ┆ ┆这┆ ┆里┆ ┆没┆ ┆杀┆ ┆来┆ ┆ ┆来┆