k8s容器日志收集方案
一. 在Node上部署logging agent
將日志文件轉發到后端存儲里保存起來
- 不難看到,這里的核心就在於
logging agent
,它一般都會以DaemonSet
的方式運行在節點上,然后將宿主機上的容器日志目錄掛載進去,最后由logging-agent
把日志轉發出去。 - 舉個例子,我們可以通過
Fluentd
項目作為宿主機上的logging-agent
,然后把日志轉發到遠端的ElasticSearch
里保存起來供將來進行檢索。具體的操作過程官網很多kubernetes
的部署里,會自動為你啟用logrotate
,在日志文件超過10MB的時候自動對日志文件進行roate操作。 - 可以看到在Node上部署logging agent最大的優點,在於只需要部署一個agent,並且不會對應用和pod有任何入侵性。所以這個方案在社區里是最常用的一種。
- 但是這種方案的不足之處就在於,它要求應用輸出的日志,都必須是直接輸出到容器的stdout和stderr里
二. 對特殊情況的一個處理
- 當容器的日志只能輸出到某些文件里的時候,我們可以通過一個sidecar容器把這些日志文件重新輸出到sidecar的stdout和stderr
-
現在我的應用pod只有一個容器,它會把日志輸出到容器里的/var/log/1.log和2.log這兩個文件里。這個pod的YAML文件如下所示:
apiVersion: v1 kind: Pod metadata: name: counter spec: containers: - name: count image: busybox 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: {}
-
在這種情況下,你用
kubectl logs
命令是看不到應用的任何日志的。這時我們就可以為這個pod添加兩個sidecar容器,分別將上述兩個日志文件里的內容重新以stdout和stderr
的方式輸出來,這個YAML文件的寫法如下:apiVersion: v1 kind: Pod metadata: name: counter spec: containers: - name: count image: busybox 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 args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log'] volumeMounts: - name: varlog mountPath: /var/log - name: count-log-2 image: busybox args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log'] volumeMounts: - name: varlog mountPath: /var/log volumes: - name: varlog emptyDir: {}
-
由於sidecar跟主容器之間是共享Volume的,所以這里的sidecar方案的額外性能損耗並不高,也就多占用一點cpu和內存。但是,這時候宿主機上實際會存在兩份相同的日志文件:一份是應用自己寫入的;另一份是sidecar的stdout和stderr對應的json文件。這對磁盤是很大的浪費。所以說,除非萬不得已或者應用容器完全不能修改,否則還是建議你直接使用方案一,或者直接使用方案三。
三. 送過sidecar容器直接把日志文件發送到遠程存儲
- 相當於把方案一里的logging agent,放在了應用pod里。
-
在這種方案里,你的應用還可以直接把日志輸出到固定的文件里而不是stdout,你的logging-agent還可以使用fluentd,后端存儲還可以是ElasticSearch。只不過,fluentd的輸入源變成了應用的日志文件。一般來說,我們會把fluentd的輸入源配置保存在一個ConfigMap里,如下所以:
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> 復制代碼
-
我們在應用pod的定義里,就可以聲明一個Fluentd容器作為sidecar,專門負責將應用生成的1.log和2.log轉發到ElasticSearch中,這個配置,如下所示:
apiVersion: v1 kind: Pod metadata: name: counter spec: containers: - name: count image: busybox 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: k8s.gcr.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
-
如上所示,這個Fluentd容器使用的輸入源,就是通過引用我們前面寫的ConfigMap來指定的。這里用到了Volume來把ConfigMap掛在到Pod里。
-
這種方案部署簡單,並且對宿主機非常友好,但是這個sidecar容器很可能會消耗較多的資源,甚至拖垮應用容器。由於日志沒有輸出到stdout上,所以你通過
kubectl logs
是看不到日志信息的。
總結
以上,就是k8s最常用的三種收集日志的手段了,綜合對比以上方案,比較建議你將應用日志輸出到stdout和stderr,然后通過在宿主機上部署logging-agent的方式集中處理日志、這種方案不但簡單,而且kubectl logs依然可以使用,也是官方推薦的一種。