日誌架構
應用程式日誌可以幫助你瞭解應用程式內部發生的情況。日誌對於除錯問題和監控叢集活動特別有用。大多數現代應用程式都有某種日誌記錄機制。同樣,容器引擎也設計為支援日誌記錄。容器化應用程式最簡單、最常用的日誌記錄方法是寫入標準輸出和標準錯誤流。
然而,容器引擎或執行時提供的原生功能通常不足以構成完整的日誌解決方案。
例如,如果容器崩潰、Pod 被逐出或節點宕機,你可能希望訪問應用程式的日誌。
在叢集中,日誌應該有獨立於節點、Pod 或容器的單獨儲存和生命週期。此概念稱為叢集級日誌。
叢集級日誌架構需要一個單獨的後端來儲存、分析和查詢日誌。Kubernetes 不提供日誌資料的原生儲存解決方案。相反,有許多日誌解決方案與 Kubernetes 整合。以下各節描述瞭如何在節點上處理和儲存日誌。
Pod 和容器日誌
Kubernetes 從正在執行的 Pod 中的每個容器捕獲日誌。
此示例使用一個 Pod
的清單,其中包含一個容器,該容器每秒向標準輸出流寫入一次文字。
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]
(預設停用)作為一項 Alpha 特性,kubelet 可以將容器生成的兩個標準流(標準輸出和標準錯誤)中的日誌分離出來。要使用此行為,你必須啟用 PodLogsQuerySplitStreams
特性門控。啟用該特性門控後,Kubernetes 1.34 允許透過 Pod API 直接訪問這些日誌流。你可以透過指定流名稱(Stdout
或 Stderr
)並使用 stream
查詢字串來獲取特定流。你必須具有讀取該 Pod 的 log
子資源的許可權。
為了演示此特性,你可以建立一個 Pod,該 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
最多返回 10 MiB 的資料。
系統元件日誌
系統元件有兩種型別:通常在容器中執行的元件,以及直接參與執行容器的元件。例如:
- 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 journal)。你可以使用 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 容器從檔案、套接字或 journald 讀取日誌。每個 Sidecar 容器將日誌列印到其自己的 stdout
或 stderr
流。
此方法允許你分離應用程式不同部分的多個日誌流,其中一些可能不支援寫入 stdout
或 stderr
。重定向日誌的邏輯最少,因此不會造成很大的開銷。此外,由於 stdout
和 stderr
由 kubelet 處理,因此你可以使用 kubectl logs
等內建工具。
例如,一個 Pod 執行一個單獨的容器,並且該容器使用兩種不同的格式寫入兩個不同的日誌檔案。以下是 Pod 的清單:
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 的清單:
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 為幾毫核,記憶體為幾兆位元組),將日誌寫入檔案然後流式傳輸到 stdout
也會使節點上所需的儲存空間加倍。如果你的應用程式寫入單個檔案,建議將 /dev/stdout
設定為目標,而不是採用流式 Sidecar 容器方法。
Sidecar 容器還可以用於輪轉應用程式本身無法輪轉的日誌檔案。這種方法的一個例子是一個定期執行 logrotate
的小型容器。然而,直接使用 stdout
和 stderr
更簡單,並將輪轉和保留策略留給 kubelet。
帶有日誌代理的 Sidecar 容器
如果節點級日誌代理對於你的情況不夠靈活,你可以建立一個帶有單獨日誌代理的 Sidecar 容器,該代理專門配置為與你的應用程式一起執行。
注意
在 Sidecar 容器中使用日誌代理會導致大量的資源消耗。此外,你將無法使用kubectl logs
訪問這些日誌,因為它們不受 kubelet 控制。以下是兩個示例清單,你可以使用它們來實現帶有日誌代理的 Sidecar 容器。第一個清單包含一個用於配置 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>
注意
在示例配置中,你可以用任何日誌代理替換 fluentd,從應用程式容器內部的任何源讀取日誌。第二個清單描述了一個包含執行 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 的範圍。