本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
使用 OpenTelemetry 增強 Kubernetes 容器執行時可觀察性
當談到雲原生領域的可觀測性時,可能每個人都會在對話的某個時刻提到 OpenTelemetry (OTEL)。這很好,因為社群需要依賴標準來推動所有叢集元件朝著同一方向發展。OpenTelemetry 使我們能夠將日誌、指標、追蹤和其他上下文資訊(稱為 baggage)組合到單個資源中。叢集管理員或軟體工程師可以使用此資源來了解在定義的時間段內叢集中發生的情況。但是 Kubernetes 本身如何利用這個技術棧呢?
Kubernetes 由多個元件組成,其中一些是獨立的,另一些是堆疊在一起的。從容器執行時的角度來看架構,從上到下有
- kube-apiserver: 驗證和配置 API 物件的資料
- kubelet: 在每個節點上執行的代理
- CRI 執行時: 容器執行時介面 (CRI) 相容的容器執行時,例如 CRI-O 或 containerd
- OCI 執行時: 底層的 開放容器倡議 (OCI) 執行時,例如 runc 或 crun
- Linux 核心 或 Microsoft Windows:底層作業系統
這意味著如果我們在 Kubernetes 中執行容器時遇到問題,我們會從這些元件之一開始查詢。在當今叢集設定的架構複雜性增加的情況下,找到問題的根本原因是我們面臨的最耗時的操作之一。即使我們知道似乎導致問題的元件,我們仍然必須考慮其他元件,以保持對正在發生的事件的心理時間線。我們如何實現這一點?嗯,大多數人可能會堅持抓取日誌,過濾它們,並將它們跨元件邊界組合在一起。我們也有指標,對吧?沒錯,但是將指標值與純日誌相關聯使得跟蹤正在發生的事情變得更加困難。一些指標也不是為除錯目的而設計的。它們是根據叢集的終端使用者視角定義的,用於連結可用的警報,而不是為開發人員除錯叢集設定而定義的。
OpenTelemetry 來拯救:該專案旨在將追蹤、指標和日誌等訊號組合在一起,以保持對叢集狀態的正確視角。
Kubernetes 中 OpenTelemetry 追蹤的現狀如何?從 API 伺服器的角度來看,自 Kubernetes v1.22 以來,我們已經有了對追蹤的 alpha 支援,這將在即將釋出的版本之一中升級到 beta。不幸的是,beta 升級錯過了 v1.26 Kubernetes 版本。設計提案可以在API Server Tracing Kubernetes 增強提案 (KEP) 中找到,其中提供了更多相關資訊。
kubelet 追蹤部分在另一個 KEP 中跟蹤,該 KEP 在 Kubernetes v1.25 中以 alpha 狀態實現。在撰寫本文時,還沒有計劃升級到 beta,但更多內容可能會在 v1.27 釋出週期中出現。除了這兩個 KEP 之外,還有其他方面的努力正在進行中,例如klog 正在考慮 OTEL 支援,這將透過將日誌訊息連結到現有追蹤來提高可觀測性。在 SIG Instrumentation 和 SIG Node 中,我們也在討論如何將 kubelet 追蹤連結在一起,因為目前它們專注於 kubelet 和 CRI 容器執行時之間的 gRPC 呼叫。
CRI-O 自 v1.23.0 起就支援 OpenTelemetry 追蹤,並正在不斷改進,例如透過將日誌附加到追蹤或將跨度擴充套件到應用程式的邏輯部分。這有助於追蹤的使用者獲得與解析日誌相同的資訊,但具有對其他 OTEL 訊號進行範圍界定和過濾的增強功能。CRI-O 維護者還在開發一個名為 conmon-rs 的容器監控替代品,用於替代 conmon,它完全用 Rust 編寫。擁有 Rust 實現的一個好處是能夠新增 OpenTelemetry 支援等功能,因為這些功能的 crates(庫)已經存在。這允許與 CRI-O 緊密整合,並讓消費者看到來自其容器的最低級別的追蹤資料。
containerd 的開發者自 v1.6.0 起添加了追蹤支援,可以透過使用外掛來使用。像 runc 或 crun 這樣的底層 OCI 執行時完全不支援 OTEL,而且似乎也沒有相關計劃。我們必須始終考慮,在收集追蹤以及將其匯出到資料接收器時會產生效能開銷。我仍然認為,評估在 OCI 執行時中擴充套件的遙測收集會是什麼樣子是值得的。讓我們看看 Rust OCI 執行時 youki 將來是否會考慮類似的東西。
我將向您展示如何嘗試一下。在我的演示中,我將使用一個包含 runc、conmon-rs、CRI-O 和一個 kubelet 的單節點本地堆疊。要在 kubelet 中啟用追蹤,我需要應用以下 KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
KubeletTracing: true
tracing:
samplingRatePerMillion: 1000000
一個等於一百萬的 samplingRatePerMillion
在內部會轉換為對所有內容進行取樣。類似的配置也必須應用於 CRI-O;我可以使用 --enable-tracing
和 --tracing-sampling-rate-per-million 1000000
啟動 crio
二進位制檔案,或者使用如下的插入式配置
cat /etc/crio/crio.conf.d/99-tracing.conf
[crio.tracing]
enable_tracing = true
tracing_sampling_rate_per_million = 1000000
要將 CRI-O 配置為使用 conmon-rs,您至少需要最新的 CRI-O v1.25.x 和 conmon-rs v0.4.0。然後可以使用如下的配置插入來使 CRI-O 使用 conmon-rs
cat /etc/crio/crio.conf.d/99-runtimes.conf
[crio.runtime]
default_runtime = "runc"
[crio.runtime.runtimes.runc]
runtime_type = "pod"
monitor_path = "/path/to/conmonrs" # or will be looked up in $PATH
就是這樣,預設配置將指向一個 localhost:4317
的 OpenTelemetry 收集器 gRPC 端點,該端點也必須啟動並執行。有多種方式可以執行 OTLP,如文件中所述,但也可以透過 kubectl proxy
連線到 Kubernetes 內執行的現有例項。
如果一切都設定好了,那麼收集器應該會記錄有傳入的追蹤
ScopeSpans #0
ScopeSpans SchemaURL:
InstrumentationScope go.opentelemetry.io/otel/sdk/tracer
Span #0
Trace ID : 71896e69f7d337730dfedb6356e74f01
Parent ID : a2a7714534c017e6
ID : 1d27dbaf38b9da8b
Name : github.com/cri-o/cri-o/server.(*Server).filterSandboxList
Kind : SPAN_KIND_INTERNAL
Start time : 2022-11-15 09:50:20.060325562 +0000 UTC
End time : 2022-11-15 09:50:20.060326291 +0000 UTC
Status code : STATUS_CODE_UNSET
Status message :
Span #1
Trace ID : 71896e69f7d337730dfedb6356e74f01
Parent ID : a837a005d4389579
ID : a2a7714534c017e6
Name : github.com/cri-o/cri-o/server.(*Server).ListPodSandbox
Kind : SPAN_KIND_INTERNAL
Start time : 2022-11-15 09:50:20.060321973 +0000 UTC
End time : 2022-11-15 09:50:20.060330602 +0000 UTC
Status code : STATUS_CODE_UNSET
Status message :
Span #2
Trace ID : fae6742709d51a9b6606b6cb9f381b96
Parent ID : 3755d12b32610516
ID : 0492afd26519b4b0
Name : github.com/cri-o/cri-o/server.(*Server).filterContainerList
Kind : SPAN_KIND_INTERNAL
Start time : 2022-11-15 09:50:20.0607746 +0000 UTC
End time : 2022-11-15 09:50:20.060795505 +0000 UTC
Status code : STATUS_CODE_UNSET
Status message :
Events:
SpanEvent #0
-> Name: log
-> Timestamp: 2022-11-15 09:50:20.060778668 +0000 UTC
-> DroppedAttributesCount: 0
-> Attributes::
-> id: Str(adf791e5-2eb8-4425-b092-f217923fef93)
-> log.message: Str(No filters were applied, returning full container list)
-> log.severity: Str(DEBUG)
-> name: Str(/runtime.v1.RuntimeService/ListContainers)
我可以看到 span 有一個追蹤 ID,並且通常附加了一個父級。事件(例如日誌)也是輸出的一部分。在上述情況下,kubelet 正在週期性地向 CRI-O 觸發一個 ListPodSandbox
RPC,這是由 Pod 生命週期事件生成器 (PLEG) 引起的。可以透過例如 Jaeger 來顯示這些追蹤。當在本地執行追蹤堆疊時,Jaeger 例項預設應該暴露在 https://:16686
上。
ListPodSandbox
請求在 Jaeger UI 中直接可見
這不太令人興奮,所以我將直接透過 kubectl
執行一個工作負載
kubectl run -it --rm --restart=Never --image=alpine alpine -- echo hi
hi
pod "alpine" deleted
現在檢視 Jaeger,我們可以看到 conmonrs
、crio
以及 kubelet
的 RunPodSandbox
和 CreateContainer
CRI RPC 的追蹤
kubelet 和 CRI-O 的跨度相互連線,以便於調查。如果我們現在仔細檢視這些跨度,我們可以看到 CRI-O 的日誌與相應的功能正確關聯。例如,我們可以像這樣從追蹤中提取容器使用者
conmon-rs 的底層跨度也是此追蹤的一部分。例如,conmon-rs 維護一個內部的 read_loop
來處理容器和終端使用者之間的 IO。讀取和寫入位元組的日誌是該跨度的一部分。這同樣適用於 wait_for_exit_code
跨度,它告訴我們容器以程式碼 0
成功退出
將所有這些資訊與 Jaeger 的過濾功能並排使用,使整個堆疊成為除錯容器問題的絕佳解決方案!提到“整個堆疊”也顯示了這種整體方法的最大缺點:與解析日誌相比,它在叢集設定之上增加了顯著的開銷。使用者必須維護一個像 Elasticsearch 這樣的接收器來持久化資料,公開 Jaeger UI,並可能要考慮效能上的損失。無論如何,這仍然是提高 Kubernetes 可觀測性的最佳方法之一。
感謝您閱讀這篇部落格文章,我堅信 Kubernetes 中 OpenTelemetry 支援的光明未來將使故障排除變得更簡單。