1. 簡介
上次更新時間:2022 年 7 月 15 日
應用程式的觀測能力
可觀測性和 OpenTelemetry
可觀察性是用來描述系統屬性的術語。具備可觀察性的系統可讓團隊主動對系統進行偵錯。在這個情況下,可觀察性有三個支柱:記錄、指標和追蹤,這些是系統取得可觀察性的基礎工具。
OpenTelemetry 是一組規格、程式庫和代理程式,可加快擷取及匯出觀測性所需的遙測資料 (記錄、指標和追蹤記錄)。OpenTelemetry 是 CNCF 旗下的開放標準和社群驅動專案。開發人員可以利用專案及其生態系統提供的程式庫,以供應商中立的方式,針對多個架構檢測應用程式。
除了可觀察性三大支柱之外,持續設定設定檔也是可觀察性的另一個重要元素,而且可擴大使用者群。Cloud Profiler 就是其中之一,它提供簡單的介面,可深入瞭解應用程式呼叫堆疊中的效能指標。
本程式碼研究室是系列課程的第 1 部分,將介紹如何使用 OpenTelemetry 和 Cloud Trace,在微服務中檢測分散式追蹤記錄。第 2 部分將介紹如何使用 Cloud Profiler 持續剖析。
分散式追蹤記錄
在記錄、指標和追蹤記錄中,追蹤記錄是用於說明系統中程序特定部分的延遲時間的遙測資料。尤其是在微服務時代,分散式追蹤功能是找出整體分散式系統中延遲瓶頸的強大動力。
分析分散式追蹤記錄時,追蹤記錄資料視覺化功能是掌握整體系統延遲時間的關鍵。在分散式追蹤記錄中,我們會處理一組呼叫,以便以包含多個跨度的追蹤記錄形式,處理系統進入點的單一要求。
Span 代表在分散式系統中完成的工作個別單位,會記錄開始和結束時間。跨度之間通常會有階層關係,在下圖中,所有較小的跨度都是大型 /messages 跨度的子跨度,並組合成一個追蹤記錄,顯示系統中的工作路徑。
Google Cloud Trace 是分散式追蹤後端的其中一個選項,可與 Google Cloud 中的其他產品完美整合。
建構項目
在本程式碼研究室中,您將在 Google Kubernetes Engine 叢集中執行的「莎士比亞應用程式」(又稱為 Shakesapp) 中,記錄追蹤資訊。Shakesapp 的架構如下所述:
- Loadgen 會透過 HTTP 將查詢字串傳送至用戶端
- 用戶端會透過 gRPC 將查詢從 loadgen 傳送至伺服器
- 伺服器會接受來自用戶端的查詢,從 Google Cloud Storage 擷取所有以文字格式呈現的莎士比亞作品,搜尋包含查詢的資料行,並傳回與用戶端相符的資料行編號
您將在要求中記錄追蹤資訊。接著,您會在伺服器中嵌入剖析器代理程式,並調查瓶頸。
課程內容
- 如何在 Go 專案中開始使用 OpenTelemetry Trace 程式庫
- 如何使用程式庫建立跨頁
- 如何在應用程式元件之間的連線中傳播跨區塊的內容
- 如何將追蹤資料傳送至 Cloud Trace
- 如何在 Cloud Trace 中分析追蹤記錄
本程式碼研究室會說明如何檢測微服務。為了方便說明,這個範例只包含 3 個元件 (負載產生器、用戶端和伺服器),但您可以將本程式碼研究室中說明的相同程序套用至更複雜和大型的系統。
軟硬體需求
- 具備 Go 的基本知識
- 具備 Kubernetes 的基本知識
2. 設定和需求
自助式環境設定
如果您還沒有 Google 帳戶 (Gmail 或 Google Apps),請務必建立帳戶。登入 Google Cloud Platform 主控台 ( console.cloud.google.com),然後建立新專案。
如果您已有專案,請按一下主控台左上方的專案選取下拉式選單:
然後按一下對話方塊中的「NEW PROJECT」(新專案) 按鈕,建立新專案:
如果您還沒有專案,系統會顯示類似下方的對話方塊,讓您建立第一個專案:
在後續的專案建立對話方塊中,您可以輸入新專案的詳細資料:
請記住專案 ID,這是所有 Google Cloud 專案的專屬名稱 (上述名稱已被使用,因此無法使用)。這個值會在本程式碼研究室的後續章節中稱為 PROJECT_ID。
接下來,如果您尚未這樣做,請在開發人員控制台中啟用帳單功能,以便使用 Google Cloud 資源並啟用 Cloud Trace API。
完成這個程式碼研究室的費用不應超過數美元,但如果您決定使用更多資源,或是將資源繼續執行,則可能會增加費用 (請參閱本文件結尾的「清理」一節)。Google Cloud Trace、Google Kubernetes Engine 和 Google Artifact Registry 的價格資訊,請參閱官方說明文件。
- Google Cloud 作業套件的計價方式 | 作業套件
- 定價 | Kubernetes Engine 說明文件
- Artifact Registry 定價 | Artifact Registry 說明文件
Google Cloud Platform 新使用者享有價值$300 美元的免費試用期,因此您應該可以免費使用本程式碼研究室。
Google Cloud Shell 設定
雖然 Google Cloud 和 Google Cloud Trace 可透過筆記型電腦遠端操作,但在本程式碼研究室中,我們會使用 Google Cloud Shell,這是在雲端運作的指令列環境。
這種以 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 叢集。接下來,我們將準備容器登錄,以便推送及部署容器。在這些步驟中,我們需要設定 Artifact Registry (GAR) 和 skaffold,以便使用這些項目。
Artifact Registry 設定
前往「Artifact Registry」選單,然後按下「ENABLE」按鈕。
片刻後,您會看到 GAR 的存放區瀏覽器。按一下「CREATE REPOSITORY」(建立存放區) 按鈕,然後輸入存放區名稱。
在本程式碼研究室中,我將新存放區命名為 trace-codelab
。構件的格式為「Docker」,位置類型為「區域」。請選擇靠近您為 Google Compute Engine 預設區域所設定的區域。舉例來說,這個範例選擇了上述的「us-central1-f」,因此我們會在此選擇「us-central1 (Iowa)」。然後按一下「建立」按鈕。
存放區瀏覽器現在會顯示「trace-codelab」。
我們稍後會再回到這裡檢查註冊表路徑。
Skaffold 設定
Skaffold 是建構在 Kubernetes 上執行的微服務時,相當實用的工具。它會透過一小組指令,處理建構、推送及部署應用程式容器的工作流程。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
- 為容器登錄建立 Artifact Registry 存放區
- 設定 skaffold 以使用容器登錄項
- 建立執行程式碼研究室微服務的 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 部署作業會自動啟動。
指令輸出
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
部署完成後,您會在每個容器中看到實際的應用程式記錄,如下所示:
指令輸出
[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 的檢測功能
追蹤檢測和傳播的概念
在編輯原始碼之前,讓我簡單說明分散式追蹤記錄在簡單圖表中的運作方式。
在這個範例中,我們會檢測程式碼,將追蹤和跨度資訊匯出至 Cloud Trace,並在從 loadgen 服務傳送至伺服器服務的要求中,散發追蹤內容。
應用程式必須傳送追蹤記錄中繼資料,例如追蹤 ID 和時距 ID,才能讓 Cloud Trace 將所有具有相同追蹤 ID 的時距組合成一個追蹤記錄。此外,應用程式還需要在要求的下游服務上傳播追蹤記錄內容 (追蹤記錄 ID 和父項範圍 ID 的組合),以便這些服務瞭解自己正在處理哪個追蹤記錄內容。
OpenTelemetry 可協助您:
- 產生專屬的 Trace ID 和 Span ID
- 匯出追蹤 ID 和跨度 ID 至後端
- 將追蹤記錄內容傳播至其他服務
- 嵌入額外中繼資料,協助分析追蹤記錄
OpenTelemetry Trace 中的元件
使用 OpenTelemetry 檢測應用程式追蹤記錄的程序如下:
- 建立匯出工具
- 建立 TracerProvider,將匯出工具繫結至 1,並將其設為全域。
- 設定 TextMapPropagator 以設定傳播方法
- 從 TracerProvider 取得 Tracer
- 從追蹤器產生 Span
目前您不需要瞭解每個元件的詳細屬性,但請務必記住以下幾點:
- 此處的匯出工具可插入 TracerProvider
- TracerProvider 會保留所有與追蹤記錄取樣和匯出相關的設定
- 所有追蹤記錄都會整合在 Tracer 物件中
瞭解這一點後,我們就來進行實際的程式碼編寫工作。
Instrument 第一個時距
檢測負載產生器服務
按下 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) ...
設定完成後,您必須建立 Span,並使用專屬的追蹤 ID 和 Span ID。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" )
由於負載產生器會在 runQuery
函式中使用 net/http
呼叫 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.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
Instrument 客戶服務
在上一節中,我們對下圖中紅色矩形所圈起的部分進行檢測。我們在負載產生器服務中檢測了跨度資訊。與負載產生器服務類似,現在我們需要檢測用戶端服務。與負載產生器服務不同的是,用戶端服務需要在 HTTP 標頭中擷取從負載產生器服務傳播的追蹤 ID 資訊,並使用該 ID 產生 Span。
開啟 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 實作,您可以像在 loadgen 中一樣使用 otelhttp
套件。
首先,我們將處理常式註冊項目替換為 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)
接著,我們會在處理常式中檢測實際的時間間隔。找出 func (*clientService) handler(),然後使用 trace.SpanFromContext()
新增跨區檢測。
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 傳播追蹤記錄內容。
5. gRPC 的檢測功能
在前一個步驟中,我們在這個微服務中檢測了請求的前半部。在這個步驟中,我們會嘗試在用戶端服務和伺服器服務之間進行 gRPC 通訊。(如下圖中的綠色和紫色矩形)
gRPC 用戶端的預先建構檢測作業
OpenTelemetry 生態系統提供許多實用的程式庫,可協助開發人員檢測應用程式。在先前的步驟中,我們使用了 net/http
套件的預先建構檢測功能。在這個步驟中,我們會嘗試透過 gRPC 傳播追蹤記錄內容,因此會使用該程式庫。
首先,您要匯入名為 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()
手動建立區間。不過,您還是會收到大量時距,因為 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
由於您已將匯出工具替換為 Cloud Trace 匯出工具,因此現在在 stdout 上,您不會看到太多以結構化記錄格式顯示的時距資訊。
指令輸出
[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 list」(追蹤記錄清單)。你可以輕鬆透過搜尋框存取這項功能。或者,您也可以按一下左側窗格的選單。
您會看到延遲圖表中分布著許多藍色圓點。每個點代表單一追蹤記錄。
按一下其中一個,即可查看追蹤記錄中的詳細資料。
就算只快速瀏覽一下,您也能掌握許多洞察資料。舉例來說,從瀑布圖中,您可以看到延遲的原因大多是因為名為 shakesapp.ShakespeareService/GetMatchCount
的跨度。(請參閱上圖中的 1)。您可以從摘要表格確認這一點。(最右欄會顯示每個時間間隔的時間長度)。此外,這份追蹤記錄是針對「friend」查詢。(請參閱上圖中的 2)
根據這些簡短的分析,您可能會發現需要進一步瞭解 GetMatchCount
方法內的細微時間間隔。與標準輸出資訊相比,視覺化功能更強大。如要進一步瞭解 Cloud Trace 的詳細資訊,請參閱官方說明文件。
摘要
在這個步驟中,您已將 stdout 匯出工具替換為 Cloud Trace 匯出工具,並在 Cloud Trace 上以視覺化方式呈現追蹤記錄。您也已瞭解如何開始分析追蹤記錄。
下一步
在下一個步驟中,您將修改伺服器服務的來源程式碼,在 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. 恭喜
您已成功使用 OpenTelemetry 建立分散式追蹤記錄,並在 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 與管理員」>「設定」,然後按一下「關閉」按鈕。
接著,在對話方塊中的表單中輸入專案 ID (而非專案名稱),然後確認關閉。