日誌架構
應用程式日誌可以協助您瞭解應用程式內部發生的情況。日誌對於偵錯問題和監控叢集活動特別有用。大多數現代應用程式都有某種日誌記錄機制。同樣地,容器引擎的設計旨在支援日誌記錄。容器化應用程式最簡單且最常用的日誌記錄方法是寫入標準輸出和標準錯誤串流。
然而,容器引擎或執行期提供的原生功能通常不足以構成完整的日誌記錄解決方案。
例如,如果容器崩潰、Pod 被驅逐或節點死亡,您可能需要存取應用程式的日誌。
在叢集中,日誌應該具有獨立於節點、Pod 或容器的個別儲存空間和生命週期。此概念稱為 叢集層級日誌記錄。
叢集層級日誌記錄架構需要一個獨立的後端來儲存、分析和查詢日誌。Kubernetes 沒有為日誌資料提供原生儲存解決方案。相反地,有許多與 Kubernetes 整合的日誌記錄解決方案。以下章節說明如何在節點上處理和儲存日誌。
Pod 和容器日誌
Kubernetes 從正在執行的 Pod 中的每個容器擷取日誌。
此範例使用 Pod
的 Manifest,其中包含一個容器,該容器每秒將文字寫入標準輸出串流一次。
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
args: [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']
若要執行此 Pod,請使用以下命令
kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml
輸出為
pod/counter created
若要擷取日誌,請使用 kubectl logs
命令,如下所示
kubectl logs counter
輸出類似於
0: Fri Apr 1 11:42:23 UTC 2022
1: Fri Apr 1 11:42:24 UTC 2022
2: Fri Apr 1 11:42:25 UTC 2022
您可以使用 kubectl logs --previous
從容器先前執行個體中擷取日誌。如果您的 Pod 有多個容器,請指定您要存取哪個容器的日誌,方法是在命令中附加容器名稱和 -c
旗標,如下所示
kubectl logs counter -c count
容器日誌串流
Kubernetes v1.32 [alpha]
(預設啟用:false)作為 Alpha 功能,kubelet 可以將容器產生的兩個標準串流中的日誌分開:標準輸出 和 標準錯誤。若要使用此行為,您必須啟用 PodLogsQuerySplitStreams
功能閘道。啟用該功能閘道後,Kubernetes 1.32 允許透過 Pod API 直接存取這些日誌串流。您可以透過指定串流名稱 (Stdout
或 Stderr
其中之一),使用 stream
查詢字串來擷取特定串流。您必須具有讀取該 Pod 的 log
子資源的權限。
為了示範此功能,您可以建立一個 Pod,定期將文字寫入標準輸出和錯誤串流。
apiVersion: v1
kind: Pod
metadata:
name: counter-err
spec:
containers:
- name: count
image: busybox:1.28
args: [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date)"; echo "$i: err" >&2 ; i=$((i+1)); sleep 1; done']
若要執行此 Pod,請使用以下命令
kubectl apply -f https://k8s.io/examples/debug/counter-pod-err.yaml
若要僅擷取 stderr 日誌串流,您可以執行
kubectl get --raw "/api/v1/namespaces/default/pods/counter-err/log?stream=Stderr"
請參閱 kubectl logs
文件 以取得更多詳細資訊。
節點如何處理容器日誌
容器執行期處理並重新導向任何產生到容器化應用程式的 stdout
和 stderr
串流的輸出。不同的容器執行期以不同的方式實作此功能;但是,與 kubelet 的整合已標準化為CRI 日誌記錄格式。
預設情況下,如果容器重新啟動,kubelet 會保留一個終止的容器及其日誌。如果 Pod 從節點中驅逐,則所有對應的容器也會被驅逐,以及其日誌。
kubelet 透過 Kubernetes API 的特殊功能,使日誌可供用戶端使用。存取此功能的常用方法是執行 kubectl logs
。
日誌輪替
Kubernetes v1.21 [stable]
kubelet 負責輪替容器日誌和管理日誌記錄目錄結構。kubelet 將此資訊傳送至容器執行期 (使用 CRI),而執行期將容器日誌寫入給定的位置。
您可以使用 kubelet 組態檔,設定兩個 kubelet 組態設定,containerLogMaxSize
(預設值為 10Mi) 和 containerLogMaxFiles
(預設值為 5)。這些設定可讓您分別設定每個日誌檔的最大大小和每個容器允許的最大檔案數。
為了在工作負載產生大量日誌的叢集中執行有效率的日誌輪替,kubelet 也提供了一種機制來調整日誌輪替的方式,包括可以執行的並行日誌輪替次數以及監視和輪替日誌的間隔 (如果需要)。您可以使用 kubelet 組態檔,設定兩個 kubelet 組態設定,containerLogMaxWorkers
和 containerLogMonitorInterval
。
當您如基本日誌記錄範例中執行 kubectl logs
時,節點上的 kubelet 會處理請求並直接從日誌檔讀取。kubelet 會傳回日誌檔的內容。
注意
只有最新的日誌檔內容可透過 kubectl logs
取得。
例如,如果 Pod 寫入 40 MiB 的日誌,而 kubelet 在 10 MiB 後輪替日誌,則執行 kubectl logs
最多會傳回 10MiB 的資料。
系統元件日誌
系統元件有兩種型別:通常在容器中執行的元件,以及直接參與執行容器的元件。例如
- kubelet 和容器執行期不在容器中執行。kubelet 執行您的容器 (在 Pod 中分組在一起)
- Kubernetes 排程器、控制器管理員和 API 伺服器在 Pod 內執行 (通常是 靜態 Pod)。etcd 元件在控制平面中執行,最常見的情況下也作為靜態 Pod 執行。如果您的叢集使用 kube-proxy,您通常會將其作為
DaemonSet
執行。
日誌位置
kubelet 和容器執行期寫入日誌的方式取決於節點使用的作業系統
在使用 systemd 的 Linux 節點上,kubelet 和容器執行期預設會寫入 journald。您可以使用 journalctl
來讀取 systemd 日誌;例如:journalctl -u kubelet
。
如果 systemd 不存在,kubelet 和容器執行期會寫入 /var/log
目錄中的 .log
檔案。如果您希望將日誌寫入其他位置,您可以透過輔助工具 kube-log-runner
間接執行 kubelet,並使用該工具將 kubelet 日誌重新導向到您選擇的目錄。
預設情況下,kubelet 會指示您的容器執行期將日誌寫入 /var/log/pods
內的目錄中。
如需 kube-log-runner
的更多資訊,請參閱系統日誌。
預設情況下,kubelet 會將日誌寫入 C:\var\logs
目錄中的檔案 (請注意,這不是 C:\var\log
)。
雖然 C:\var\log
是這些日誌的 Kubernetes 預設位置,但一些叢集部署工具會設定 Windows 節點以記錄到 C:\var\log\kubelet
。
如果您希望將日誌寫入其他位置,您可以透過輔助工具 kube-log-runner
間接執行 kubelet,並使用該工具將 kubelet 日誌重新導向到您選擇的目錄。
但是,預設情況下,kubelet 會指示您的容器執行期將日誌寫入 C:\var\log\pods
目錄內。
如需 kube-log-runner
的更多資訊,請參閱系統日誌。
對於在 Pod 中執行的 Kubernetes 叢集元件,這些元件會寫入 /var/log
目錄內部的檔案,繞過預設的日誌記錄機制 (元件不會寫入 systemd 日誌)。您可以使用 Kubernetes 的儲存機制將持久儲存空間對應到執行元件的容器中。
Kubelet 允許將 Pod 日誌目錄從預設的 /var/log/pods
變更為自訂路徑。可以透過在 kubelet 的組態檔中設定 podLogsDir
參數來進行此調整。
注意
務必注意,預設位置 /var/log/pods
已使用很長一段時間,某些程序可能會隱含地假設此路徑。因此,變更此參數時必須謹慎並自行承擔風險。
另一個需要記住的注意事項是,kubelet 支援該位置與 /var
位於同一磁碟上。否則,如果日誌與 /var
位於不同的檔案系統上,則 kubelet 將不會追蹤該檔案系統的使用量,如果檔案系統已滿,則可能會導致問題。
如需 etcd 及其日誌的詳細資訊,請檢視 etcd 文件。同樣地,您可以使用 Kubernetes 的儲存機制將持久儲存空間對應到執行元件的容器中。
注意
如果您部署 Kubernetes 叢集元件 (例如排程器) 以記錄到從父節點共用的磁碟區,則需要考量並確保這些日誌已輪替。Kubernetes 不管理該日誌輪替。
您的作業系統可能會自動實作一些日誌輪替 - 例如,如果您將 /var/log
目錄共用到元件的靜態 Pod 中,則節點層級日誌輪替會將該目錄中的檔案視為與 Kubernetes 外部的任何元件寫入的檔案相同。
某些部署工具會考慮該日誌輪替並將其自動化;其他工具則將此責任留給您。
叢集層級日誌記錄架構
雖然 Kubernetes 沒有為叢集層級日誌記錄提供原生解決方案,但您可以考慮幾種常見的方法。以下是一些選項
- 使用在每個節點上執行的節點層級日誌記錄代理程式。
- 在應用程式 Pod 中包含專用的 Sidecar 容器以進行日誌記錄。
- 從應用程式內部將日誌直接推送至後端。
使用節點日誌記錄代理程式
您可以透過在每個節點上包含節點層級日誌記錄代理程式來實作叢集層級日誌記錄。日誌記錄代理程式是一種專用工具,可公開日誌或將日誌推送至後端。通常,日誌記錄代理程式是一個容器,可以存取包含該節點上所有應用程式容器的日誌檔的目錄。
由於日誌記錄代理程式必須在每個節點上執行,因此建議將代理程式作為 DaemonSet
執行。
節點層級日誌記錄每個節點僅建立一個代理程式,且不需要對節點上執行的應用程式進行任何變更。
容器寫入 stdout 和 stderr,但沒有一致的格式。節點層級代理程式收集這些日誌並轉發以進行彙總。
將 Sidecar 容器與日誌記錄代理程式搭配使用
您可以使用 Sidecar 容器,透過以下方式之一
- Sidecar 容器將應用程式日誌串流至其自己的
stdout
。 - Sidecar 容器執行日誌記錄代理程式,該代理程式設定為從應用程式容器中擷取日誌。
串流 Sidecar 容器
透過讓您的 Sidecar 容器寫入其自己的 stdout
和 stderr
串流,您可以利用 kubelet 和已在每個節點上執行的日誌記錄代理程式。Sidecar 容器從檔案、Socket 或 journald 讀取日誌。每個 Sidecar 容器都會將日誌列印到其自己的 stdout
或 stderr
串流。
此方法可讓您分隔來自應用程式不同部分的數個日誌串流,其中某些串流可能缺乏寫入 stdout
或 stderr
的支援。重新導向日誌背後的邏輯非常簡單,因此不會造成顯著的額外負荷。此外,由於 stdout
和 stderr
由 kubelet 處理,因此您可以使用內建工具,例如 kubectl logs
。
例如,一個 Pod 執行單一容器,而該容器使用兩種不同的格式寫入兩個不同的日誌檔。以下是 Pod 的 Manifest
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
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: {}
不建議將不同格式的日誌項目寫入相同的日誌串流,即使您設法將兩個元件都重新導向到容器的 stdout
串流。相反地,您可以建立兩個 Sidecar 容器。每個 Sidecar 容器都可以追蹤共用磁碟區中的特定日誌檔,然後將日誌重新導向到其自己的 stdout
串流。
以下是具有兩個 Sidecar 容器的 Pod 的 Manifest
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
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:1.28
args: [/bin/sh, -c, 'tail -n+1 -F /var/log/1.log']
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-2
image: busybox:1.28
args: [/bin/sh, -c, 'tail -n+1 -F /var/log/2.log']
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
現在,當您執行此 Pod 時,您可以透過執行以下命令來個別存取每個日誌串流
kubectl logs counter count-log-1
輸出類似於
0: Fri Apr 1 11:42:26 UTC 2022
1: Fri Apr 1 11:42:27 UTC 2022
2: Fri Apr 1 11:42:28 UTC 2022
...
kubectl logs counter count-log-2
輸出類似於
Fri Apr 1 11:42:29 UTC 2022 INFO 0
Fri Apr 1 11:42:30 UTC 2022 INFO 0
Fri Apr 1 11:42:31 UTC 2022 INFO 0
...
如果您在叢集中安裝了節點層級代理程式,則該代理程式會自動擷取這些日誌串流,而無需任何其他組態。如果您願意,您可以設定代理程式以根據來源容器剖析日誌行。
即使對於 CPU 和記憶體使用率低的 Pod (CPU 約為數毫核心,記憶體約為數 MB),將日誌寫入檔案,然後將其串流到 stdout
也可能會使您在節點上需要的儲存空間增加一倍。如果您的應用程式寫入單一檔案,建議您將 /dev/stdout
設定為目的地,而不是實作串流 Sidecar 容器方法。
Sidecar 容器也可以用於輪替應用程式本身無法輪替的日誌檔。此方法的一個範例是定期執行 logrotate
的小型容器。但是,更直接的方法是直接使用 stdout
和 stderr
,並將輪替和保留原則留給 kubelet。
具有日誌記錄代理程式的 Sidecar 容器
如果節點層級日誌記錄代理程式對於您的情況不夠彈性,您可以建立一個具有個別日誌記錄代理程式的 Sidecar 容器,您已將其特別設定為與您的應用程式一起執行。
注意
在 Sidecar 容器中使用日誌記錄代理程式可能會導致顯著的資源消耗。此外,您將無法使用kubectl logs
存取這些日誌,因為它們不受 kubelet 控制。以下是兩個範例 Manifest,您可以使用它們來實作具有日誌記錄代理程式的 Sidecar 容器。第一個 Manifest 包含 ConfigMap
以組態 fluentd。
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>
注意
在範例組態中,您可以使用任何日誌記錄代理程式取代 fluentd,從應用程式容器內部的任何來源讀取。第二個 Manifest 描述一個具有執行 fluentd 的 Sidecar 容器的 Pod。Pod 掛載一個磁碟區,fluentd 可以從該磁碟區中擷取其組態資料。
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox:1.28
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: registry.k8s.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
直接從應用程式公開日誌
從每個應用程式直接公開或推送日誌的叢集日誌記錄超出 Kubernetes 的範圍。