K8S的日誌採集,沒有我們想的那麼簡單!

網路通訊頻道發表於2023-02-21

相比傳統的主機日誌採集,在Kubernetes叢集中,採集容器日誌有一些差異,使用方式上也有所區別。因此我們羅列了一些常規的部署和使用方式以供參考。

1.從主機到容器

在傳統的使用虛擬機器/雲主機/物理機的時代,業務程式部署在固定的節點上,業務日誌直接輸出到宿主機上,運維只需要手動或者使用自動化工具把日誌採集Agent部署在節點上,加一下Agent的配置,就可以開始採集日誌了。而在Kubernetes環境中,就沒這麼簡單了:

「動態遷移」:在Kubernetes叢集中經常存在Pod主動或者被動的遷移,頻繁的銷燬、建立,我們無法和傳統的方式一樣人為的給每個服務下發日誌採集配置。

「日誌儲存方式多樣性」:容器的日誌儲存方式有很多不同的型別,例如stdout、hostPath、emptyDir、pv等。

「Kubernetes元資訊」:由於日誌資料採集後會被集中儲存,所以查詢日誌時,需要根據namespace、pod、container、node,甚至包括容器的環境變數、label等維度來檢索、過濾,此時要求Agent感知並預設在日誌裡注入這些元資訊。

以上都是有別於傳統日誌採集配置方式的需求和痛點,究其原因,還是因為傳統的方式脫離了Kubernetes,無法感知Kubernetes,無法和Kubernetes整合。

2. 在Kubernetes下的日誌形態

為了採集容器日誌,我們先來看一下市面上一般都有哪些解決方案。

2.1 採集的日誌型別

首先,需要提及的是,在雲原生的12要素裡,推薦業務容器將日誌輸出到stdout中,而不是採用列印日誌檔案的方式。當然,實際情況是,我們很難這麼做,原因大概有:

需要業務方修改日誌配置,比較難以推廣

有些複雜的業務對日誌檔案有分類,比如審計日誌、訪問日誌等,一般會輸出為獨立的日誌檔案,日誌採集需根據不同的檔案分類進行不同的處理

所以正常情況下,我們需要同時採集:

「標準輸出stdout」

「日誌檔案」

2.2 Agent部署方式

採集容器日誌,Agent有兩種部署方式:

「DaemonSet」:每個節點部署一個Agent

「Sidecar」:每個Pod增加一個Sidecar容器,執行日誌Agent

兩種部署方式的優劣都顯而易見:

資源佔用:DaemonSet每個節點上一個,而Sidecar每個Pod裡一個,容器化形態下,往往一個Node上可能會跑很多的Pod,此時DaemonSet的方式遠小於Sidecar,而且節點上Pod個數越多越明顯

侵入性:Sidecar的方式,Agent需要注入到業務Pod中,不管是否有平臺封裝這一過程,還是採用Kubernetes webhook的方式預設注入,仍然改變了原本的部署方式

穩定性:日誌採集在大部分的情況下,需要保障的是穩定性,最重要的是不能影響業務,如果採用Sidecar的方式,在Agent發生異常或者oom等情況,很容易對業務容器造成影響。另外,Agent比較多的時候,在連線數等方面會對下游服務比如Kafka造成一定的隱患。

隔離性:DaemonSet情況下,節點所有的日誌都共用同一個Agent,而Sidecar方式,只會採集同一個Pod內的業務日誌,此時Sidecar的隔離性理論上會好一些

效能:Sidecar由於只會採集該Pod裡的日誌,壓力相對較小,極端情況下,達到Agent的效能瓶頸比DaemonSet方式機率也會小很多

❝Tip:正常情況下,優先使用DaemonSet的方式採集日誌,如果單個Pod日誌量特別大,超過一般Agent傳送吞吐量,可以單獨對該Pod使用Sidecar的方式採集日誌。❞

2.3 採集方式

DaemonSet + Stdout

如果使用容器執行時的是docker,正常情況下我們可以在節點的docker路徑中找到容器的stdout的日誌,預設為/var/lib/docker/containers/{containerId}/{containerId}-json.log。在Kubernetes 1.14版本之前,kubelet會在/var/log/pods/<podUID>/<containerName>/<num>.log建立一個軟連結到stdout檔案中。類似如下所示:

在Kubernetes 1.14版本之後,改成了/var/log/pods/<namespace>_<pod_name>_<pod_id>/<container_name>/<num>.log的形式。

所以,對於Agent採集標準輸出日誌來說,也就是採集節點上的這些日誌檔案。一種簡單粗暴的採集方式是,使用DaemonSet部署日誌Agent,掛載/var/log/pods目錄,Agent的配置檔案使用類似/var/log/pod.log去通配日誌檔案,採集節點上所有的容器標準輸出。

❝「Tip:這也是我最初的解決方案,想到這就臉紅!」❞

但是這樣的侷限在於:

無法注入更多元資訊比如一些pod的label/env等,特別是在k8s1.14版本之前,甚至無法在採集的path裡獲取到namespace/pod等資訊

很難針對單個服務配置特殊的配置,比如某個檔案需要使用特殊的多行日誌採集,需要配置適合服務自身的日誌格式切分等

會採集很多不必要的容器日誌,造成採集、傳輸、儲存壓力

當然現在的一些日誌Agent比如Filebeat/Fluentd都針對性的做了支援,比如可以將namespace/pod等資訊注入日誌中,但仍然沒有解決大部分的問題。

所以,這種方式只適合簡單的業務場景,後續也難以滿足其他更多的日誌需求。

DaemonSet + 日誌檔案

如果Pod裡不僅僅是輸出stdout,還包括日誌檔案,就需要考慮到掛載日誌檔案到節點上,同時採用DaemonSet部署的Agent也需要掛載相同的目錄,否則採用容器化部署的Agent無法檢視到相應的檔案,更無法採集。業務Pod掛載日誌路徑的方式有以下幾種:

「(1) emtpyDir」

emtpyDir的生命週期跟隨Pod,Pod銷燬後其中儲存的日誌也會消失。

優點:使用簡單,不同Pod都使用自己的emtpyDir,有一定的隔離性。

缺點:日誌如果採集不及時,在Pod消耗後,存在丟失的可能性。

使用emptyDir掛載的日誌檔案,一般在節點的路徑如下:/var/lib/kubelet/pods/${pod.UID}/volumes/kubernetes.io~empty-dir/${volumeName}

「(2) hostPath」

生命週期和Pod無關,Pod遷移或者銷燬,日誌檔案還保留在現有磁碟上。

優點:生命週期和Pod無關,即使Pod銷燬,日誌檔案依然在節點磁碟上,假設Agent沒有采集日誌,仍然可以找到日誌檔案

缺點:預設無隔離性,需要控制掛載的日誌路徑;另外,Pod遷移節點後,殘留的日誌檔案長期積累容易佔據磁碟,同時日誌佔據的磁碟無法控制使用的配額

為了解決隔離性,避免多個Pod列印日誌到相同的路徑和檔案中,我們需要使用 subPathExpr 欄位從 Downward API 環境變數構造 subPath 目錄名。該 VolumeSubpathEnvExpansion 功能從 Kubernetes1.15 開始預設開啟,在1.17 GA。

「(3) Pv」

Pv的訪問模式包括:

ReadWriteOnce(RWO):讀寫許可權,並且只能被單個Node掛載。

ReadOnlyMany(ROX):只讀許可權,允許被多個Node掛載。

ReadWriteMany(RWX):讀寫許可權,允許被多個Node掛載。

對於大部分的業務來說,都是Deployment無狀態部署,需要掛載同一個Pv共享;對於一些中介軟體等有狀態服務,一般會使用StatefulSet部署,每個Pod會使用獨立的Pv。

優點:儲存日誌不容易丟失;

缺點:有一定的使用和運維複雜度;多個Pod共享同一個Pv時存在隔離性問題;很多的日誌Agent對採集雲盤上的日誌檔案支援不夠成熟,可能存在一些隱患;

雖然同樣可以在Node上找到使用Pv掛載的對應日誌檔案,但是Pv根據不同的底層實現,在Node上的路徑會有一定的區別。目前市面上大部分日誌Agent均對這些掛載方式沒有感知,所以你能做的和上面使用stdout的方式類似,也就是簡單粗暴的讓Agent將路徑都掛載,使用通配的方式採集所有的日誌,使用上的侷限和stdout的方式同樣一致。

另外,鑑於一些Agent對採集docker stdout有一定的支援,所以還存在一些使用上變種,比如利用webhook注入一個sidecar,讀取Pod裡的日誌檔案,轉換成sidecar的stdout,然後採集sidecar的stdout日誌,這裡不再詳述。

來自 “ 木訥大叔愛運維 ”, 原文作者:Loggie;原文連結:https://mp.weixin.qq.com/s/PQSXkne0GenoF7Lb7sloyQ,如有侵權,請聯絡管理員刪除。

相關文章