GSP761
總覽
在「Serverless Cloud Run Development」課程的實驗室中,您要支援一間虛擬的企業,並輔助其中角色進行無伺服器遷移計畫。
小莉在 12 年前設立了連鎖獸醫診所 Pet Theory。隨著連鎖診所業務的成長,小莉花在與保險公司通話的時間,比醫療寵物的時間還要長。是否有辦法讓保險公司在網路上查看療程的總費用呢?
在先前本系列的其他實驗室中,小茹 (軟體顧問) 和阿克 (開發運作工程師) 將 Pet Theory 的顧客資料庫移至雲端的無伺服器 Firestore 資料庫,並且開放存取權,讓顧客可以在線上預約服務。Pet Theory 的營運團隊只有一個人,因此他們需要不必經常持續維護的無伺服器解決方案。
在本實驗室中,您會協助小茹和阿克將客戶資料存取權授予保險公司,但不公開個人識別資訊 (PII)。您將使用 Cloud Run 建構無伺服器且安全的「具象狀態傳輸」(REST) API 閘道。這樣一來,保險公司就能看到治療的總費用,且看不到顧客的 PII 資訊。
目標
本實驗室的學習內容如下:
- 使用 Go 開發 REST API
- 將測試用客戶資料匯入 Firestore
- 將 REST API 連線至 Firestore 資料庫
- 將 REST API 部署到 Cloud Run
先備知識
本實驗室的難度為中級,亦即假設您已熟悉 Cloud 控制台和 Cloud Shell 環境。本實驗室屬於一系列實驗室之一。參加先前的實驗室會對您有幫助,但並非必要:
- 將資料匯入無伺服器資料庫
- 使用 Firebase 和 Firestore 建構無伺服器網頁應用程式
- 建構可建立 PDF 檔案的無伺服器應用程式
設定和需求
點選「Start Lab」按鈕前的須知事項
請詳閱以下操作說明。研究室活動會計時,而且中途無法暫停。點選「Start Lab」 後就會開始計時,讓您瞭解有多少時間可以使用 Google Cloud 資源。
您將在真正的雲端環境中完成實作研究室活動,而不是在模擬或示範環境。為達此目的,我們會提供新的暫時憑證,讓您用來在研究室活動期間登入及存取 Google Cloud。
如要完成這個研究室活動,請先確認:
- 您可以使用標準的網際網路瀏覽器 (Chrome 瀏覽器為佳)。
注意:請使用無痕模式或私密瀏覽視窗執行此研究室。這可以防止個人帳戶和學生帳戶之間的衝突,避免個人帳戶產生額外費用。
- 是時候完成研究室活動了!別忘了,活動一開始將無法暫停。
注意:如果您擁有個人 Google Cloud 帳戶或專案,請勿用於本研究室,以免產生額外費用。
如何開始研究室及登入 Google Cloud 控制台
-
按一下「Start Lab」(開始研究室) 按鈕。如果研究室會產生費用,畫面中會出現選擇付款方式的彈出式視窗。左側的「Lab Details」窗格會顯示下列項目:
- 「Open Google Cloud console」按鈕
- 剩餘時間
- 必須在這個研究室中使用的暫時憑證
- 完成這個實驗室所需的其他資訊 (如有)
-
點選「Open Google Cloud console」;如果使用 Chrome 瀏覽器,也能按一下滑鼠右鍵,然後選取「在無痕式視窗中開啟連結」。
接著,實驗室會啟動相關資源並開啟另一個分頁,當中顯示「登入」頁面。
提示:您可以在不同的視窗中並排開啟分頁。
注意:如果頁面中顯示「選擇帳戶」對話方塊,請點選「使用其他帳戶」。
-
如有必要,請將下方的 Username 貼到「登入」對話方塊。
{{{user_0.username | "Username"}}}
您也可以在「Lab Details」窗格找到 Username。
-
點選「下一步」。
-
複製下方的 Password,並貼到「歡迎使用」對話方塊。
{{{user_0.password | "Password"}}}
您也可以在「Lab Details」窗格找到 Password。
-
點選「下一步」。
重要事項:請務必使用實驗室提供的憑證,而非自己的 Google Cloud 帳戶憑證。
注意:如果使用自己的 Google Cloud 帳戶來進行這個實驗室,可能會產生額外費用。
-
按過後續的所有頁面:
- 接受條款及細則。
- 由於這是臨時帳戶,請勿新增救援選項或雙重驗證機制。
- 請勿申請免費試用。
Google Cloud 控制台稍後會在這個分頁開啟。
注意:如要查看列出 Google Cloud 產品和服務的選單,請點選左上角的「導覽選單」。
啟動 Cloud Shell
Cloud Shell 是搭載多項開發工具的虛擬機器,提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作。Cloud Shell 提供指令列存取權,方便您使用 Google Cloud 資源。
- 點按 Google Cloud 控制台上方的「啟用 Cloud Shell」圖示 。
連線完成即代表已通過驗證,且專案已設為您的 PROJECT_ID。輸出內容中有一行宣告本工作階段 PROJECT_ID 的文字:
您在本工作階段中的 Cloud Platform 專案會設為「YOUR_PROJECT_ID」
gcloud
是 Google Cloud 的指令列工具,已預先安裝於 Cloud Shell,並支援 Tab 鍵自動完成功能。
- (選用) 您可以執行下列指令來列出使用中的帳戶:
gcloud auth list
-
點按「授權」。
-
輸出畫面應如下所示:
輸出內容:
ACTIVE: *
ACCOUNT: student-01-xxxxxxxxxxxx@qwiklabs.net
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- (選用) 您可以使用下列指令來列出專案 ID:
gcloud config list project
輸出內容:
[core]
project = <project_ID>
輸出內容範例:
[core]
project = qwiklabs-gcp-44776a13dea667a6
附註:如需有關 gcloud
的完整說明,請前往 Google Cloud 並參閱「gcloud CLI overview guide」(gcloud CLI 總覽指南)。
小莉 (Pet Theory 創辦人)
|
小茹,妳好:
妳記得我上週跟妳說過,我快被保險公司的書面文件和電話淹沒了嗎?有沒有辦法能讓保險公司代表以有效且安全的方式,存取顧客記錄?
目前的工作負荷程度不太能長期保持下去。妳可以幫幫我嗎?
小莉
|
小茹 (軟體顧問)
|
小莉,妳好:
我昨天和阿克吃了一頓午餐,我們擬定了一項計畫,可以輕鬆讓授權的第三方安全地存取 Pet Theory 的數位記錄。
這項方法有四個步驟:
- 建構簡易的 REST API。
- 匯入客戶測試資料。
- 將 REST API 連線至顧客資料庫。
- 為 REST API 新增驗證機制。
阿克和我已經具備步驟 1 和 2 所需的技能了,所以我們有不錯的起點。我們計畫在本週末前有一個工作原型。
小茹
|
請協助小茹,管理為 Pet Theory 建構 REST API 所需的活動。
工作 1:啟用 Google API
本實驗室已為您啟用 2 個 API:
名稱 |
API |
Cloud Build |
cloudbuild.googleapis.com |
Cloud Run |
run.googleapis.com |
工作 2:開發 REST API
- 啟用專案:
gcloud config set project $(gcloud projects list --format='value(PROJECT_ID)' --filter='qwiklabs-gcp')
- 複製 pet-theory 存放區並存取原始碼:
git clone https://github.com/rosera/pet-theory.git && cd pet-theory/lab08
-
使用您常用的文字編輯器,或 Cloud Shell 工具列的「程式碼編輯器」按鈕,查看 go.mod
和 go.sum
檔案。
-
建立 main.go
檔案,並在其中新增以下內容:
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "{status: 'running'}")
})
log.Println("Pets REST API listening on port", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Error launching Pets REST API server: %v", err)
}
}
注意:
以上的程式碼會建立端點,用於測試服務是否正常運作。在服務網址中附加「/v1/」,即可驗證應用程式是否正常運作。Cloud Run 會部署容器,因此您必須提供容器定義。「Dockerfile」檔案會告知 Cloud Run 要使用哪個 Go 版本、要在應用程式中加入哪些檔案,以及如何啟動程式碼。
- 接著建立
Dockerfile
檔案,並在其中加入下列內容:
FROM gcr.io/distroless/base-debian12
WORKDIR /usr/src/app
COPY server .
CMD [ "/usr/src/app/server" ]
server
檔案是從 main.go
建構的執行二進位檔。
- 執行下列指令,建構二進位檔:
go build -o server
- 執行建構指令後,請確認必要的 Dockerfile 和伺服器位於相同目錄中:
ls -la
.
├── Dockerfile
├── go.mod
├── go.sum
├── main.go
└── server
大部分以 Go 語言編寫的 Cloud Run 應用程式,不必修改上述的 Dockerfile 範本就能直接使用。
- 執行下列指令,部署簡易的 REST API:
gcloud builds submit \
--tag gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.1
這個指令會使用您的程式碼建構容器,並將其放入專案的 Container Registry。依序點選「導覽選單」>「Container Registry」,即可看到容器。如果沒有看到 rest-api
,請按一下「重新整理」。
點選「Check my progress」,確認上述工作已完成。
使用 Cloud Build 建構映像檔
- 如果容器已建構,請部署容器:
gcloud run deploy rest-api \
--image gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.1 \
--platform managed \
--region {{{ project_0.default_region | "Filled in at lab startup." }}} \
--allow-unauthenticated \
--max-instances=2
- 部署作業完成後,您會看到類似下方的訊息:
Service [rest-api] revision [rest-api-00001] has been deployed and is serving
traffic at https://rest-api-[hash].a.run.app
點選「Check my progress」,確認上述工作已完成。
已部署 REST API 服務
- 按一下訊息結尾的服務網址,在新瀏覽器分頁中開啟。將
/v1/
附加至網址結尾,然後按下 Enter 鍵。
您應該會看到下列訊息:
REST API 正常運作中。透過可用的原型服務,我們將在下一節使用 API 擷取 Firestore 資料庫的「顧客」資訊。
工作 3:匯入測試用客戶資料
軟體顧問小茹
|
阿克,你好:
你還有我們不久前建立的虛擬客戶資料嗎?我們要用這些資料來測試。
你記得如何設定 Firestore 資料庫並匯入資料嗎?
小茹
|
IT 管理員阿克
|
小茹,妳好:
我還有測試資料。我今天會將資料移到 Firestore 供妳測試。
阿克
|
小茹和阿克先前建立了 10 位顧客的測試資料庫,內含某位顧客貓咪的一些療程提案。
請幫阿克設定 Firestore 資料庫,並且匯入顧客測試資料。首先,在專案中啟用 Firestore。
-
返回 Cloud 控制台,然後依序點選「導覽選單」>「Firestore」。
-
按一下「建立資料庫」按鈕。
-
按一下「原生模式」按鈕,然後點選「繼續」。
-
在「位置類型」部分,選取「區域」。
-
從可用清單中選取區域,然後點選「建立資料庫」。
繼續操作前,請先等待資料庫建立完成。
點選「Check my progress」,確認上述工作已完成。
已建立 Firestore 資料庫
- 將匯入檔案遷移至系統為您建立的 Cloud Storage bucket:
gsutil mb -c standard -l {{{ project_0.default_region | Region }}} gs://$GOOGLE_CLOUD_PROJECT-customer
gsutil cp -r gs://spls/gsp645/2019-10-06T20:10:37_43617 gs://$GOOGLE_CLOUD_PROJECT-customer
- 接著將此檔案匯入 Firebase:
gcloud beta firestore import gs://$GOOGLE_CLOUD_PROJECT-customer/2019-10-06T20:10:37_43617/
重新載入 Cloud 控制台瀏覽器,查看 Firestore 結果。
- 在 Firestore 中,按一下「預設」下方的「客戶」。您應該會看到已匯入的寵物資料。如果沒有看到任何資料,請嘗試重新整理頁面。
做得好,您已成功建立 Firestore 資料庫,並匯入測試資料!
工作 4:將 REST API 連線至 Firestore 資料庫
小茹 (軟體顧問)
|
小莉,妳好:
只是簡單更新一下進度,阿克和我已經完成清單中的前兩項工作了。
現在我要開始建構 REST API,以存取 Firestore 中的客戶資料。
小茹
|
小莉 (Pet Theory 創辦人)
|
小茹,妳好:
好極了!小茹。期待看到妳在下一個階段的進展。
小莉
|
在本節中,您要幫小茹在 REST API 中建立另一個端點,如下所示:
https://rest-api-[hash].a.run.app/v1/customer/22530
舉例來說,如果 Firestore 資料庫中有 ID 為 22530 的顧客,該網址應會傳回該顧客所有已提案、接受、及拒絕的療程總數。
{
"status": "success",
"data": {
"proposed": 1602,
"approved": 585,
"rejected": 489
}
}
注意:如果資料庫中沒有該顧客,系統應會改為傳回 狀態碼 404 (找不到) 及錯誤訊息。
這個新功能需要使用一個套件存取 Firestore 資料庫,並使用另一個套件處理跨源資源共享。
- 取得 $GOOGLE_CLOUD_PROJECT 環境變數的值
echo $GOOGLE_CLOUD_PROJECT
- 在 pet-theory/lab08 目錄中,開啟現有的
main.go
檔案。
注意:請將 main.go 檔案中的值,更新為 $GOOGLE_CLOUD_PROJECT 顯示的值。
- 將檔案內容替換成下列程式碼,確保
PROJECT_ID
已設為 :
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"cloud.google.com/go/firestore"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"google.golang.org/api/iterator"
)
var client *firestore.Client
func main() {
var err error
ctx := context.Background()
client, err = firestore.NewClient(ctx, "{{{ project_0.project_id | \"Filled in at lab startup\"}}}")
if err != nil {
log.Fatalf("Error initializing Cloud Firestore client: %v", err)
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
r := mux.NewRouter()
r.HandleFunc("/v1/", rootHandler)
r.HandleFunc("/v1/customer/{id}", customerHandler)
log.Println("Pets REST API listening on port", port)
cors := handlers.CORS(
handlers.AllowedHeaders([]string{"X-Requested-With", "Authorization", "Origin"}),
handlers.AllowedOrigins([]string{"https://storage.googleapis.com"}),
handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "OPTIONS", "PATCH", "CONNECT"}),
)
if err := http.ListenAndServe(":"+port, cors(r)); err != nil {
log.Fatalf("Error launching Pets REST API server: %v", err)
}
}
- 在檔案底部新增處理常式支援:
func rootHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "{status: 'running'}")
}
func customerHandler(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
ctx := context.Background()
customer, err := getCustomer(ctx, id)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, `{"status": "fail", "data": '%s'}`, err)
return
}
if customer == nil {
w.WriteHeader(http.StatusNotFound)
msg := fmt.Sprintf("`Customer \"%s\" not found`", id)
fmt.Fprintf(w, fmt.Sprintf(`{"status": "fail", "data": {"title": %s}}`, msg))
return
}
amount, err := getAmounts(ctx, customer)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, `{"status": "fail", "data": "Unable to fetch amounts: %s"}`, err)
return
}
data, err := json.Marshal(amount)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, `{"status": "fail", "data": "Unable to fetch amounts: %s"}`, err)
return
}
fmt.Fprintf(w, fmt.Sprintf(`{"status": "success", "data": %s}`, data))
}
- 在檔案底部新增顧客服務:
type Customer struct {
Email string `firestore:"email"`
ID string `firestore:"id"`
Name string `firestore:"name"`
Phone string `firestore:"phone"`
}
func getCustomer(ctx context.Context, id string) (*Customer, error) {
query := client.Collection("customers").Where("id", "==", id)
iter := query.Documents(ctx)
var c Customer
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, err
}
err = doc.DataTo(&c)
if err != nil {
return nil, err
}
}
return &c, nil
}
func getAmounts(ctx context.Context, c *Customer) (map[string]int64, error) {
if c == nil {
return map[string]int64{}, fmt.Errorf("Customer should be non-nil: %v", c)
}
result := map[string]int64{
"proposed": 0,
"approved": 0,
"rejected": 0,
}
query := client.Collection(fmt.Sprintf("customers/%s/treatments", c.Email))
if query == nil {
return map[string]int64{}, fmt.Errorf("Query is nil: %v", c)
}
iter := query.Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, err
}
treatment := doc.Data()
result[treatment["status"].(string)] += treatment["cost"].(int64)
}
return result, nil
}
- 「儲存」檔案。
工作 6:趣味測驗
Which function responds to URLs with the pattern `/v1/customer/`
getAmounts
customerHandler
Which statement returns success to the client
fmt.Fprintf(w, `{"status": "fail", "data": "Unable to fetch amounts: %s"}
fmt.Fprintf(w, fmt.Sprintf(`{"status": "success", "data": %s}
Which functions read from the Firestore database
customerHandler and getCustomer
getCustomer and getAmounts
工作 7:部署新的修訂版本
- 重新建構原始碼:
go build -o server
- 為 REST API 建構新映像檔:
gcloud builds submit \
--tag gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.2
點選「Check my progress」,確認目標已達成。
建構映像檔 0.2 修訂版本
- 部署更新後的映像檔:
gcloud run deploy rest-api \
--image gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.2 \
--platform managed \
--region {{{ project_0.default_region | "Filled in at lab startup." }}} \
--allow-unauthenticated \
--max-instances=2
4. 部署作業完成後,您會看到與先前類似的訊息。在您部署新版本後,REST API 的網址不會變更:
Service [rest-api] revision [rest-api-00002] has been deployed and is serving
traffic at https://rest-api-[hash].a.run.app
- 返回已指向該網址的瀏覽器分頁 (結尾為
/v1/
)。重新整理分頁,並確認您收到與先前相同的訊息,表示 API 狀態仍為執行中。
- 將
/customer/22530
附加至瀏覽器網址列中的應用程式網址。您應該會收到下列 JSON 回應,列出顧客已提案、核准及拒絕的療程總數。
- 以下是一些其他的顧客 ID,您可以用這些 ID 取代網址中的 22530:
- 34216
- 70156 (所有數量應為零)
- 12345 (顧客/寵物皆不存在,系統應會傳回錯誤訊息,例如 Query is nil)
您已建構可擴充、不必頻繁維護、且可從資料庫讀取資料的無伺服器 REST API。
恭喜!
恭喜!在本實驗室中,您成功協助小茹和阿克,為 Pet Theory 建構了原型 REST API。您建立了連線至 Firestore 資料庫的 REST API,並將其部署至 Cloud Run。您也測試了 API,確保 API 能正常運作。
Google Cloud 教育訓練與認證
協助您瞭解如何充分運用 Google Cloud 的技術。我們的課程會介紹專業技能和最佳做法,讓您可以快速掌握要領並持續進修。我們提供從基本到進階等級的訓練課程,並有隨選、線上和虛擬課程等選項,方便您抽空參加。認證可協助您驗證及證明自己在 Google Cloud 技術方面的技能和專業知識。
使用手冊上次更新日期:2024 年 5 月 6 日
實驗室上次測試日期:2024 年 5 月 6 日
Copyright 2024 Google LLC 保留所有權利。Google 和 Google 標誌是 Google LLC 的商標,其他公司和產品名稱則有可能是其關聯公司的商標。