本教學課程說明如何使用 Stackdriver 工具進行探索,以及使用本機開發工作流程進行調查,排解 Knative 服務中斷的問題。
這份逐步「案例研究」是疑難排解指南的輔助文件,會使用範例專案,說明如何排解部署時發生的執行階段錯誤,找出並修正問題。
目標
- 撰寫、建構服務,並將服務部署到 Knative serving
- 使用 Cloud Logging 找出錯誤
- 從 Container Registry 擷取容器映像檔,進行根本原因分析
- 修正「生產」服務,然後改善服務,以減少日後發生問題的機率
費用
在本文件中,您會使用 Google Cloud的下列計費元件:
如要根據預測用量估算費用,請使用 Pricing Calculator。
事前準備
- 本教學課程假設您已在叢集上安裝及設定 Knative 服務。
- 確認已設定指令列環境,且工具為最新版本:
- Google Cloud 上的 GKE 叢集
- 外部 GKE 叢集 Google Cloud
- 安裝 curl 以便試用服務。
- 在本機安裝 Docker。
組裝程式碼
逐步建構新的 Knative serving 問候語服務。 提醒您,這項服務會刻意建立執行階段錯誤,以供疑難排解練習使用。
建立新專案:
Node.js
定義服務套件、初始依附元件和一些常見作業,建立 Node.js 專案。建立
hello-service
目錄:mkdir hello-service cd hello-service
產生
package.json
檔案:npm init --yes npm install express@4
在編輯器中開啟新的
package.json
檔案,並設定要執行node index.js
的start
指令碼。完成後,檔案會如下所示:{ "name": "hello-service", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1" } }
如果您在本教學課程結束後,仍持續開發這項服務,請考慮填寫說明、作者,並評估授權。詳情請參閱 package.json 說明文件。
Python
建立新的
hello-service
目錄:mkdir hello-service cd hello-service
建立 requirements.txt 檔案,並將依附元件複製到該檔案中:
Go
建立
hello-service
目錄:mkdir hello-service cd hello-service
初始化新的 Go 模組,建立 Go 專案:
go mod init <var>my-domain</var>.com/hello-service
您可以視需要更新特定名稱:如果程式碼發布至可透過網路存取的程式碼存放區,就應該更新名稱。
Java
建立 Maven 專案:
mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=hello-service \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false
將依附元件複製到
pom.xml
依附元件清單 (介於<dependencies>
元素之間):將建構設定複製到
pom.xml
(位於<dependencies>
元素下方):
建立 HTTP 服務來處理連入要求:
Node.js
Python
Go
Java
建立
Dockerfile
,定義用於部署服務的容器映像檔:Node.js
Python
Go
Java
本範例使用 Jib,透過常見的 Java 工具建構 Docker 映像檔。Jib 可最佳化容器建構作業,不需要 Dockerfile,也不必安裝 Docker。進一步瞭解如何使用 Jib 建構 Java 容器。
推送程式碼
推送程式碼包含三個步驟:使用 Cloud Build 建構容器映像檔、將容器映像檔上傳到 Container Registry,然後將容器映像檔部署到 Knative serving。
如要推送程式碼:
建構容器並發布至 Container Registry:
Node.js
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用
gcloud config get-value project
檢查目前的專案 ID。若成功執行,您應會看到包含 ID、建立時間和映像檔名稱的「SUCCESS」(成功) 訊息。映像檔儲存在 Container Registry 中,日後如有需要,可以重複使用。
Python
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用
gcloud config get-value project
檢查目前的專案 ID。若成功執行,您應會看到包含 ID、建立時間和映像檔名稱的「SUCCESS」(成功) 訊息。映像檔儲存在 Container Registry 中,日後如有需要,可以重複使用。
Go
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用
gcloud config get-value project
檢查目前的專案 ID。若成功執行,您應會看到包含 ID、建立時間和映像檔名稱的「SUCCESS」(成功) 訊息。映像檔儲存在 Container Registry 中,日後如有需要,可以重複使用。
Java
mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/hello-service
其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用
gcloud config get-value project
檢查目前的專案 ID。成功後,您應該會看到「BUILD SUCCESS」(建構成功) 訊息。映像檔會儲存在 Container Registry 中,日後如有需要,可以重複使用。
執行下列指令來部署您的應用程式:
gcloud run deploy hello-service --image gcr.io/PROJECT_ID/hello-service
將 PROJECT_ID 替換為專案 ID。 Google Cloud
hello-service
是容器映像檔名稱,也是 Knative 服務服務的名稱。請注意,容器映像檔是部署到您之前在設定 gcloud 中設定的服務和叢集。請等待部署完成,這可能需要半分鐘的時間。 成功完成後,指令列會顯示服務網址。
立即體驗
試用服務,確認您已成功部署。要求應會失敗,並顯示 HTTP 500 或 503 錯誤 (5xx 伺服器錯誤類別的成員)。本教學課程將逐步說明如何排解這項錯誤回應。
如果叢集已設定可路由的預設網域,請略過上述步驟,改為將網址複製到網頁瀏覽器。
如果您未使用自動 TLS 憑證和網域對應,系統就不會提供服務的可瀏覽網址。
請改用提供的網址和服務的 Ingress 閘道 IP 位址,建立可對服務提出要求的 curl
指令:
-
如要取得負載平衡器的外部 IP,請執行下列指令:
kubectl get svc istio-ingressgateway -n ASM-INGRESS-NAMESPACE
將 ASM-INGRESS-NAMESPACE 替換為 Cloud Service Mesh Ingress 所在的命名空間。如果您使用預設設定安裝 Cloud Service Mesh,請指定
istio-system
。輸出結果類似如下:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) istio-ingressgateway LoadBalancer XX.XX.XXX.XX pending 80:32380/TCP,443:32390/TCP,32400:32400/TCP
其中,EXTERNAL-IP 值是負載平衡器的外部 IP 位址。
使用網址中的
GATEWAY_IP
位址來執行curl
指令。curl -G -H "Host: SERVICE-DOMAIN" https://EXTERNAL-IP/
將 SERVICE-DOMAIN 替換為服務的預設指派網域。取得方式是採用預設網址,然後移除通訊協定
http://
。看到 HTTP 500 或 HTTP 503 錯誤訊息。
調查問題
請注意,上述「試用」中遇到的 HTTP 5xx 錯誤,是正式環境執行階段錯誤。本教學課程將逐步說明處理這類問題的正式程序。雖然解決製作錯誤的程序差異很大,但本教學課程會介紹特定步驟順序,說明如何運用實用工具和技術。
如要調查這個問題,請完成下列階段:
- 收集回報錯誤的詳細資料,以利進一步調查並制定緩解策略。
- 決定繼續修正或復原至已知正常版本,減輕對使用者的影響。
- 重現錯誤,確認已收集正確的詳細資料,且錯誤並非一次性故障
- 對錯誤執行根本原因分析,找出造成錯誤的程式碼、設定或程序
調查開始時,您會看到網址、時間戳記和「Internal Server Error」訊息。
收集更多詳細資料
收集更多問題相關資訊,瞭解實際情況並決定後續步驟。
使用可用工具收集更多詳細資料:
詳情請查看記錄檔。
使用 Cloud Logging 檢查導致問題的操作順序,包括錯誤訊息。
復原至正常版本
如果您知道某個修訂版本可以正常運作,可以將服務回溯至該版本。舉例來說,您無法對本教學課程中部署的新 hello-service
服務執行回溯作業,因為該服務只包含一個修訂版本。
如要找出修訂版本並回溯服務,請按照下列指示操作:
重現錯誤
使用先前取得的詳細資料,確認問題是否持續在測試條件下發生。
再次試用並傳送相同的 HTTP 要求,查看是否回報相同的錯誤和詳細資料。錯誤詳細資料可能需要一段時間才會顯示。
由於本教學課程中的範例服務是唯讀服務,不會觸發任何複雜的副作用,因此在實際工作環境中重現錯誤是安全的。不過,許多實際服務並非如此:您可能需要在測試環境中重現錯誤,或將這個步驟限制在本地調查。
重現錯誤可為後續工作建立背景資訊。舉例來說,如果開發人員無法重現錯誤,可能需要對服務進行額外檢測,才能進一步調查。
執行根本原因分析
根本原因分析是有效疑難排解的重要步驟,可確保您修正問題,而非只是解決症狀。
在本教學課程中,您先前已在 Knative serving 上重現問題,確認服務在 Knative serving 上代管時,問題確實會發生。現在請在本機重現問題,判斷問題是否與程式碼無關,或只會在實際工作環境主機中發生。
如果您尚未在本機使用 Docker CLI 搭配 Container Registry,請使用 gcloud 驗證:
gcloud auth configure-docker
如需替代方法,請參閱「Container Registry 驗證方法」。
如果無法取得最近使用的容器映像檔名稱,服務說明會提供最近部署的容器映像檔資訊:
gcloud run services describe hello-service
在
spec
物件中找出容器映像檔名稱。更精確的指令可以直接擷取該值:gcloud run services describe hello-service \ --format="value(spec.template.spec.containers.image)"
這個指令會顯示容器映像檔名稱,例如
gcr.io/PROJECT_ID/hello-service
。從 Container Registry 將容器映像檔提取至環境,下載容器映像檔可能需要幾分鐘:
docker pull gcr.io/PROJECT_ID/hello-service
日後更新容器映像檔時,只要沿用這個名稱,就能使用相同指令擷取映像檔。如果略過這個步驟,下方的
docker run
指令會提取容器映像檔 (如果本機電腦上沒有的話)。在本機執行,確認問題並非 Knative 服務特有:
PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \ gcr.io/PROJECT_ID/hello-service
上述指令的元素可細分為:
- 服務會使用
PORT
環境變數,判斷要在容器內監聽的通訊埠。 run
指令會啟動容器,預設為 Dockerfile 或父項容器映像檔中定義的進入點指令。--rm
旗標會在結束時刪除容器執行個體。-e
旗標會為環境變數指派值。-e PORT=$PORT
正在將PORT
變數從本機系統傳播到容器中, 且變數名稱相同。-p
標記會將容器發布為服務,並在通訊埠 9000 的本機主機上提供。系統會將對 localhost:9000 的要求,轉送至容器的 8080 通訊埠。這表示服務輸出的使用中連接埠號碼,與服務的存取方式不符。- 最後一個引數
gcr.io/PROJECT_ID/hello-service
是指向最新版容器映像的存放區路徑。如果本機沒有,Docker 會嘗試從遠端登錄檔擷取映像檔。
在瀏覽器中開啟 http://localhost:9000。檢查終端機輸出內容,查看與 Google Cloud Observability 上的錯誤訊息相符的訊息。
如果問題無法在本機重現,可能是 Knative 服務環境特有的問題。請參閱 Knative serving 疑難排解指南,瞭解要調查的特定領域。
在這種情況下,錯誤會在本地重現。
- 服務會使用
現在已確認錯誤持續發生,且是由服務程式碼而非代管平台所致,因此接下來要更仔細地檢查程式碼。
在本教學課程中,您可以放心地假設容器內的程式碼與本機系統中的程式碼相同。
Node.js
在index.js
檔案中,找出記錄檔中堆疊追蹤記錄所指出行號附近的錯誤訊息來源:
Python
在main.py
檔案中,找出記錄檔中堆疊追蹤記錄所指出行號附近的錯誤訊息來源:
Go
在 main.go
檔案中,找出記錄中顯示的堆疊追蹤記錄所指出行號附近的錯誤訊息來源:
Java
在記錄中顯示的堆疊追蹤記錄中,找出 App.java
附近行號的錯誤訊息來源:
檢查這段程式碼後,會發現如果未設定 NAME
環境變數,系統會採取下列行動:
- 錯誤會記錄到 Google Cloud Observability
- 傳送 HTTP 錯誤回應
問題是由於缺少變數所致,但根本原因更具體:程式碼變更會新增環境變數的硬性依附元件,但未包含部署指令碼和執行階段需求文件中的相關變更。
修正根本原因
收集程式碼並找出潛在根本原因後,我們就能採取步驟修正問題。
檢查服務是否在本機運作,並提供
NAME
環境:在本機執行容器,並新增環境變數:
PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \ -e NAME="Local World!" \ gcr.io/PROJECT_ID/hello-service
在瀏覽器中前往 http://localhost:9000
看到頁面顯示「Hello Local World!」
修改正在執行的 Knative serving 服務環境,加入這個變數:
執行服務更新指令,並使用
--update-env-vars
參數新增環境變數:gcloud run services update hello-service \ --update-env-vars NAME=Override
等待幾秒,Knative serving 會根據先前的修訂版本建立新的修訂版本,並新增環境變數。
確認服務已修復:
- 在瀏覽器中前往 Knative serving 服務網址。
- 頁面上會顯示「Hello Override!」。
- 確認 Cloud Logging 中沒有任何非預期的訊息或錯誤。
提升日後的疑難排解速度
在這個生產問題範例中,錯誤與作業設定有關。我們將進行程式碼變更,盡量減少日後發生這個問題的影響。
- 改善錯誤記錄,加入更具體的詳細資料。
- 服務應改為回溯至安全預設值,而非傳回錯誤。如果使用預設值會導致正常功能發生變化,請使用警告訊息進行監控。
讓我們逐步移除 NAME
環境變數,做為硬性依附元件。
移除現有的
NAME
處理程式碼:Node.js
Python
Go
Java
新增設定備用值的新程式碼:
Node.js
Python
Go
Java
透過受影響的設定案例,重新建構及執行容器,在本機進行測試:
Node.js
docker build --tag gcr.io/PROJECT_ID/hello-service .
Python
docker build --tag gcr.io/PROJECT_ID/hello-service .
Go
docker build --tag gcr.io/PROJECT_ID/hello-service .
Java
mvn compile jib:build
確認
NAME
環境變數仍可運作:PORT=8080 && docker run --rm -e $PORT -p 9000:$PORT \ -e NAME="Robust World" \ gcr.io/PROJECT_ID/hello-service
確認服務在沒有
NAME
變數的情況下也能運作:PORT=8080 && docker run --rm -e $PORT -p 9000:$PORT \ gcr.io/PROJECT_ID/hello-service
如果服務未傳回結果,請確認移除第一個步驟中的程式碼時,是否也移除了額外行數,例如用於撰寫回應的行數。
-
每次部署到服務都會建立一個新的修訂版本,並會在就緒時自動開始處理流量。
如要清除先前設定的環境變數,請執行下列操作:
gcloud run services update hello-service --clear-env-vars
將預設值的新功能新增至服務的自動化測試涵蓋範圍。
在記錄中尋找其他問題
您可能會在這項服務的記錄檢視器中看到其他問題。舉例來說,記錄檔會將不支援的系統呼叫顯示為「Container Sandbox Limitation」。
舉例來說,Node.js 服務有時會產生下列記錄訊息:
Container Sandbox Limitation: Unsupported syscall statx(0xffffff9c,0x3e1ba8e86d88,0x0,0xfff,0x3e1ba8e86970,0x3e1ba8e86a90). Please, refer to https://gvisor.dev/c/linux/amd64/statx for more information.
在本例中,缺少支援不會影響 hello-service 服務範例。
清除所用資源
您可以刪除為本教學課程建立的資源,以免產生費用。
刪除教學課程資源
刪除您在本教學課程中部署的 Knative Serving 服務:
gcloud run services delete SERVICE-NAME
其中 SERVICE-NAME 是您選擇的服務名稱。
您也可以從Google Cloud 控制台刪除 Knative 服務:
移除您在教學課程設定期間新增的 gcloud 預設設定:
gcloud config unset run/platform gcloud config unset run/cluster gcloud config unset run/cluster_location
移除專案設定:
gcloud config unset project
刪除在本教學課程中建立的其他 Google Cloud 資源:
- 從 Container Registry 刪除名為
gcr.io/<var>PROJECT_ID</var>/hello-service
的容器映像檔。 - 如果您為本教學課程建立了叢集,請刪除該叢集。
- 從 Container Registry 刪除名為
後續步驟
- 進一步瞭解如何使用 Cloud Logging 深入瞭解生產環境行為。
- 如要進一步瞭解如何排解 Knative serving 問題,請參閱這篇文章。
- 探索 Google Cloud 的參考架構、圖表和最佳做法。 歡迎瀏覽我們的雲端架構中心。