在 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