1. 簡介
上次更新時間:2022 年 7 月 15 日
應用程式的觀測能力
觀測能力與 OpenTelemetry
「觀測能力」是指用來描述系統屬性的字詞。具備觀測能力的系統可讓團隊主動偵錯。在這些情境下,觀測能力的三大要素;記錄、指標和追蹤記錄是系統取得觀測能力的基本檢測標準。
OpenTelemetry 是一組規格、程式庫和代理程式,可加快檢測與匯出觀測能力所需的遙測資料 (記錄、指標和追蹤記錄)。OpenTelemetry 是根據 CNCF 編製的開放式標準和社群導向的專案。只要使用專案及其生態系統提供的程式庫,開發人員就能以不受供應商的方式,使用多個架構檢測應用程式。
除了觀測能力的三大要素以外,持續分析是觀測能力的另一個關鍵要素,同時在業界擴大使用者族群。Cloud Profiler 是來源系統之一,您可透過簡單易用的介面細查應用程式呼叫堆疊中的效能指標。
本程式碼研究室是本系列文章的第 1 集,說明如何使用 OpenTelemetry 和 Cloud Trace 檢測微服務中的分散式追蹤記錄。第 2 部分會介紹如何使用 Cloud Profiler 持續剖析資料。
分散式追蹤記錄
追蹤記錄是記錄檔、指標和追蹤記錄中的遙測資料,可指出系統中特定部分程序的延遲時間。分散式追蹤記錄 (特別是在微服務的時代) 是強大的驅動力,能找出整體分散式系統的延遲瓶頸。
分析分散式追蹤記錄時,可透過追蹤記錄資料視覺化功能快速掌握整體系統延遲時間。在分散式追蹤記錄中,我們會處理一組呼叫,以含有多個 Spans 的 Trace 形式處理對系統進入點的單一要求。
「Span 」代表在分散式系統中完成的個別工作單元,記錄開始和停止時間。Span 通常彼此之間具有階層關係,在下圖中,所有較小的跨距都是大型 /訊息跨距的子時距,會組合成一個追蹤記錄,顯示透過系統執行工作的路徑。
Google Cloud Trace 是分散式追蹤記錄後端的其中一種方案,這項服務與其他 Google Cloud 產品完美整合。
建構項目
在本程式碼研究室中,您要在名為「Shakespeare 應用程式」的服務中檢測追蹤記錄資訊(又稱「Shakesapp」) 使用 Google Kubernetes Engine 叢集Shakesapp 的架構如下:
- Loadgen 使用 HTTP 將查詢字串傳送至用戶端
- 用戶端將查詢從 loadgen 傳遞至 gRPC 中的伺服器
- 伺服器接受從用戶端查詢、從 Google Cloud Storage 擷取所有 Shakespare 都能以文字格式運作、搜尋包含查詢的行,並傳回與用戶端相符的該行數
您將檢測所有要求的追蹤資訊。接著,將分析器代理程式嵌入伺服器,並調查瓶頸。
課程內容
- 如何在 Go 專案中開始使用 OpenTelemetry Trace 程式庫
- 如何使用程式庫建立 Span
- 如何在應用程式元件之間的線路傳播 Span 內容
- 如何將追蹤記錄資料傳送至 Cloud Trace
- 如何分析 Cloud Trace 的追蹤記錄
本程式碼研究室說明如何檢測微服務。為方便您理解,這個範例僅包含 3 個元件 (載入產生器、用戶端和伺服器),但您可以將本程式碼研究室所述的相同程序套用至更複雜和較大的系統。
軟硬體需求
- Go 基本知識
- Kubernetes 的基本知識
2. 設定和需求
自修環境設定
如果您還沒有 Google 帳戶 (Gmail 或 Google Apps),請先建立帳戶。登入 Google Cloud Platform 控制台 ( console.cloud.google.com),並建立新專案。
如果您已有專案,請按一下控制台左上方的專案選取下拉式選單:
並點選 [新增專案]按鈕,用於建立新專案:
如果您還沒有專案,系統會顯示如下的對話方塊,讓您建立第一個專案:
後續的專案建立對話方塊可讓您輸入新專案的詳細資料:
請記住,專案 ID 在所有的 Google Cloud 專案中是不重複的名稱 (已經有人使用上述名稱,目前無法為您解決問題!)。稍後在本程式碼研究室中會稱為 PROJECT_ID。
接下來,如果您尚未在 Developers Console 中啟用計費功能,您需要先在 Developers Console 中啟用計費功能,才能使用 Google Cloud 資源並啟用 Cloud Trace API。
執行本程式碼研究室所需的費用不應超過數美元,但如果您決定使用更多資源,或讓這些資源繼續運作,費用會增加 (請參閱本文件結尾的「清理」一節)。如需 Google Cloud Trace、Google Kubernetes Engine 和 Google Artifact Registry 的定價資訊,請參閱官方說明文件。
Google Cloud Platform 的新使用者符合 $300 美元的免費試用資格,應該可以免費使用本程式碼研究室。
Google Cloud Shell 設定
雖然 Google Cloud 和 Google Cloud Trace 可以在筆記型電腦上遠端運作,但在本程式碼研究室中,我們會使用 Google Cloud Shell,這是一種在 Cloud 中執行的指令列環境。
這種以 Debian 為基礎的虛擬機器,搭載各種您需要的開發工具。提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,大幅提高網路效能和驗證能力。換言之,本程式碼研究室只需要在 Chromebook 上運作即可。
如要透過 Cloud 控制台啟用 Cloud Shell,只要點選「啟用 Cloud Shell」圖示 ,即可佈建並連線至環境,操作只需幾分鐘的時間。
連線至 Cloud Shell 後,您應會發現自己通過驗證,且專案已設為 PROJECT_ID
。
gcloud auth list
指令輸出
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
指令輸出
[core] project = <PROJECT_ID>
如因故未設定專案,請直接發出以下指令:
gcloud config set project <PROJECT_ID>
正在尋找 PROJECT_ID
嗎?查看您在設定步驟中使用的 ID,或在 Cloud 控制台資訊主頁查詢:
根據預設,Cloud Shell 也會設定一些環境變數,方便您之後執行指令。
echo $GOOGLE_CLOUD_PROJECT
指令輸出
<PROJECT_ID>
最後,進行預設可用區和專案設定。
gcloud config set compute/zone us-central1-f
您可以選擇各種不同的可用區。詳情請參閱「區域與可用區。
Go 語言設定
在本程式碼研究室中,我們會針對所有原始碼使用 Go。在 Cloud Shell 執行下列指令,並確認 Go 的版本是否為 1.17 以上版本
go version
指令輸出
go version go1.18.3 linux/amd64
設定 Google Kubernetes 叢集
在本程式碼研究室中,您將在 Google Kubernetes Engine (GKE) 執行微服務叢集。本程式碼研究室的流程如下:
- 將基準專案下載至 Cloud Shell
- 將微服務建構至容器
- 將容器上傳至 Google Artifact Registry (GAR)
- 將容器部署至 GKE
- 修改追蹤記錄檢測服務的原始碼
- 前往步驟 2
啟用 Kubernetes Engine
首先,我們設定了一個 Kubernetes 叢集,讓 Shakesapp 在 GKE 上執行,因此必須啟用 GKE。前往「Kubernetes Engine」選單然後按下「啟用」按鈕
您現在可以建立 Kubernetes 叢集了。
建立 Kubernetes 叢集
在 Cloud Shell 執行下列指令,建立 Kubernetes 叢集。請確認可用區值位於您要用來建立 Artifact Registry 存放區的區域。如果存放區區域未涵蓋可用區,請變更可用區值 us-central1-f
。
gcloud container clusters create otel-trace-codelab2 \ --zone us-central1-f \ --release-channel rapid \ --preemptible \ --enable-autoscaling \ --max-nodes 8 \ --no-enable-ip-alias \ --scopes cloud-platform
指令輸出
Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done. Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403 kubeconfig entry generated for otel-trace-codelab2. NAME: otel-trace-codelab2 LOCATION: us-central1-f MASTER_VERSION: 1.23.6-gke.1501 MASTER_IP: 104.154.76.89 MACHINE_TYPE: e2-medium NODE_VERSION: 1.23.6-gke.1501 NUM_NODES: 3 STATUS: RUNNING
Artifact Registry 和 Skaffold 設定
現在,我們已備妥可部署的 Kubernetes 叢集。接下來,我們要針對推送和部署容器的 Container Registry 做好準備。為執行這些步驟,我們必須設定 Artifact Registry (GAR) 和 Skaffold,
Artifact Registry 設定
前往「Artifact Registry」的選單然後按下「啟用」按鈕
一段時間後,您會看到 GAR 的存放區瀏覽器。按一下 [建立存放區]按鈕,然後輸入存放區名稱。
在本程式碼研究室中,我會將新存放區命名為 trace-codelab
。構件的格式為「Docker」地點類型為「區域」選擇接近您為 Google Compute Engine 預設可用區設定的區域。以下範例選擇「us-central1-f」上方,我們選擇「us-central1 (愛荷華州)」接著按一下「建立」按鈕。
現在您會看到複製到存放區瀏覽器
稍後會再回來檢查登錄路徑。
Skaffold 設定
如要建構在 Kubernetes 上執行的微服務,Skaffold 是相當實用的工具。這個工具包只需使用少量指令,就能處理建構、推送及部署應用程式容器的工作流程。根據預設,Skaffold 會使用 Docker Registry 做為容器登錄檔,因此您需要設定 skaffold 來辨識 GAR,以將容器推送到容器。
再次開啟 Cloud Shell,並確認是否已安裝 skaffold。(Cloud Shell 預設會在環境中安裝 Skaffold。)執行下列指令並查看 Skaffold 版本。
skaffold version
指令輸出
v1.38.0
現在,您可以註冊使用 Skaffold 的預設存放區。如要取得登錄檔路徑,請前往 Artifact Registry 資訊主頁,然後按一下您在上一個步驟中設定的存放區名稱。
您就會在頁面頂端看到導覽標記記錄。按一下 圖示,將登錄路徑複製到剪貼簿。
按一下「複製」按鈕後,您會在瀏覽器底部看到含有下列訊息的對話方塊:
"us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab"已複製
返回 Cloud Shell。使用剛剛從資訊主頁複製的值執行 skaffold config set default-repo
指令。
skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab
指令輸出
set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox
此外,您必須將登錄檔設為 Docker 設定。執行下列指令:
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
指令輸出
{ "credHelpers": { "gcr.io": "gcloud", "us.gcr.io": "gcloud", "eu.gcr.io": "gcloud", "asia.gcr.io": "gcloud", "staging-k8s.gcr.io": "gcloud", "marketplace.gcr.io": "gcloud", "us-central1-docker.pkg.dev": "gcloud" } } Adding credentials for: us-central1-docker.pkg.dev
您現在可以前往下一個步驟,在 GKE 中設定 Kubernetes 容器。
摘要
在這個步驟中,您會設定程式碼研究室環境:
- 設定 Cloud Shell
- 已為 Container Registry 建立 Artifact Registry 存放區
- 設定 skaffold 以使用 Container Registry
- 建立用於執行程式碼研究室微服務的 Kubernetes 叢集
下一步
在下一個步驟中,您會建構微服務並將微服務推送至叢集
3. 建構、推送及部署微服務
下載程式碼研究室教材
在上一個步驟中,我們已設定本程式碼研究室的所有必備條件。您現在可以透過這些微服務執行整個微服務了。程式碼研究室的內容託管於 GitHub,請使用以下 Git 指令將這些內容下載至 Cloud Shell 環境。
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
專案的目錄結構如下:
. ├── README.md ├── step0 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step1 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step2 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step3 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step4 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step5 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src └── step6 ├── manifests ├── proto ├── skaffold.yaml └── src
- 資訊清單:Kubernetes 資訊清單檔案
- proto:用戶端和伺服器通訊的 Proto 定義
- src: 每個服務的原始碼目錄
- skaffold.yaml:skaffold 的設定檔
在本程式碼研究室中,您將更新 step0
資料夾下的原始碼。您也可以在下列步驟中,參考 step[1-6]
資料夾中的原始碼來取得答案。(第 1 部分涵蓋步驟 0 到步驟 4,第 2 部分涵蓋步驟 5 和 6)
執行 skaffold 指令
最後,您可以開始建構完整內容,並將完整內容推送至剛剛建立的 Kubernetes 叢集。這聽起來似乎包含多個步驟,但其實 Skaffold 可以為您執行所有操作。請使用下列指令:
cd step0 skaffold dev
執行指令後,您會看到 docker build
的記錄輸出,並且可以確認已成功推送至登錄檔。
指令輸出
... ---> Running in c39b3ea8692b ---> 90932a583ab6 Successfully built 90932a583ab6 Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1 The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice] cc8f5a05df4a: Preparing 5bf719419ee2: Preparing 2901929ad341: Preparing 88d9943798ba: Preparing b0fdf826a39a: Preparing 3c9c1e0b1647: Preparing f3427ce9393d: Preparing 14a1ca976738: Preparing f3427ce9393d: Waiting 14a1ca976738: Waiting 3c9c1e0b1647: Waiting b0fdf826a39a: Layer already exists 88d9943798ba: Layer already exists f3427ce9393d: Layer already exists 3c9c1e0b1647: Layer already exists 14a1ca976738: Layer already exists 2901929ad341: Pushed 5bf719419ee2: Pushed cc8f5a05df4a: Pushed step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001
所有服務容器推送完畢後,Kubernetes Deployment 就會自動啟動。
指令輸出
sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997 Tags used in deployment: - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a Starting deploy... - deployment.apps/clientservice created - service/clientservice created - deployment.apps/loadgen created - deployment.apps/serverservice created - service/serverservice created
部署完畢後,您會在每個容器中看到發送至 stdout 的實際應用程式記錄檔,如下所示:
指令輸出
[client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:16 {"match_count":3040} [loadgen] 2022/07/14 06:33:16 query 'love': matched 3040 [client] 2022/07/14 06:33:19 {"match_count":463} [loadgen] 2022/07/14 06:33:19 query 'tear': matched 463 [loadgen] 2022/07/14 06:33:20 query 'world': matched 728 [client] 2022/07/14 06:33:20 {"match_count":728} [client] 2022/07/14 06:33:22 {"match_count":463} [loadgen] 2022/07/14 06:33:22 query 'tear': matched 463
請注意,在這個階段,您可以查看伺服器傳送的任何郵件。最後,您已準備好使用 OpenTelemetry 檢測應用程式,以追蹤服務的分散情況。
在開始檢測服務之前,請使用 Ctrl-C 關閉叢集。
指令輸出
... [client] 2022/07/14 06:34:57 {"match_count":1} [loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1 ^CCleaning up... - W0714 06:34:58.464305 28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead. - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
摘要
在這個步驟中,您已準備好環境中的程式碼研究室教材,並確認 Skaffold 可以正常執行。
下一步
在下一個步驟中,您將修改 loadgen 服務的原始碼,以檢測追蹤記錄資訊。
4. HTTP 檢測
追蹤記錄檢測和傳播概念
在編輯原始碼之前,我們用簡單的圖表簡單說明分散式追蹤記錄的運作方式。
在本範例中,我們會檢測程式碼,將 Trace 和 Span 資訊匯出至 Cloud Trace,並將來自 Loadgen 服務的要求將追蹤記錄內容傳播至伺服器服務。
應用程式必須傳送追蹤記錄中繼資料 (例如追蹤記錄 ID 和時距 ID),Cloud Trace 才能將具有相同追蹤記錄 ID 的所有時距組合成單一追蹤記錄。此外,應用程式需要傳播追蹤記錄內容 (父項時距的追蹤記錄 ID 和時距 ID 的組合),才能要求下游服務,讓他們知道自己處理的是哪個追蹤記錄情境。
OpenTelemetry 可協助您:
- 以便產生不重複的追蹤記錄 ID 和時距 ID
- ,將追蹤記錄 ID 和時距 ID 匯出至後端
- 將追蹤記錄背景資訊傳播至其他服務
- 嵌入可協助分析追蹤記錄的額外中繼資料
OpenTelemetry Trace 中的元件
使用 OpenTelemetry 檢測應用程式追蹤記錄的程序如下:
- 建立匯出工具
- 在 1 中建立 TracerProvider 繫結,並設為全域。
- 設定 TextMapPropagaror,以便設定傳播方法
- 從 TracerProvider 取得 Tracer
- 從追蹤器產生時距
目前,您不必瞭解每個元件的詳細屬性,但最重要的是:
- 這裡的匯出程式可以插入 TracerProvider
- TracerProvider 會保留所有追蹤記錄取樣和匯出設定
- 所有追蹤記錄都會封裝在 Tracer 物件中
瞭解這點後,接著來看看實際的程式設計作業。
檢測首距
檢測負載產生器服務
按下 Cloud Shell 右上角的 按鈕,開啟 Cloud Shell 編輯器。在左側窗格中的探索工具中開啟
step0/src/loadgen/main.go
,然後找出主要函式。
step0/src/loadgen/main.go
func main() { ... for range t.C { log.Printf("simulating client requests, round %d", i) if err := run(numWorkers, numConcurrency); err != nil { log.Printf("aborted round with error: %v", err) } log.Printf("simulated %d requests", numWorkers) if numRounds != 0 && i > numRounds { break } i++ } }
在主函式中,您會看到呼叫函式 run
的迴圈。在目前的實作中,本節有 2 行記錄,記錄函式呼叫的開始和結束。現在,我們可以檢測 Span 資訊,以追蹤函式呼叫的延遲時間。
首先,如上一節所述,我們要進行 OpenTelemetry 的完整設定。新增 OpenTelemetry 套件,如下所示:
step0/src/loadgen/main.go
import ( "context" // step1. add packages "encoding/json" "fmt" "io" "log" "math/rand" "net/http" "net/url" "time" // step1. add packages "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.opentelemetry.io/otel/trace" // step1. end add packages )
為方便閱讀,我們建立名為 initTracer
的設定函式,並在 main
函式中呼叫該函式。
step0/src/loadgen/main.go
// step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
您可能瞭解到上一節所述,設定 OpenTelemetry 的程序。在這個實作中,我們使用 stdout
匯出工具,將所有追蹤記錄資訊以結構化格式匯出至 stdout。
接著,您會從主函式呼叫該函式。請呼叫 initTracer()
,並確保在關閉應用程式時呼叫 TracerProvider.Shutdown()
。
step0/src/loadgen/main.go
func main() { // step1. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step1. end setup log.Printf("starting worder with %d workers in %d concurrency", numWorkers, numConcurrency) log.Printf("number of rounds: %d (0 is inifinite)", numRounds) ...
完成設定後,您需要使用不重複的追蹤記錄 ID 和時距 ID 建立 Span。OpenTelemetry 提供了實用的程式庫。將額外的套件新增至檢測設備 HTTP 用戶端。
step0/src/loadgen/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "math/rand" "net/http" "net/http/httptrace" // step1. add packages "net/url" "time" // step1. add packages "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" // step1. end add packages "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.opentelemetry.io/otel/trace" )
由於載入產生器使用 net/http
的 runQuery
函式以 HTTP 呼叫用戶端服務,我們會使用 net/http
的 contrib 套件,並啟用含有 httptrace
和 otelhttp
套件擴充功能的檢測功能。
首先,請新增套件全域變數 httpClient,透過檢測的用戶端呼叫 HTTP 要求。
step0/src/loadgen/main.go
var httpClient = http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport) }
接下來,請在 runQuery
函式中新增檢測,以便使用 OpenTelemetry 建立自訂時距,以及從自訂 HTTP 用戶端自動產生的時距。您要執行的操作如下:
- 使用
otel.Tracer()
從全域TracerProvider
取得 Tracer - 使用
Tracer.Start()
方法建立根時距 - 以任意時間結束根時距 (在本例中為
runQuery
函式的結尾)
step0/src/loadgen/main.go
reqURL.RawQuery = v.Encode() // step1. replace http.Get() with custom client call // resp, err := http.Get(reqURL.String()) // step1. instrument trace ctx := context.Background() tr := otel.Tracer("loadgen") ctx, span := tr.Start(ctx, "query.request", trace.WithAttributes( semconv.TelemetrySDKLanguageGo, semconv.ServiceNameKey.String("loadgen.runQuery"), attribute.Key("query").String(s), )) defer span.End() ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx)) req, err := http.NewRequestWithContext(ctx, "GET", reqURL.String(), nil) if err != nil { return -1, fmt.Errorf("error creating HTTP request object: %v", err) } resp, err := httpClient.Do(req) // step1. end instrumentation if err != nil { return -1, fmt.Errorf("error sending request to %v: %v", reqURL.String(), err) }
您已完成 loadgen (HTTP 用戶端應用程式) 的檢測作業。請務必使用 go mod
指令更新 go.mod
和 go.sum
。
go mod tidy
檢測用戶端服務
在上一節中,我們在下方繪圖中檢測了紅色矩形包圍的部分。我們已在負載產生器服務中檢測時距資訊。與載入產生器服務類似,現在我們需要檢測用戶端服務。不同於負載產生器服務,用戶端服務必須擷取從 HTTP 標頭載入產生器服務傳播的追蹤 ID 資訊,並使用 ID 產生 Spans。
開啟 Cloud Shell 編輯器,並新增必要套件,就像我們對負載產生器服務的做法一樣。
step0/src/client/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "time" "opentelemetry-trace-codelab-go/client/shakesapp" // step1. add new import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" // step1. end new import )
我們同樣需要設定 OpenTelemtry。只要複製並貼上 loadgen 中的 initTracer
函式,然後在用戶端服務的 main
函式中呼叫這個函式即可。
step0/src/client/main.go
// step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
接下來要檢測跨度。由於用戶端服務必須接受來自 loadgen 服務的 HTTP 要求,因此需要檢測處理常式。用戶端服務中的 HTTP 伺服器是以 net/http 實作,而您可以使用 otelhttp
套件,就像在 loadgen 中的處理方式。
首先,我們將處理常式註冊替換為 otelhttp
處理常式。在 main
函式中,找出使用 http.HandleFunc()
註冊 HTTP 處理常式的行。
step0/src/client/main.go
// step1. change handler to intercept OpenTelemetry related headers // http.HandleFunc("/", svc.handler) otelHandler := otelhttp.NewHandler(http.HandlerFunc(svc.handler), "client.handler") http.Handle("/", otelHandler) // step1. end intercepter setting http.HandleFunc("/_genki", svc.health)
接下來,我們會在處理常式中檢測實際的 Span。找出 Func (*clientService) handler(),並使用 trace.SpanFromContext()
新增 Span 檢測。
step0/src/client/main.go
func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) { ... ctx := r.Context() ctx, cancel := context.WithCancel(ctx) defer cancel() // step1. instrument trace span := trace.SpanFromContext(ctx) defer span.End() // step1. end instrument ...
透過這個檢測,您可以取得 handler
方法的開頭到結尾的時距。如要輕鬆分析時距,請新增額外屬性,將相符的計數儲存至查詢。在記錄行前方加入下列程式碼。
func (cs *clientService) handler(w http.ResponseWriter, r *http.Request) { ... // step1. add span specific attribute span.SetAttributes(attribute.Key("matched").Int64(resp.MatchCount)) // step1. end adding attribute log.Println(string(ret)) ...
在上述所有檢測作業中,您已完成 loadgen 和用戶端之間的追蹤記錄檢測作業。讓我們來瞭解這個機制。使用 Skaffold 再次執行程式碼。
skaffold dev
花費時間在 GKE 叢集上執行服務後,您會看到大量記錄訊息,如下所示:
指令輸出
[loadgen] { [loadgen] "Name": "query.request", [loadgen] "SpanContext": { [loadgen] "TraceID": "cfa22247a542beeb55a3434392d46b89", [loadgen] "SpanID": "18b06404b10c418b", [loadgen] "TraceFlags": "01", [loadgen] "TraceState": "", [loadgen] "Remote": false [loadgen] }, [loadgen] "Parent": { [loadgen] "TraceID": "00000000000000000000000000000000", [loadgen] "SpanID": "0000000000000000", [loadgen] "TraceFlags": "00", [loadgen] "TraceState": "", [loadgen] "Remote": false [loadgen] }, [loadgen] "SpanKind": 1, [loadgen] "StartTime": "2022-07-14T13:13:36.686751087Z", [loadgen] "EndTime": "2022-07-14T13:14:31.849601964Z", [loadgen] "Attributes": [ [loadgen] { [loadgen] "Key": "telemetry.sdk.language", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "go" [loadgen] } [loadgen] }, [loadgen] { [loadgen] "Key": "service.name", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "loadgen.runQuery" [loadgen] } [loadgen] }, [loadgen] { [loadgen] "Key": "query", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "faith" [loadgen] } [loadgen] } [loadgen] ], [loadgen] "Events": null, [loadgen] "Links": null, [loadgen] "Status": { [loadgen] "Code": "Unset", [loadgen] "Description": "" [loadgen] }, [loadgen] "DroppedAttributes": 0, [loadgen] "DroppedEvents": 0, [loadgen] "DroppedLinks": 0, [loadgen] "ChildSpanCount": 5, [loadgen] "Resource": [ [loadgen] { [loadgen] "Key": "service.name", [loadgen] "Value": { [loadgen] "Type": "STRING", [loadgen] "Value": "unknown_service:loadgen" ...
這些訊息會由 stdout
匯出工具發出。您會發現 Loadgen 的所有時距的父項都有 TraceID: 00000000000000000000000000000000
,因為這是根時距,即追蹤記錄中的第一個時距。此外,您也發現嵌入屬性 "query"
含有要傳遞至用戶端服務的查詢字串。
摘要
在這個步驟中,您已檢測了載入產生器服務和用戶端服務,且這些服務可在 HTTP 中進行通訊,且您確認自己能在這兩項服務中順利傳播追蹤記錄內容,並將 Span 資訊匯出至 stdout。
下一步
在下一個步驟中,您將檢測用戶端服務和伺服器服務,以確認如何透過 gRPC 傳播 Trace Context。
5. gRPC 檢測
在前一個步驟中,我們已檢測了這項微服務中要求的前半部。在這個步驟中,我們會嘗試檢測用戶端服務和伺服器服務之間的 gRPC 通訊。(下圖中的綠色和紫色矩形)。
gRPC 用戶端的預先建構檢測功能
OpenTelemetry 的生態系統提供許多實用的程式庫,協助開發人員檢測應用程式。在上一個步驟中,我們針對 net/http
套件使用了預先建構檢測。在這個步驟中,由於我們嘗試透過 gRPC 傳播 Trace Context,因此會使用其程式庫。
首先,匯入名為 otelgrpc
的預先建構 gRPC 套件。
step0/src/client/main.go
import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "time" "opentelemetry-trace-codelab-go/client/shakesapp" // step2. add prebuilt gRPC package (otelgrpc) "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" )
這次用戶端服務是針對伺服器服務的 gRPC 用戶端,因此您必須檢測 gRPC 用戶端。找出 mustConnGRPC
函式並新增 gRPC 攔截器,讓用戶端每次向伺服器發出要求時,都能檢測新的時距。
step0/src/client/main.go
// Helper function for gRPC connections: Dial and create client once, reuse. func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) { var err error // step2. add gRPC interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) *conn, err = grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(interceptorOpt)), grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(interceptorOpt)), grpc.WithTimeout(time.Second*3), ) // step2: end adding interceptor if err != nil { panic(fmt.Sprintf("Error %s grpc: failed to connect %s", err, addr)) } }
您已在上一節設定 OpenTelemetry,因此不必進行這項程序。
為 gRPC 伺服器預先建構檢測功能
如同我們為 gRPC 用戶端的做法,我們呼叫了 gRPC 伺服器的預先建構檢測。將套件新增至匯入部分,例如:
step0/src/server/main.go
import ( "context" "fmt" "io/ioutil" "log" "net" "os" "regexp" "strings" "opentelemetry-trace-codelab-go/server/shakesapp" "cloud.google.com/go/storage" // step2. add OpenTelemetry packages including otelgrpc "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "google.golang.org/api/iterator" "google.golang.org/api/option" "google.golang.org/grpc" healthpb "google.golang.org/grpc/health/grpc_health_v1" )
由於這是第一次檢測伺服器,您需要先設定 OpenTelemetry,類似於我們為 loadgen 和用戶端服務所做的設定。
step0/src/server/main.go
// step2. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // create a stdout exporter to show collected spans out to stdout. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil } func main() { ... // step2. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step2. end setup ...
接著,您需要新增伺服器攔截器。在 main
函式中,找出呼叫 grpc.NewServer()
的位置,然後在函式中加入攔截器。
step0/src/server/main.go
func main() { ... svc := NewServerService() // step2: add interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) srv := grpc.NewServer( grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)), grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)), ) // step2: end adding interceptor shakesapp.RegisterShakespeareServiceServer(srv, svc) ...
執行微服務並確認追蹤記錄
接著使用 skaffold 指令執行修改過的程式碼。
skaffold dev
再次提醒您,stdout 顯示大量時距資訊。
指令輸出
... [server] { [server] "Name": "shakesapp.ShakespeareService/GetMatchCount", [server] "SpanContext": { [server] "TraceID": "89b472f213a400cf975e0a0041649667", [server] "SpanID": "96030dbad0061b3f", [server] "TraceFlags": "01", [server] "TraceState": "", [server] "Remote": false [server] }, [server] "Parent": { [server] "TraceID": "89b472f213a400cf975e0a0041649667", [server] "SpanID": "cd90cc3859b73890", [server] "TraceFlags": "01", [server] "TraceState": "", [server] "Remote": true [server] }, [server] "SpanKind": 2, [server] "StartTime": "2022-07-14T14:05:55.74822525Z", [server] "EndTime": "2022-07-14T14:06:03.449258891Z", [server] "Attributes": [ ... [server] ], [server] "Events": [ [server] { [server] "Name": "message", [server] "Attributes": [ ... [server] ], [server] "DroppedAttributeCount": 0, [server] "Time": "2022-07-14T14:05:55.748235489Z" [server] }, [server] { [server] "Name": "message", [server] "Attributes": [ ... [server] ], [server] "DroppedAttributeCount": 0, [server] "Time": "2022-07-14T14:06:03.449255889Z" [server] } [server] ], [server] "Links": null, [server] "Status": { [server] "Code": "Unset", [server] "Description": "" [server] }, [server] "DroppedAttributes": 0, [server] "DroppedEvents": 0, [server] "DroppedLinks": 0, [server] "ChildSpanCount": 0, [server] "Resource": [ [server] { ... [server] ], [server] "InstrumentationLibrary": { [server] "Name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", [server] "Version": "semver:0.33.0", [server] "SchemaURL": "" [server] } [server] } ...
您發現您尚未嵌入任何時距名稱,以及使用 trace.Start()
或 span.SpanFromContext()
手動建立 Span。您仍會取得大量時距,因為 gRPC 攔截器會產生這些時距。
摘要
在這個步驟中,您已完成與 OpenTelemetry 生態系統程式庫的支援,以檢測以 gRPC 為基礎的通訊。
下一步
在下一個步驟中,您會使用 Cloud Trace 以視覺化方式呈現追蹤記錄,並瞭解如何分析收集到的時距。
6. 使用 Cloud Trace 以圖表呈現追蹤記錄
您已使用 OpenTelemetry 檢測整個系統的追蹤記錄。您已瞭解如何檢測 HTTP 和 gRPC 服務。雖然您已學會如何檢測它們,但仍然還沒學到如何分析。在本節中,您將使用 Cloud Trace 匯出工具取代 stdout 匯出工具,並瞭解如何分析追蹤記錄。
使用 Cloud Trace 匯出工具
OpenTelemetry 的強大特色之一,就是可插入性。如要以視覺化方式呈現檢測作業收集的所有時距,只要將 stdout 匯出工具替換為 Cloud Trace 匯出工具即可。
開啟每項服務的 main.go
檔案,然後找出 initTracer()
函式。刪除該行程式碼來產生 stdout 匯出工具,並改為建立 Cloud Trace 匯出工具。
step0/src/loadgen/main.go
import ( ... // step3. add OpenTelemetry for Cloud Trace package cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" ) // step1. add OpenTelemetry initialization function func initTracer() (*sdktrace.TracerProvider, error) { // step3. replace stdout exporter with Cloud Trace exporter // cloudtrace.New() finds the credentials to Cloud Trace automatically following the // rules defined by golang.org/x/oauth2/google.findDefaultCredentailsWithParams. // https://pkg.go.dev/golang.org/x/oauth2/google#FindDefaultCredentialsWithParams exporter, err := cloudtrace.New() // step3. end replacing exporter if err != nil { return nil, err } // for the demonstration, we use AlwaysSmaple sampler to take all spans. // do not use this option in production. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.TraceContext{}) return tp, nil }
您也需要在用戶端和伺服器服務中編輯相同的函式。
執行微服務並確認追蹤記錄
編輯完成後,只要照常使用 skaffold 指令執行叢集即可。
skaffold dev
而現在,stdout 上不會以結構化記錄格式看見過多的時距資訊,因為您以 Cloud Trace 取代匯出工具。
指令輸出
[loadgen] 2022/07/14 15:01:07 simulated 20 requests [loadgen] 2022/07/14 15:01:07 simulating client requests, round 37 [loadgen] 2022/07/14 15:01:14 query 'sweet': matched 958 [client] 2022/07/14 15:01:14 {"match_count":958} [client] 2022/07/14 15:01:14 {"match_count":3040} [loadgen] 2022/07/14 15:01:14 query 'love': matched 3040 [client] 2022/07/14 15:01:15 {"match_count":349} [loadgen] 2022/07/14 15:01:15 query 'hello': matched 349 [client] 2022/07/14 15:01:15 {"match_count":484} [loadgen] 2022/07/14 15:01:15 query 'faith': matched 484 [loadgen] 2022/07/14 15:01:15 query 'insolence': matched 14 [client] 2022/07/14 15:01:15 {"match_count":14} [client] 2022/07/14 15:01:21 {"match_count":484} [loadgen] 2022/07/14 15:01:21 query 'faith': matched 484 [client] 2022/07/14 15:01:21 {"match_count":728} [loadgen] 2022/07/14 15:01:21 query 'world': matched 728 [client] 2022/07/14 15:01:22 {"match_count":484} [loadgen] 2022/07/14 15:01:22 query 'faith': matched 484 [loadgen] 2022/07/14 15:01:22 query 'hello': matched 349 [client] 2022/07/14 15:01:22 {"match_count":349} [client] 2022/07/14 15:01:23 {"match_count":1036} [loadgen] 2022/07/14 15:01:23 query 'friend': matched 1036 [loadgen] 2022/07/14 15:01:28 query 'tear': matched 463 ...
現在,讓我們確認所有時距是否已正確傳送至 Cloud Trace。請前往 Cloud 控制台,然後前往「Trace 清單」頁面。您可以直接透過搜尋框使用這項工具。否則,您可以按一下左側窗格中的選單。
然後,您可以看到延遲時間圖表中有許多藍色點。每個點都代表單一追蹤記錄。
點選其中一個參數,即可在追蹤記錄中查看詳細資料。
就算只是做個簡單的介面,您也已經掌握許多深入分析資料。舉例來說,從瀑布圖中發現,發生延遲的原因主要在於名為 shakesapp.ShakespeareService/GetMatchCount
的時距。(請參閱上圖中的 1) 您可以從摘要表格中確認。(最右側的資料欄則會顯示每個時距的時間長度)。而且,這個追蹤項目是針對「friend」查詢。(請見上圖 2)。
有鑑於這些短分析,您可能會注意到,您需要在 GetMatchCount
方法中更精細的時距。相較於 stdout 資訊,視覺化功能十分強大。如要進一步瞭解 Cloud Trace,請參閱官方說明文件。
摘要
在這個步驟中,您以 Cloud Trace 的 Cloud Trace 和視覺化追蹤記錄取代 stdout 匯出工具。此外,您也學會瞭如何開始分析追蹤記錄。
下一步
在下一個步驟中,您將修改伺服器服務的原始碼,以便在 GetMatchCount 中新增子時距。
7. 新增子時距來提升分析品質
在上一個步驟中,您已發現從 loadgen 觀察到往返時間的原因,主要是 GetMatchCount 方法 (伺服器服務中的 gRPC 處理常式) 內的程序。然而,由於我們尚未檢測處理常式,因此無法從刊登序列圖表取得進一步的深入分析。在我們開始檢測微服務時,就經常會發生這種情況。
在本節中,我們會檢測伺服器呼叫 Google Cloud Storage 的某個子時節,因為某些外部網路 I/O 在處理過程中會花費很長的時間,因此必須判斷呼叫是否為原因。
檢測伺服器中的子時距
在伺服器中開啟 main.go
並找到 readFiles
函式。這個函式會呼叫 Google Cloud Storage 的要求,以擷取莎士比亞的所有文字檔案。在這個函式中,您可以建立子時距,例如您在用戶端服務中對 HTTP 伺服器檢測執行的操作。
step0/src/server/main.go
func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) { type resp struct { s string err error } // step4: add an extra span span := trace.SpanFromContext(ctx) span.SetName("server.readFiles") span.SetAttributes(attribute.Key("bucketname").String(bucketName)) defer span.End() // step4: end add span ...
以上就是新增 Span。讓我們一起看看應用程式的運作方式。
執行微服務並確認追蹤記錄
編輯完成後,只要照常使用 skaffold 指令執行叢集即可。
skaffold dev
然後從追蹤記錄清單中選擇名為 query.request
的追蹤記錄。您會看到類似的追蹤記錄刊登序列圖表,但 shakesapp.ShakespeareService/GetMatchCount
下的新時距除外。(下方以紅色矩形包住的跨度)
從這張圖表中可以發現,對 Google Cloud Storage 的外部呼叫佔用大量延遲,但其他情況仍導致大部分的延遲。
透過追蹤記錄刊登序列圖表,只要查看數項分析,就能獲得許多深入分析資訊。如何在應用程式中取得更詳細的效能詳細資料?這裡提供分析器,但現在要完成本程式碼研究室,並將所有分析器教學課程委派至第 2 部分。
摘要
在這個步驟中,您已檢測伺服器服務中的另一個時距,並取得更多系統延遲時間的深入分析。
8. 恭喜
您已成功透過 OpenTelemery 建立分散式追蹤記錄,並確認 Google Cloud Trace 上微服務的要求延遲時間。
對於延長的運動,您可以自行嘗試下列主題。
- 目前的實作項目會傳送健康狀態檢查產生的所有時距。(
grpc.health.v1.Health/Check
) 您如何從 Cloud Trace 中篩除這些時距?提示請參閱這裡。 - 將事件記錄與時距建立關聯,並觀察記錄在 Google Cloud Trace 和 Google Cloud Logging 中的運作方式。提示請參閱這裡。
- 請將某些服務替換為其他語言,並嘗試以該語言的 OpenTelemetry 檢測服務。
此外,如果想進一步瞭解分析器,請前往第 2 部分。在這種情況下,您可以略過下方的清理作業一節。
清理
在這個程式碼研究室中,請停止 Kubernetes 叢集並確實刪除專案,以免在 Google Kubernetes Engine、Google Cloud Trace 和 Google Artifact Registry 中產生非預期的費用。
首先,請刪除叢集。如果您使用 skaffold dev
執行叢集,只要按下 Ctrl-C 鍵即可。如果您使用 skaffold run
執行叢集,請執行下列指令:
skaffold delete
指令輸出
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
刪除叢集後,請從選單窗格中選取「IAM 與管理員」>,然後按一下「SHUT DOWN」按鈕。
然後在對話方塊中的表單中輸入專案 ID (而非專案名稱),並確認關閉。