使用 Knative、OpenTelemetry 和 Jaeger 進行分散式追蹤 ¶
發布於:2021-08-30 , 修改於:2024-01-17
使用 Knative、OpenTelemetry 和 Jaeger 進行分散式追蹤¶
當我們試圖了解和診斷我們的系統時,我們學會依賴的最基本工具之一就是堆疊追蹤。堆疊追蹤為我們提供了程式執行的邏輯流程的結構化視圖,以幫助我們了解如何進入特定狀態。分散式追蹤是我們業界試圖將這個概念應用於更高層次的抽象,並讓我們了解訊息在程式之間流動的方式。
Knative Eventing 是一組用於連接當今許多人青睞的分散式架構的建構區塊。它為我們提供了一種描述和組裝程式之間連接的語言,透過代理程式、觸發器、通道和流程,但有了這種能力,就會有創建一堆義大利麵條的風險,使得確定事件是如何觸發的變得困難。在這篇文章中,我們將逐步介紹如何使用 Eventing 設定分散式追蹤,並了解它如何幫助我們更好地了解我們的程式以及 Eventing 在底層的工作方式。
追蹤概況¶
學習如何進行追蹤時遇到的第一個問題之一就是了解生態系統:Zipkin、Jaeger、OpenTelemetry、OpenCensus、OpenTracing 以及其他無數工具,應該使用哪一個?好消息是,這最後三個「開放」程式庫試圖為指標和追蹤創建標準,以便我們不必立即決定將使用哪些儲存和視覺化工具,而且它們之間的切換應該(大部分)是輕鬆的。OpenCensus 和 OpenTracing 最初都是為了統一追蹤和指標周圍分散的格局,導致了一組悲劇/滑稽的新分歧和競爭標準。OpenTelemetry 是最新的嘗試,它本身就是 OpenCensus 和 OpenTracing 的統一。
Knative 目前的追蹤支援 僅適用於 OpenCensus,但 OpenTelemetry 社群為我們提供了彌合系統中這種差距的工具。在這篇文章中,我們將重點介紹如何透過混合使用 OpenCensus 和 OpenTelemetry 來使用 Jaeger,但更廣泛的教訓應該適用於您使用的任何工具。
開始使用¶
我們假設您有一個已安裝 Knative Serving 和 Eventing 的叢集。如果您還沒有叢集,我建議您試試Knative 快速入門,但理論上任何設定都應該可行。
安裝 Knative 後,我們將把 OpenTelemetry 運算子新增到我們的叢集,該運算子依賴 cert-manager。在安裝這兩個工具時要注意的一點是,您需要等待 cert-manager 的 webhook pod 啟動,然後才能安裝運算子,否則您會看到一堆「連線拒絕」錯誤,導致無法建立憑證。執行 kubectl -n cert-manager wait --for=condition=Ready pods --all
將會阻塞,直到 cert-manager 準備就緒。kubectl wait
的預設逾時時間為 30 秒,因此在您的叢集上可能需要更長的時間,具體取決於映像下載速度。
kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml &&
kubectl -n cert-manager wait --for=condition=Ready pods --all &&
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.40.0/opentelemetry-operator.yaml
接下來,我們將設定 Jaeger 運算子(是的,另一個運算子,我發誓這是最後一個)。
kubectl create namespace observability &&
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/crds/jaegertracing.io_jaegers_crd.yaml &&
kubectl create -n observability \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/service_account.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/role.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/role_binding.yaml \
-f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.28.0/deploy/operator.yaml
啟動後,我們可以執行以下命令來建立 Jaeger 執行個體
kubectl apply -n observability -f - <<EOF
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: simplest
EOF
這可能需要一些時間才能啟動,因為我們再次等待運算子 pod,然後等待 Jaeger pod 本身啟動。啟動後,Jaeger 運算子將為我們的 Jaeger 建立 Kubernetes Ingress,但由於我們在 Kind 上執行,因此我們沒有安裝任何 ingress。沒關係,連接埠轉送足以滿足我們的目的:執行 kubectl -n observability port-forward service/simplest-query 16686
將使我們的 Jaeger 儀表板可在 https://#:16686 上存取。
接下來,我們要建立 OpenTelemetry 收集器,它將負責接收來自我們程式的追蹤資料,並將其轉發到 Jaeger。收集器是一個抽象層,可以讓我們連接使用不同協定的系統。即使我們只匯出 Zipkin 追蹤資料,我們也可以依靠收集器將它們轉換成 Jaeger 可以使用的格式。這個收集器定義會告訴 OpenTelemetry Operator 建立一個收集器,它會像 Zipkin 實例一樣監聽追蹤資料,但同時將它們匯出到日誌以進行除錯,並匯出到我們的 Jaeger 實例。
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel
namespace: observability
spec:
config: |
receivers:
zipkin:
exporters:
logging:
jaeger:
endpoint: "simplest-collector.observability:14250"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [zipkin]
processors: []
exporters: [logging, jaeger]
EOF
如果一切順利,我們現在應該看到 3 個 Pod 在 observability
命名空間中執行:我們的 Jaeger Operator、我們的 Jaeger 實例和 OpenTelemetry 收集器。
最後,我們可以設定 Eventing 和 Serving 將它們所有的追蹤資料指向我們的收集器。
for ns in knative-eventing knative-serving; do
kubectl patch --namespace "$ns" configmap/config-tracing \
--type merge \
--patch '{"data":{"backend":"zipkin","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans", "debug": "true"}}'
done
此處的 debug
標誌會告知 Knative 將所有追蹤資料傳送到我們的收集器,而在實際部署中,您可能需要設定一個取樣率,僅取得具有代表性的追蹤資料子集。
哈囉,世界?¶
現在我們的追蹤基礎架構都已部署並設定完成,我們可以開始部署一些服務來善用它。我們可以部署 heartbeat 映像檔作為 ContainerSource 來測試,並確認所有項目都正確連接。
kubectl apply -f - <<EOF
apiVersion: sources.knative.dev/v1
kind: ContainerSource
metadata:
name: heartbeats
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/heartbeats:latest
name: heartbeats
args:
- --period=1
env:
- name: POD_NAME
value: "heartbeats"
- name: POD_NAMESPACE
value: "default"
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","sample-rate":"1","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
sink:
uri: http://dev.null
EOF
目前,這個容器只會將其 heartbeat 傳送到不存在的網域 http://dev.null,因此如果我們查看此 Pod 的日誌,我們會看到一堆 DNS 解析錯誤。但是,如果我們檢查 otel-collector
Pod 的日誌,我們應該會看到它正在成功地接收來自我們服務的追蹤資料。這是一個很好的確認,表示我們的設定有效,但從追蹤的角度來看,這並不是很令人興奮!讓我們新增一個 Knative 服務來接收我們的 heartbeat,使其更接近真實情況。
kubectl apply -f - <<EOF
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: event-display
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/event_display:latest
env:
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
EOF
我們會更新我們的 heartbeat 服務,開始將 heartbeat 傳送到這裡。
kubectl apply -f - <<EOF
apiVersion: sources.knative.dev/v1
kind: ContainerSource
metadata:
name: heartbeats
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/heartbeats:latest
name: heartbeats
args:
- --period=1
env:
- name: POD_NAME
value: "heartbeats"
- name: POD_NAMESPACE
value: "default"
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
sink:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-display
EOF
一旦這些服務部署完成,我們可以回到我們的 Jaeger 儀表板,我們應該會看到一些更有趣的追蹤資料。
在 Jaeger 的「系統架構」標籤中,我們也可以看到一個不錯的拓撲圖,其中包括一個您可能不知道的元件,即 activator。
這是 Knative Serving 新增到 Knative 服務網路路徑中的一個元件,用於在我們的服務尚未準備好處理請求時緩衝請求,並將請求指標報告給自動調整程式。您也可以看到它增加了一個小小的延遲,在我叢集上約為 2 毫秒。可以設定 Knative,讓 activator 在不同情況下保持不在路徑中,但這是另一篇部落格文章的主題:)。
進階應用¶
讓我們透過新增一些 Knative 的花俏功能,使我們的拓撲更有趣一點。首先,讓我們開始透過 Broker 和 Trigger 傳送訊息,而不是直接從我們的 heartbeat 服務傳送。我們將建立一個 broker 和 trigger,將所有訊息轉發到 event-display 服務,並重新設定我們的 heartbeat 服務以指向 broker。
kubectl apply -f - <<EOF
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: heartbeat-to-eventdisplay
spec:
broker: default
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-display
---
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
name: default
---
apiVersion: sources.knative.dev/v1
kind: ContainerSource
metadata:
name: heartbeats
spec:
template:
spec:
containers:
- image: gcr.io/knative-nightly/knative.dev/eventing/cmd/heartbeats:latest
name: heartbeats
args:
- --period=1
env:
- name: POD_NAME
value: "heartbeats"
- name: POD_NAMESPACE
value: "default"
- name: K_CONFIG_TRACING
value: '{"backend":"zipkin","debug":"true","zipkin-endpoint":"http://otel-collector.observability:9411/api/v2/spans"}'
sink:
ref:
apiVersion: eventing.knative.dev/v1
kind: Broker
name: default
EOF
如果我們現在回到我們的 Jaeger,我們應該會看到更複雜的追蹤,在 heartbeat 和 event-display 之間,訊息所採取的路徑多了許多 Eventing 的記憶體內 broker。如果您使用不同的 broker 實作,您的追蹤資料將會有所不同,但在所有情況下,我們都在為了提高系統的彈性和功能而增加系統的複雜性。
從這裡,我們可以為我們的部署新增另一個變化:不要讓每個 heartbeat 直接傳送到我們的 event-display 服務,讓我們擲硬幣,只有在我們得到「正面」時才傳送。幸運的是,我對數字符號理論非常了解,並且已經編寫了這個擲硬幣微服務,所以我們可以直接將它部署為新的 Knative 服務。
kubectl apply -f - <<EOF
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: coinflip
spec:
template:
spec:
containers:
- image: benmoss/coinflip:latest
env:
- name: OTLP_TRACE_ENDPOINT
value: otel-collector.observability:4317
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: heartbeat-to-coinflip
spec:
broker: default
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: coinflip
filter:
attributes:
type: dev.knative.eventing.samples.heartbeat
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: heartbeat-to-eventdisplay
spec:
broker: default
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: event-display
filter:
attributes:
flip: heads
EOF
kubectl apply -f - <<EOF
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel
namespace: observability
spec:
config: |
receivers:
zipkin:
otlp:
protocols:
grpc:
exporters:
logging:
jaeger:
endpoint: "simplest-collector.observability.svc.cluster.local:14250"
insecure: true
service:
pipelines:
traces:
receivers: [zipkin, otlp]
processors: []
exporters: [logging, jaeger]
EOF
如果我們檢查新的觸發器設定,我們會看到現在我們有兩個觸發器,一個將所有 heartbeat 類型的事件傳送到擲硬幣器,另一個將所有具有擴充功能「flip: heads」的事件傳送到 event-display。擲硬幣服務會複製傳入的 heartbeat 事件,擲硬幣並將結果新增為 CloudEvents 擴充功能,並更改事件類型,以免我們不小心產生無限循環的擲硬幣。然後,它會將此事件傳送回 broker 進行重新排隊,然後在正面時分派到 event-display,如果結果是反面則會被捨棄。
如果我們回到我們的 Jaeger 介面,我們會看到不同長度的 heartbeat 追蹤,有時會因為不幸的反面而終止,但有時會贏得大獎並轉發到 event-display。檢查 event-display 的日誌,我們應該會看到事件仍然在傳入,儘管速度比以前慢,而且都具有「flip: heads」的擴充功能。我們也會看到我們從擲硬幣服務中透過 自訂檢測傳送的這些自訂範圍。
我們可以從 Jaeger 的架構圖中了解這裡發生的情況。我們的事件從 heartbeat 服務流入,通過 broker,然後輸出到我們的每個觸發器。我們觸發器上的篩選器表示最初事件只會繼續到我們的擲硬幣服務。擲硬幣服務會回覆一個新的事件,然後該事件會流回 broker 和篩選器,這次被我們的擲硬幣觸發器拒絕,但被 event-display 觸發器接受。
總結¶
希望透過這一切,我們對 Knative 和良好可觀察性工具的價值都有了更深入的了解。我們看到了如何善用 OpenTelemetry 收集器來整合使用不同協定的系統,並將它們全部導向一個共用的 Jaeger 實例。我們建立的拓撲在一方面來說是微不足道的,但希望它足夠有趣和複雜,可以指出您如何建構真實的事件驅動系統。可觀察性和指標生態系統很大,有時會讓人感到不知所措,但一旦設定完成,它可以在理解和疑難排解您的系統方面成為救星。