1. 總覽
在本程式碼研究室中,您將在先前的研究室中建構內容,並新增縮圖服務。縮圖服務是擷取大型圖片的網路容器,會從這些圖片建立縮圖。
圖片上傳至 Cloud Storage 時,系統會透過 Cloud Pub/Sub 傳送通知至 Cloud Run 網路容器,接著調整圖片的大小,並將圖片重新儲存於 Cloud Storage 的另一個值區中。
課程內容
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub
2. 設定和需求
自修環境設定
- 登入 Google Cloud 控制台,建立新專案或重複使用現有專案。如果您還沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶。
- 「專案名稱」是這項專案參與者的顯示名稱。這是 Google API 不使用的字元字串,您可以隨時更新。
- 所有 Google Cloud 專案的專案 ID 均不得重複,且設定後即無法變更。Cloud 控制台會自動產生一個不重複的字串。但通常是在乎它何在在大部分的程式碼研究室中,您必須參照專案 ID (通常稱為
PROJECT_ID
),因此如果您不喜歡的話,可以再隨機產生一個,或者,您也可以自行嘗試看看是否可用。是「凍結」建立專案後 - 還有第三個值,也就是部分 API 使用的專案編號。如要進一步瞭解這三個值,請參閱說明文件。
- 接下來,您需要在 Cloud 控制台中啟用計費功能,才能使用 Cloud 資源/API。執行這個程式碼研究室並不會產生任何費用,如果有的話。如要關閉資源,以免產生本教學課程結束後產生的費用,請按照任「清除所用資源」操作請參閱本程式碼研究室結尾處的操作說明。Google Cloud 的新使用者符合 $300 美元免費試用計畫的資格。
啟動 Cloud Shell
雖然 Google Cloud 可以從筆記型電腦遠端操作,但在本程式碼研究室中,您將使用 Google Cloud Shell,這是一種在 Cloud 中執行的指令列環境。
在 GCP 控制台的右上方,按一下「Cloud Shell」圖示:
佈建並連線至環境的作業只需幾分鐘的時間。完成後,您應該會看到類似下方的內容:
這部虛擬機器都裝載了您需要的所有開發工具。提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,大幅提高網路效能和驗證能力。這個研究室中的所有工作都可以透過瀏覽器完成。
3. 啟用 API
在本研究室中,您需要使用 Cloud Build 建構容器映像檔,並透過 Cloud Run 部署容器。
透過 Cloud Shell 啟用這兩個 API:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
您應該會看到作業成功完成:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. 建立另一個值區
您會將所上傳圖片的縮圖儲存在其他值區中。讓我們使用 gsutil
建立第二個值區。
在 Cloud Shell 中為不重複的值區名稱設定變數。Cloud Shell 已將 GOOGLE_CLOUD_PROJECT
設為專屬的專案 ID。您可以將該名稱附加到值區名稱,接著,在歐洲建立公開多區域值區,並採用統一層級存取權:
BUCKET_THUMBNAILS=thumbnails-$GOOGLE_CLOUD_PROJECT gsutil mb -l EU gs://$BUCKET_THUMBNAILS gsutil uniformbucketlevelaccess set on gs://$BUCKET_THUMBNAILS gsutil iam ch allUsers:objectViewer gs://$BUCKET_THUMBNAILS
最後,您應該會擁有新的公開值區:
5. 複製程式碼
複製程式碼並前往包含服務的目錄:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop cd serverless-photosharing-workshop/services/thumbnails/nodejs
服務的檔案版面配置如下:
services | ├── thumbnails | ├── nodejs | ├── Dockerfile ├── index.js ├── package.json
thumbnails/nodejs
資料夾中有 3 個檔案:
index.js
包含 Node.js 程式碼package.json
定義程式庫依附元件Dockerfile
定義容器映像檔
6. 探索程式碼
如要查看程式碼,您可以使用內建的文字編輯器,方法是點選 Cloud Shell 視窗頂端的 Open Editor
按鈕:
您也可以在專用的瀏覽器視窗中開啟編輯器,使用更多螢幕空間。
依附元件
package.json
檔案會定義所需的程式庫依附元件:
{
"name": "thumbnail_service",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"bluebird": "^3.7.2",
"express": "^4.17.1",
"imagemagick": "^0.1.3",
"@google-cloud/firestore": "^4.9.9",
"@google-cloud/storage": "^5.8.3"
}
}
Cloud Storage 程式庫可用於在 Cloud Storage 中讀取及儲存圖片檔。來更新圖片中繼資料的 Firestore。Express 是 JavaScript / Node 網路架構。主體剖析器模組可用來輕鬆剖析傳入的要求。Bluebird 用於處理承諾,而 Imagemagick 是處理圖片的程式庫。
Dockerfile
Dockerfile
定義應用程式的容器映像檔:
FROM node:14-slim
# installing Imagemagick
RUN set -ex; \
apt-get -y update; \
apt-get -y install imagemagick; \
rm -rf /var/lib/apt/lists/*; \
mkdir /tmp/original; \
mkdir /tmp/thumbnail;
WORKDIR /picadaily/services/thumbnails
COPY package*.json ./
RUN npm install --production
COPY . .
CMD [ "npm", "start" ]
基本映像檔為 Node 14,而 imagemagick 程式庫則用於操控圖片。系統會建立一些暫存目錄,用來存放原始和縮圖檔案。接著,系統會安裝程式碼所需的 NPM 模組,再透過 npm start
啟動程式碼。
index.js
讓我們繼續探索各程式碼的部分,進一步瞭解這個程式的用途。
const express = require('express');
const imageMagick = require('imagemagick');
const Promise = require("bluebird");
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
const app = express();
app.use(express.json());
我們首先需要必要的依附元件,並建立 Express 網頁應用程式,並且指出我們要使用 JSON 主體剖析器,因為傳入要求其實只是透過 POST 要求傳送至應用程式的 JSON 酬載。
app.post('/', async (req, res) => {
try {
// ...
} catch (err) {
console.log(`Error: creating the thumbnail: ${err}`);
console.error(err);
res.status(500).send(err);
}
});
我們在 / 基礎網址上接收到這些傳入的酬載,我們正在使用一些錯誤邏輯處理功能包裝程式碼,以深入瞭解為何程式碼失敗的原因。您可以在 Google Cloud 網路控制台的 Stackdriver Logging 介面中查看記錄檔,進一步瞭解可能出現故障的原因。
const pubSubMessage = req.body;
console.log(`PubSub message: ${JSON.stringify(pubSubMessage)}`);
const fileEvent = JSON.parse(Buffer.from(pubSubMessage.message.data, 'base64').toString().trim());
console.log(`Received thumbnail request for file ${fileEvent.name} from bucket ${fileEvent.bucket}`);
在 Cloud Run 平台上,Pub/Sub 訊息和下列格式的 JSON 酬載是透過 HTTP POST 要求傳送:
{
"message": {
"attributes": {
"bucketId": "uploaded-pictures",
"eventTime": "2020-02-27T09:22:43.255225Z",
"eventType": "OBJECT_FINALIZE",
"notificationConfig": "projects/_/buckets/uploaded-pictures/notificationConfigs/28",
"objectGeneration": "1582795363255481",
"objectId": "IMG_20200213_181159.jpg",
"payloadFormat": "JSON_API_V1"
},
"data": "ewogICJraW5kIjogInN0b3JhZ2Ujb2JqZWN...FQUU9Igp9Cg==",
"messageId": "1014308302773399",
"message_id": "1014308302773399",
"publishTime": "2020-02-27T09:22:43.973Z",
"publish_time": "2020-02-27T09:22:43.973Z"
},
"subscription": "projects/serverless-picadaily/subscriptions/gcs-events-subscription"
}
不過,此 JSON 文件真正有意思的是 message.data
屬性所含的內容,該屬性只是字串,但會將實際酬載編碼為 Base 64。因此,上述程式碼正在解碼此屬性的 Base 64 內容。解碼後,data
屬性會包含代表 Cloud Storage 事件詳細資料的另一份 JSON 文件,其中含有其他中繼資料,代表檔案名稱和值區名稱。
{
"kind": "storage#object",
"id": "uploaded-pictures/IMG_20200213_181159.jpg/1582795363255481",
"selfLink": "https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg",
"name": "IMG_20200213_181159.jpg",
"bucket": "uploaded-pictures",
"generation": "1582795363255481",
"metageneration": "1",
"contentType": "image/jpeg",
"timeCreated": "2020-02-27T09:22:43.255Z",
"updated": "2020-02-27T09:22:43.255Z",
"storageClass": "STANDARD",
"timeStorageClassUpdated": "2020-02-27T09:22:43.255Z",
"size": "4944335",
"md5Hash": "QzBIoPJBV2EvqB1EVk1riw==",
"mediaLink": "https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/IMG_20200213_181159.jpg?generation=1582795363255481&alt=media",
"crc32c": "hQ3uHg==",
"etag": "CLmJhJu08ecCEAE="
}
我們想瞭解圖片和值區名稱,因為程式碼會從值區擷取圖片以處理縮圖:
const bucket = storage.bucket(fileEvent.bucket);
const thumbBucket = storage.bucket(process.env.BUCKET_THUMBNAILS);
const originalFile = path.resolve('/tmp/original', fileEvent.name);
const thumbFile = path.resolve('/tmp/thumbnail', fileEvent.name);
await bucket.file(fileEvent.name).download({
destination: originalFile
});
console.log(`Downloaded picture into ${originalFile}`);
我們正在從環境變數擷取輸出儲存空間值區的名稱。
其中有檔案建立作業觸發 Cloud Run 服務的來源值區,以及用來儲存結果圖片的目的地值區。由於 Imagemagick 程式庫會在本機的 /tmp
暫存目錄中建立縮圖,因此我們使用 path
內建 API 處理本機檔案。我們 await
進行非同步呼叫,以下載上傳的圖片檔。
const resizeCrop = Promise.promisify(im.crop);
await resizeCrop({
srcPath: originalFile,
dstPath: thumbFile,
width: 400,
height: 400
});
console.log(`Created local thumbnail in ${thumbFile}`);
Imagemagick 模組不是非常適用的 async
/ await
相容,因此會在 JavaScript 承諾內 (由 Bluebird 模組提供)。接著,再呼叫我們建立的非同步大小調整 / 裁剪函式,其中包含來源和目的地檔案的參數,以及我們要建立的縮圖尺寸。
await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);
縮圖檔案上傳至 Cloud Storage 後,我們也會更新 Cloud Firestore 中的中繼資料,加入布林值標記,指出這張圖片的縮圖已確實產生:
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(fileEvent.name);
await doc.set({
thumbnail: true
}, {merge: true});
console.log(`Updated Firestore about thumbnail creation for ${fileEvent.name}`);
res.status(204).send(`${fileEvent.name} processed`);
要求處理完畢後,我們會回覆 HTTP POST 要求,表示檔案已妥善處理。
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started thumbnail generator on port ${PORT}`);
});
來源檔案的結尾處,我們有操作說明讓 Express 實際上使用 8080 預設連接埠啟動網頁應用程式。
7. 在本機測試
先在本機測試程式碼,確保程式碼能正常運作再部署至雲端。
在 thumbnails/nodejs
資料夾中,安裝 npm 依附元件並啟動伺服器:
npm install; npm start
如果一切順利,則應透過通訊埠 8080 啟動伺服器:
Started thumbnail generator on port 8080
使用 CTRL-C
退出。
8. 建構並發布容器映像檔
Cloud Run 會執行容器,但您需要先建構容器映像檔 (以 Dockerfile
定義)。您可以使用 Google Cloud Build 建構容器映像檔,然後託管至 Google Container Registry。
在包含 Dockerfile
的 thumbnails/nodejs
資料夾中發出以下指令,建構容器映像檔:
gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
經過一到兩分鐘後,建構作業應會成功:
Cloud Build 的「歷史」部分應該也會顯示成功的版本:
按一下建構 ID,即可在「建構構件」中取得詳細資料檢視畫面分頁中,您應該會看到容器映像檔已上傳至 Cloud Registry (GCR):
如有需要,您可以再次透過 Cloud Shell 檢查容器映像檔是否在本機執行:
docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
容器應該會在容器的通訊埠 8080 啟動伺服器:
Started thumbnail generator on port 8080
使用 CTRL-C
退出。
9. 部署至 Cloud Run
部署至 Cloud Run 之前,請將 Cloud Run 區域設為下列其中一個支援的區域和平台:managed
:
gcloud config set run/region europe-west1 gcloud config set run/platform managed
您可以檢查設定是否完成:
gcloud config list ... [run] platform = managed region = europe-west1
執行下列指令,在 Cloud Run 上部署容器映像檔:
SERVICE_NAME=thumbnail-service gcloud run deploy $SERVICE_NAME \ --image gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service \ --no-allow-unauthenticated \ --update-env-vars BUCKET_THUMBNAILS=$BUCKET_THUMBNAILS
請注意 --no-allow-unauthenticated
旗標。這樣一來,Cloud Run 服務就會成為只會由特定服務帳戶觸發的內部服務。
如果部署成功,您應該會看到以下輸出內容:
如果您前往 Cloud 控制台 UI,應該也會看到服務已成功部署:
10. 透過 Pub/Sub 將 Cloud Storage 事件傳送至 Cloud Run
服務已準備就緒,但您仍需為新建立的 Cloud Run 服務設定 Cloud Storage 事件。Cloud Storage 可透過 Cloud Pub/Sub 傳送檔案建立事件,但要採取幾個步驟才能正常運作。
建立 Pub/Sub 主題做為通訊管道:
TOPIC_NAME=cloudstorage-cloudrun-topic gcloud pubsub topics create $TOPIC_NAME
將檔案儲存在值區中時,建立 Pub/Sub 通知:
BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES
為稍後建立的 Pub/Sub 訂閱項目建立服務帳戶:
SERVICE_ACCOUNT=$TOPIC_NAME-sa gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name "Cloud Run Pub/Sub Invoker"
授予服務帳戶叫用 Cloud Run 服務的權限:
SERVICE_NAME=thumbnail-service gcloud run services add-iam-policy-binding $SERVICE_NAME \ --member=serviceAccount:$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \ --role=roles/run.invoker
如果您是在 2021 年 4 月 8 日當天或之前啟用 Pub/Sub 服務帳戶,請將 iam.serviceAccountTokenCreator
角色授予 Pub/Sub 服務帳戶:
PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)') gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \ --member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \ --role=roles/iam.serviceAccountTokenCreator
系統可能需要幾分鐘的時間,才能套用 IAM 變更。
最後,使用服務帳戶建立 Pub/Sub 訂閱項目:
SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --format 'value(status.url)') gcloud pubsub subscriptions create $TOPIC_NAME-subscription --topic $TOPIC_NAME \ --push-endpoint=$SERVICE_URL \ --push-auth-service-account=$SERVICE_ACCOUNT@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com
您可以檢查訂閱項目是否已建立完成。在控制台中前往 Pub/Sub,選取「gcs-events
」主題,畫面底部應會顯示訂閱項目:
11. 測試服務
如要測試設定是否正常運作,請將新圖片上傳至 uploaded-pictures
值區,然後查看 thumbnails
值區,確認經過調整後的圖片是否如預期顯示。
Cloud Run 服務的每個步驟都會逐一完成,您也可以檢查記錄是否出現記錄訊息:
12. 清除 (選用)
如果不想繼續參加本系列的其他研究室課程,您可以清理資源來節省成本,並成為良好的雲端公民。您可以按照下列步驟個別清除資源。
刪除值區:
gsutil rb gs://$BUCKET_THUMBNAILS
刪除服務:
gcloud run services delete $SERVICE_NAME -q
刪除 Pub/Sub 主題:
gcloud pubsub topics delete $TOPIC_NAME
或者,您也可以刪除整個專案:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
13. 恭喜!
現在一切都已就緒:
- 在 Cloud Storage 中建立通知,可在上傳新圖片時傳送特定主題的 Pub/Sub 訊息。
- 已定義必要的 IAM 繫結和帳戶 (與採用自動化功能的 Cloud Functions 不同,在這裡手動設定)。
- 建立訂閱項目,以便 Cloud Run 服務接收 Pub/Sub 訊息。
- 每當有新圖片上傳至值區時,系統就會使用新的 Cloud Run 服務調整相片大小。
涵蓋內容
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub