日誌架構

應用程式日誌可以幫助你瞭解應用程式內部發生的情況。日誌對於除錯問題和監控叢集活動特別有用。大多數現代應用程式都有某種日誌記錄機制。同樣,容器引擎也設計為支援日誌記錄。容器化應用程式最簡單、最常用的日誌記錄方法是寫入標準輸出和標準錯誤流。

然而,容器引擎或執行時提供的原生功能通常不足以構成完整的日誌解決方案。

例如,如果容器崩潰、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 直接訪問這些日誌流。你可以透過指定流名稱(StdoutStderr)並使用 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 文件

節點如何處理容器日誌

Node level logging

容器執行時處理並將任何生成到容器化應用程式 stdoutstderr 流的輸出進行重定向。不同的容器執行時以不同的方式實現這一點;然而,與 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 配置設定containerLogMaxWorkerscontainerLogMonitorInterval

當你在基本日誌記錄示例中執行 kubectl logs 時,節點上的 kubelet 處理請求並直接從日誌檔案讀取。kubelet 返回日誌檔案的內容。

系統元件日誌

系統元件有兩種型別:通常在容器中執行的元件,以及直接參與執行容器的元件。例如:

  • 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 引數來完成。

有關 etcd 及其日誌的詳細資訊,請檢視 etcd 文件。同樣,你可以使用 Kubernetes 的儲存機制將持久儲存對映到執行該元件的容器中。

叢集級日誌架構

雖然 Kubernetes 不提供叢集級日誌的原生解決方案,但你可以考慮以下幾種常見方法。

  • 使用在每個節點上執行的節點級日誌代理。
  • 在應用程式 Pod 中包含一個用於日誌記錄的專用 Sidecar 容器。
  • 直接從應用程式內部將日誌推送到後端。

使用節點日誌代理

Using a node level logging agent

你可以透過在每個節點上包含一個**節點級日誌代理**來實現叢集級日誌。日誌代理是一個專用工具,用於暴露日誌或將日誌推送到後端。通常,日誌代理是一個容器,可以訪問包含該節點上所有應用程式容器日誌檔案的目錄。

由於日誌代理必須在每個節點上執行,因此建議將代理作為 DaemonSet 執行。

節點級日誌記錄在每個節點上只建立一個代理,並且不需要對節點上執行的應用程式進行任何更改。

容器寫入 stdout 和 stderr,但沒有統一的格式。節點級代理收集這些日誌並將其轉發以進行聚合。

將 Sidecar 容器與日誌代理一起使用

你可以透過以下方式之一使用 Sidecar 容器:

  • Sidecar 容器將應用程式日誌流式傳輸到自己的 stdout
  • Sidecar 容器執行一個日誌代理,該代理配置為從應用程式容器中獲取日誌。

流式 Sidecar 容器

Sidecar container with a streaming container

透過讓 Sidecar 容器寫入其自己的 stdoutstderr 流,你可以利用已在每個節點上執行的 kubelet 和日誌代理。Sidecar 容器從檔案、套接字或 journald 讀取日誌。每個 Sidecar 容器將日誌列印到其自己的 stdoutstderr 流。

此方法允許你分離應用程式不同部分的多個日誌流,其中一些可能不支援寫入 stdoutstderr。重定向日誌的邏輯最少,因此不會造成很大的開銷。此外,由於 stdoutstderr 由 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 的小型容器。然而,直接使用 stdoutstderr 更簡單,並將輪轉和保留策略留給 kubelet。

帶有日誌代理的 Sidecar 容器

Sidecar container with a logging agent

如果節點級日誌代理對於你的情況不夠靈活,你可以建立一個帶有單獨日誌代理的 Sidecar 容器,該代理專門配置為與你的應用程式一起執行。

以下是兩個示例清單,你可以使用它們來實現帶有日誌代理的 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 的 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

直接從應用程式暴露日誌

Exposing logs directly from the application

直接從每個應用程式暴露或推送日誌的叢集日誌記錄超出 Kubernetes 的範圍。

下一步

上次修改時間:2024 年 10 月 17 日,太平洋標準時間晚上 11:21:doc: 記錄 PodLogsQuerySplitStreams 功能的使用 (387153787a)