返回文章列表

Docker Kubernetes 日誌管理最佳實務

本文探討 Docker 和 Kubernetes 環境下的日誌管理策略,涵蓋 Docker 日誌驅動程式、Fluentd 整合、Kubernetes 元件日誌收集及 Logrotate 的使用,並提供最佳實務與組態範例,協助工程師有效管理容器化應用程式的日誌。

容器技術 DevOps

在現代容器化應用程式中,有效管理日誌對於應用程式監控、故障排除和效能分析至關重要。本文將探討如何利用 Docker 和 Kubernetes 的日誌機制,並結合 Fluentd 等工具,構建完善的日誌管理系統。從 Docker 日誌驅動程式的組態到 Kubernetes 元件日誌的收集,我們將逐步解析每個環節,並提供實務操作。同時,我們還會探討 Logrotate 的使用,以解決日誌輪替和儲存空間管理的問題,並討論 Kubernetes 內建的 klog 日誌框架及其演進過程。最後,我們將介紹如何使用 kubectl 工具來管理 Kubernetes 叢集中的日誌。

使用 Docker 與 Kubernetes 進行日誌管理

8.2 使用 Docker 日誌驅動程式

Docker 提供了一種控制日誌的方法。預設情況下,Docker 使用 JSON 日誌驅動程式,將日誌寫入 stdout 和 stderr(即我們的控制檯,除非您在環境中覆寫了這些輸出的路由)。有兩種方法可以控制日誌驅動程式:一種是在 Docker run 命令中新增額外的引數,另一種是修改 Docker 組態。命令列方法的區別在於,您可以為特定的 Docker 容器使用替代組態。

8.2.1 透過命令列使用 Docker 日誌驅動程式

首先,我們將使用命令列方法。這是調整日誌驅動程式行為的最不具侵入性的方法,因此,嘗試組態控制涉及最少的中斷性變更。

我們將繼續在主機電腦上執行 Fluentd,以接收和輸出日誌事件。首先,我們將從 Linux 虛擬機器(VM)中執行 Hello-World Docker 映像檔。如果您的主機作業系統是 Linux,這可能看起來有點反直覺,但這種方法具有以下好處:

  • 明確區分網路層,因為虛擬化層將提供一個獨立的網路層,此外還有來自 Docker 層的網路抽象。
  • 減少所需的虛擬機器數量和虛擬化所帶來的資源開銷。
  • 無論主機作業系統如何,結果都將相同。如果您的主機是 Windows,這尤其有益,因為它有助於強調 Fluentd 是平台無關的。

8.2.2 快速檢查網路連線

正確設定網路組態對於使用 Docker、Kubernetes 和虛擬機器至關重要。這意味著始終值得進行快速和簡單的檢查,以確保網路連線按預期工作,例如使用 curl 或 Postman 將 HTTP 日誌事件傳送到 Fluentd。為了幫助實作這一點並使用 Fluentd 日誌驅動程式,我們準備了一個簡單的 Fluentd 組態,將接收到的任何內容傳送到 stdout。

fluentd -c Chapter8/Fluentd/forwardstdout.conf

一旦 Fluentd 在 Linux 環境中執行,我們就可以執行我們在第 2 章中使用的“Hello World”測試的變體。在以下組態和命令中,我們需要將 w.x.y.z 替換為主機電腦的 IP,就像 Linux 客戶機所看到的那樣。您可以使用命令 ipconfig 在 Windows 上取得機器的 IP,在 Linux 主機上使用 ip addr showifconfig 也可能有效,但已被棄用)。

curl -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://w.x.y.z:18080/test

這應該會導致 JSON 詳細資訊 {"foo":"bar"} 顯示在執行 Fluentd 的主機的控制檯上。

8.2.3 執行 Docker 命令列

設定並檢查我們的佈署(尤其是網路)後,我們可以繼續使用 Docker 守護程式。我們將檢索傳統的“Hello World”映像檔,而不是構建自己的 Docker 映像檔。hello-world Docker 映像檔很簡單,當人們熟悉 Docker 時,它是一個好的起點。映像檔的詳細資訊可在 https://hub.docker.com/_/hello-world 上找到。

docker pull hello-world:latest

我們尚未對 Docker 組態進行任何更改,這意味著當我們要求 Docker 守護程式執行我們的映像檔時,我們將看到標準的 Docker 日誌驅動程式行為。雖然 Docker 日誌的位置可能會有所不同,但通常我們應該在 /var/lib/docker 資料夾中找到它們,在那裡我們將看到一個名為 containers 的資料夾。

docker run hello-world

如果我們現在重新整理對 /var/lib/docker/containers 資料夾的檢視,該資料夾將有一個新條目。在新容器的資料夾結構中,我們將看到一個具有長名稱的日誌檔(Docker 映像檔例項;例如 b361e69a1...)。導航到容器的資料夾中,我們將看到該 Docker 例項的資源,包括一個名為 local-logs 的資料夾。最後,導航到 local-logs 資料夾中,我們可以看到容器的日誌檔名為 container.log

程式碼解析:

上述命令和組態展示瞭如何使用 Docker 和 Fluentd 來管理和檢視日誌。首先,我們使用 docker pull 命令下載 hello-world 映像檔。然後,我們執行 docker run 命令來執行該映像檔。在執行過程中,Docker 會將日誌寫入預設的位置。我們可以透過檢查 /var/lib/docker/containers 資料夾來檢視這些日誌。


#### 程式碼解密:
1. `docker pull hello-world:latest`:下載最新版本的 `hello-world` Docker 映像檔。
2. `docker run hello-world`:執行 `hello-world` Docker 容器。
3. `/var/lib/docker/containers`:Docker 日誌的預設儲存位置。

為了使事情更實用,我們希望組態 Docker 以使用更可消費的格式進行日誌記錄。我們可以覆寫預設設定,以便 Docker 使用 Fluentd。透過這種方式,我們可以更好地管理和分析日誌。

圖表說明:

此圖示展示了作業系統、虛擬化和容器化的不同層,以及它們如何協同工作以確保主機環境不會被Docker幹擾。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Docker Kubernetes 日誌管理最佳實務

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

使用 Docker 日誌驅動程式

Docker 提供了一種機制,可以將容器日誌傳送到外部日誌收集系統,例如 Fluentd。這是透過告訴 Docker 守護程式使用替代的日誌驅動程式來實作的,使用引數 --log-driver=fluentd。由於 Fluentd 驅動程式已經被封裝在 Docker 的佈署中,因此我們不需要做任何額外的操作。我們還需要告訴驅動程式在哪裡找到我們的 Fluentd 節點以接收日誌事件。這些組態選項是透過引數 --log-opt 後面跟著一個名稱-值對(以等於號(=)字元分隔)來提供的。在我們的例子中,我們需要提供主機的 Fluentd 的地址(就像之前的 curl 命令一樣)。由於 Docker 日誌驅動程式可以使用 forward 外掛(並且可以從 msgpack 中受益,提供壓縮),因此我們需要確保提供網路地址,包括該埠。這導致執行 hello-world 的命令如下:

docker run --log-driver=fluentd --log-opt fluentd-address=w.x.y.z:28080 hello-world

內容解密:

  • --log-driver=fluentd 指定使用 Fluentd 作為日誌驅動程式。
  • --log-opt fluentd-address=w.x.y.z:28080 指定 Fluentd 的地址和埠。
  • hello-world 是要執行的 Docker 映像。

執行該陳述式的結果將是在 Fluentd 控制檯上看到來自 Docker 映像的日誌事件。如果 Docker 命令傳回錯誤訊息,例如:

docker: Error response from daemon: failed to initialize logging driver: dial tcp w.x.y.z:28080: connect: connection refused.

則表示網路或 Fluentd 存在問題(例如,它沒有繫結到正確的網路)。還需要注意 Docker 映像和目標 Fluentd 節點的啟動順序。當轉移到使用 Kubernetes 進行容器協調時,這一點尤為重要,因為它管理著 pod 的啟動順序。在出現此類別問題時,我們建議檢查 Docker 組態值中的網路埠,以確保允許容器外的網路流量。如果發生任何埠號對映,則這是正常的。

切換到透過組態檔案進行驅動程式組態

透過引數化的解決方案已經得到驗證,我們可以以更易讀的方式推進組態,並新增其他相關選項。鑒於所有可能的組態選項,使用命令列進行高階組態將使維護任務變得困難。預設情況下,更改 Docker 守護程式組態檔案將影響所有正在執行的 Docker 映像。Docker 命令列還允許我們使用引數 --config 後跟檔名來指向組態檔案,以進行替代組態。

Docker 守護程式將其組態(包括日誌驅動程式組態)儲存在名為 daemon.json 的檔案中。該檔案的預設位置在 Linux 上是 /etc/docker/。如果您在 Windows 上使用 Docker 例項(而不是我們選擇採用的間接方法),則位置是 ProgramData\docker\config\(ProgramData 通常位於 C 盤根目錄下)。如果 Docker 安裝程式完全執行在預設值上,則該檔案可能不存在。

在守護程式組態檔案中,我們顯然希望包括日誌驅動程式型別和與我們的 Fluentd 例項的連線的設定。為此,我們在 JSON 檔案中包含命令列引數 "log-driver": "fluentd" 的組態版本。在命令列中,我們還提供了 fluentd-address 屬性。在 fluentd-address 的情況下,我們可以將地址提供為 tcp://w.x.y.z:28080 或作為對相關 socket 檔案的明確路徑參照(例如,unix:///usr/var/fluentd/fluent.sock)。

除了地址之外,我們還應該引入與日誌驅動程式直接相關的幾個其他引數,以及與日誌記錄相關的其他常規引數。我們包含的常規設定有:

  • raw-logs:應設定為 truefalse。如果指定為 false,則會應用完整的 ANSI 時間戳(例如,YYYY-MM-DD HH:MM:SS),並且會關閉對日誌文字的顏色編碼。
  • log-driver:如命令列示例中所示,用於設定日誌驅動程式。
  • log-level:要應用於 Docker 守護程式的日誌過濾閾值。接受的級別有 debuginfowarnerrorfatal,預設為 info

在組態檔案中,我們可以開始一個名為 log-opts 的內部屬性組;這些日誌記錄特定選項包括:

  • env:我們可以要求驅動程式捕捉並包含特定的環境變數。這是透過定義逗號分隔的列表來完成的。就我們的目的而言,我們可以使用 "os, customer"。這假設某些東西已經設定了這些值。也可以透過使用屬性 env-regex 來定義正規表示式版本。
  • labels:這與 env 非常相似,因為可以指定 Docker 後設資料名稱-值對的列表,或者可以透過 labels-regex 提供正規表示式。
  • fluentd-retry-wait:每次連線失敗時,在重試之前會應用一個等待期。該值需要包括持續時間型別(例如,s 表示秒,h 表示小時)。

內容解密:

  • 組態檔案使我們能夠以更結構化的方式管理日誌驅動程式的組態。
  • 可以根據需要新增或修改組態選項,以滿足特定的需求。
  • 正確組態日誌驅動程式對於收集和分析容器日誌至關重要。

Kubernetes 元件日誌記錄與 Fluentd 的應用

在前面的章節中,我們探討瞭如何使用 Docker 和 Fluentd 來管理日誌。現在,我們將進一步討論 Kubernetes 元件日誌記錄以及 Fluentd 在其中的應用。

Kubernetes 的日誌記錄挑戰

Kubernetes 的設計使其具有高度的可擴充套件性和可插拔性,這使得其生態系統變得相當複雜。雖然 Docker 是目前最主流的容器技術,但 Kubernetes 的 API 模型允許我們使用其他容器技術,如 containerd 和 cri-o。這些技術都隸屬於雲原生計算基金會(CNCF)的治理之下。

Open Container Initiative(OCI)也在 CNCF 的治理之下,它有助於抽象化容器實作與 Kubernetes 容器協調之間的互動。這種複雜性對我們使用 Fluentd 來收集和管理日誌有什麼影響呢?

組態容器日誌記錄

我們已經知道如何組態 Docker 將事件傳播到 stdout 和 stderr。那麼,其他容器技術是否也支援這種能力呢?雖然不是所有的容器技術都像 Docker 一樣成熟,但大多數主流的容器技術都提供了類別似的日誌記錄功能。

使用 Fluentd 收集 Kubernetes 日誌

Fluentd 可以與 Kubernetes 無縫整合,收集來自各個元件的日誌,包括容器、節點和叢集層級的日誌。Fluentd 的強大之處在於其靈活性,可以根據不同的需求進行組態。

組態 Fluentd 日誌驅動程式

在 Docker 中,我們可以使用 Fluentd 日誌驅動程式將日誌傳送到 Fluentd 伺服器。下面是一個範例組態:

{
  "log-driver": "fluentd",
  "log-level": "debug",
  "raw-logs": true,
  "log-opts": {
    "env": "os,customer",
    "labels": "production_status,dev",
    "fluentd-retry-wait": "1s",
    "fluentd-max-retries": "600",
    "fluentd-sub-second-precision": "false",
    "tag": "{{.ID}}-{{.ImageID}}",
    "fluentd-address": "w.x.y.z:28080"
  }
}

內容解密:

  • "log-driver": "fluentd" 指定使用 Fluentd 日誌驅動程式。
  • "log-level": "debug" 設定日誌層級為 debug。
  • "raw-logs": true 啟用原始日誌模式。
  • "log-opts" 中的設定項用於組態 Fluentd 日誌驅動程式的行為。
  • "env""labels" 指定要包含在日誌中的環境變數和標籤。
  • "fluentd-retry-wait""fluentd-max-retries" 設定重試等待時間和最大重試次數。
  • "fluentd-sub-second-precision" 設定是否使用亞秒級精確度。
  • "tag" 指定日誌事件的標籤。
  • "fluentd-address" 指定 Fluentd 伺服器的位址。

在 Kubernetes 中佈署 Fluentd

要在 Kubernetes 中佈署 Fluentd,我們可以建立一個 DaemonSet 資源,該資源會在每個節點上執行一個 Fluentd Pod。下面是一個範例 YAML 檔案:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
spec:
  selector:
    matchLabels:
      name: fluentd
  template:
    metadata:
      labels:
        name: fluentd
    spec:
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:v1
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

內容解密:

  • apiVersionkind 指定了資源的 API 版本和型別。
  • metadata 中的 name 指定了 DaemonSet 的名稱。
  • spec 中的 selectortemplate 指定了 DaemonSet 的選擇器和範本。
  • containers 中的 nameimage 指定了容器的名稱和映像檔。
  • volumeMounts 中的 namemountPath 指定了要掛載的磁碟區和掛載路徑。
  • volumes 中的 namehostPath 指定了磁碟區的名稱和主機路徑。

使用 Docker 和 Kubernetes 管理日誌記錄

在 Kubernetes 環境中,日誌記錄是一項重要的任務。許多應用程式直接使用 Kubernetes 內部提供的日誌框架 klog (https://github.com/kubernetes/klog)。當佈署在支援 journald 的環境中時,klog 會將日誌寫入 journald,否則會寫入預設的檔案位置。

Kubernetes 元件與結構化日誌

目前,Kubernetes 元件採用結構化日誌的過程仍在演進中,並非所有元件都已採用結構化日誌。未來可能會遇到未採用結構化日誌的系統元件或擴充套件元件,因此建議主動採用第 7 章中介紹的日誌和佈署模式(例如 Fluentd 作為 sidecar 模式,或將日誌處理嵌入應用程式中),而非嘗試從 Kubernetes 中收集日誌。

Kubernetes 預設日誌保留和日誌輪替

當容器將日誌傳送到 Kubernetes 時,Kubernetes 會為每個容器例項將日誌條目寫入日誌檔案。要管理日誌大小和輪替,需要建立日誌輪替工具,以控制日誌檔案數量和輪替頻率。

Kubernetes 沒有內建的日誌輪替工具,因此需要在佈署 Kubernetes 工作節點時自行解決日誌輪替問題。如果使用 Kubernetes 提供的指令碼(kube-up.sh, http://mng.bz/M25n)設定工作節點,則會佈署開源工具 logrotate (https://github.com/logrotate/logrotate)。logrotate 可以設定保留特定數量的檔案。某些 Linux 發行版預設已佈署 logrotate,因此只需進行額外組態。

logrotate 的設定可能會因 Linux 發行版的不同而有所不同。某些發行版使用 systemd,並將 logrotate 納入其中。如果 logrotate 未預設佈署,可以透過 Linux 發行版的套件管理器進行獨立安裝。

然而,logrotate 並非跨平台解決方案,因此在 Windows 上執行 Kubernetes 需要其他方法來實作日誌輪替,這是一個挑戰。

Klog 的演進

Klog 源自 Google 的 C++ 函式庫(https://github.com/google/glog)。由於 Kubernetes 是使用 Go 語言實作,因此無法使用 C++ 函式庫。後來開發了 Go 語言的實作版本(https://github.com/golang/glog)。然而,Kubernetes 開發者認為 glog 在容器化方面存在一些挑戰,因此 fork 了該程式碼函式庫,並演變成 klog。兩者的 API 基本保持一致。在所有情況下,日誌機制都針對最佳效能進行了最佳化,因此插入和組態日誌主要取決於應用程式使用的命令列選項,而非設定檔。

使用 kubectl 管理日誌

kubectl 是與 Kubernetes 互動的主要命令列工具。當 Kubernetes 知道日誌寫入的位置時,可以使用 kubectl 執行各種任務,例如跟蹤一個或多個日誌檔案、將日誌轉發到不同的連線埠,以及支援日常的日誌檔案活動。有關 kubectl 日誌命令的詳細資訊,請參閱 kubectl 命令參考(http://mng.bz/aDJB)。