1. Tổng quan
Trong lớp học lập trình này, bạn sẽ tiếp tục với lớp học lập trình trước và thêm một dịch vụ hình thu nhỏ. Dịch vụ hình thu nhỏ là một vùng chứa trên web lấy các hình ảnh lớn và tạo hình thu nhỏ từ các hình ảnh đó.
Khi hình ảnh được tải lên Cloud Storage, một thông báo sẽ được gửi qua Cloud Pub/Sub đến một vùng chứa web Cloud Run. Vùng chứa này sau đó sẽ đổi kích thước hình ảnh và lưu lại vào một bộ chứa khác trong Cloud Storage.

Kiến thức bạn sẽ học được
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub
2. Thiết lập và yêu cầu
Thiết lập môi trường theo tốc độ của riêng bạn
- Đăng nhập vào Google Cloud Console rồi tạo một dự án mới hoặc sử dụng lại một dự án hiện có. Nếu chưa có tài khoản Gmail hoặc Google Workspace, bạn phải tạo một tài khoản.



- Tên dự án là tên hiển thị của những người tham gia dự án này. Đây là một chuỗi ký tự mà các API của Google không dùng và bạn có thể cập nhật chuỗi này bất cứ lúc nào.
- Mã dự án phải là duy nhất trên tất cả các dự án trên Google Cloud và không thể thay đổi (bạn không thể thay đổi sau khi đã đặt). Cloud Console sẽ tự động tạo một chuỗi duy nhất; thường thì bạn không cần quan tâm đến chuỗi này. Trong hầu hết các lớp học lập trình, bạn sẽ cần tham chiếu đến Mã dự án (thường được xác định là
PROJECT_ID). Vì vậy, nếu không thích mã này, bạn có thể tạo một mã ngẫu nhiên khác hoặc thử mã của riêng mình để xem mã đó có dùng được hay không. Sau đó, mã này sẽ "đóng băng" sau khi dự án được tạo. - Có một giá trị thứ ba là Số dự án mà một số API sử dụng. Tìm hiểu thêm về cả 3 giá trị này trong tài liệu.
- Tiếp theo, bạn cần bật tính năng thanh toán trong Cloud Console để sử dụng các tài nguyên/API trên Cloud. Việc thực hiện lớp học lập trình này sẽ không tốn nhiều chi phí, nếu có. Để tắt các tài nguyên nhằm tránh bị tính phí ngoài phạm vi hướng dẫn này, hãy làm theo mọi hướng dẫn "dọn dẹp" ở cuối lớp học lập trình. Người dùng mới của Google Cloud đủ điều kiện tham gia chương trình Dùng thử miễn phí trị giá 300 USD.
Khởi động Cloud Shell
Mặc dù có thể vận hành Google Cloud từ xa trên máy tính xách tay, nhưng trong lớp học lập trình này, bạn sẽ sử dụng Google Cloud Shell, một môi trường dòng lệnh chạy trên Cloud.
Trên Bảng điều khiển GCP, hãy nhấp vào biểu tượng Cloud Shell trên thanh công cụ ở trên cùng bên phải:

Quá trình này chỉ mất vài phút để cung cấp và kết nối với môi trường. Khi quá trình này kết thúc, bạn sẽ thấy như sau:

Máy ảo này được trang bị tất cả các công cụ phát triển mà bạn cần. Nó cung cấp một thư mục chính có dung lượng 5 GB và chạy trên Google Cloud, giúp tăng cường đáng kể hiệu suất mạng và hoạt động xác thực. Bạn chỉ cần một trình duyệt là có thể thực hiện mọi thao tác trong phòng thí nghiệm này.
3. Bật API
Trong phòng thí nghiệm này, bạn sẽ cần Cloud Build để tạo hình ảnh vùng chứa và Cloud Run để triển khai vùng chứa.
Bật cả hai API này trong Cloud Shell:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
Bạn sẽ thấy thao tác hoàn tất thành công:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Tạo một bộ chứa khác
Bạn sẽ lưu trữ hình thu nhỏ của những bức ảnh đã tải lên trong một nhóm khác. Hãy dùng gsutil để tạo nhóm thứ hai.
Trong Cloud Shell, hãy đặt một biến cho tên bộ chứa duy nhất. Cloud Shell đã đặt GOOGLE_CLOUD_PROJECT thành mã dự án duy nhất của bạn. Bạn có thể thêm thông tin đó vào tên nhóm. Sau đó, hãy tạo một vùng chứa công khai ở nhiều khu vực tại Châu Âu với quyền truy cập ở cấp độ đồng nhất:
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
Cuối cùng, bạn sẽ có một nhóm công khai mới:

5. Sao chép mã
Sao chép mã và chuyển đến thư mục chứa dịch vụ:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop cd serverless-photosharing-workshop/services/thumbnails/nodejs
Bạn sẽ có bố cục tệp sau cho dịch vụ:
services
|
├── thumbnails
|
├── nodejs
|
├── Dockerfile
├── index.js
├── package.json
Trong thư mục thumbnails/nodejs, bạn có 3 tệp:
index.jschứa mã Node.jspackage.jsonxác định các phần phụ thuộc của thư việnDockerfilexác định hình ảnh vùng chứa
6. Khám phá mã
Để khám phá mã, bạn có thể sử dụng trình chỉnh sửa văn bản tích hợp bằng cách nhấp vào nút Open Editor ở đầu cửa sổ Cloud Shell:

Bạn cũng có thể mở trình chỉnh sửa trong một cửa sổ trình duyệt riêng để có thêm không gian trên màn hình.
Phần phụ thuộc
Tệp package.json xác định các phần phụ thuộc cần thiết của thư viện:
{
"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"
}
}
Thư viện Cloud Storage được dùng để đọc và lưu các tệp hình ảnh trong Cloud Storage. Firestore để cập nhật siêu dữ liệu của hình ảnh. Express là một khung web JavaScript / Node. Mô-đun body-parser được dùng để dễ dàng phân tích cú pháp các yêu cầu đến. Bluebird được dùng để xử lý các promise và Imagemagick là một thư viện để thao tác với hình ảnh.
tệp Docker
Dockerfile xác định hình ảnh vùng chứa cho ứng dụng:
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" ]
Hình ảnh cơ sở là Node 14 và thư viện imagemagick được dùng để thao tác với hình ảnh. Một số thư mục tạm thời được tạo để lưu trữ các tệp ảnh gốc và thu nhỏ. Sau đó, các mô-đun NPM mà mã của chúng ta cần sẽ được cài đặt trước khi bắt đầu mã bằng npm start.
index.js
Hãy khám phá mã theo từng phần để hiểu rõ hơn về những gì chương trình này đang làm.
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());
Trước tiên, chúng ta cần các phần phụ thuộc cần thiết và tạo ứng dụng web Express, cũng như cho biết rằng chúng ta muốn sử dụng trình phân tích cú pháp nội dung JSON, vì các yêu cầu đến thực ra chỉ là tải trọng JSON được gửi qua yêu cầu POST đến ứng dụng của chúng ta.
app.post('/', async (req, res) => {
try {
// ...
} catch (err) {
console.log(`Error: creating the thumbnail: ${err}`);
console.error(err);
res.status(500).send(err);
}
});
Chúng tôi đang nhận những tải trọng đến trên URL cơ sở /, đồng thời bao bọc mã của mình bằng một số logic xử lý lỗi để có thông tin chính xác hơn về lý do có thể khiến một số nội dung không hoạt động trong mã của chúng tôi bằng cách xem nhật ký sẽ xuất hiện trên giao diện Stackdriver Logging trong bảng điều khiển web Google Cloud.
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}`);
Trên nền tảng Cloud Run, thông báo Pub/Sub được gửi qua các yêu cầu HTTP POST, dưới dạng tải trọng JSON có dạng:
{
"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"
}
Nhưng điều thực sự thú vị trong tài liệu JSON này là nội dung có trong thuộc tính message.data. Đây chỉ là một chuỗi nhưng mã hoá tải trọng thực tế thành Base 64. Đó là lý do mã ở trên của chúng tôi đang giải mã nội dung Base 64 của thuộc tính này. Thuộc tính data sau khi được giải mã sẽ chứa một tài liệu JSON khác biểu thị thông tin chi tiết về sự kiện Cloud Storage. Trong số các siêu dữ liệu khác, tài liệu này cho biết tên tệp và tên vùng chứa.
{
"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="
}
Chúng ta cần biết tên hình ảnh và tên nhóm, vì mã của chúng ta sẽ tìm nạp hình ảnh đó từ nhóm để xử lý hình thu nhỏ:
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}`);
Chúng ta đang truy xuất tên của vùng lưu trữ đầu ra từ một biến môi trường.
Chúng ta có bộ chứa nguồn mà việc tạo tệp đã kích hoạt dịch vụ Cloud Run và bộ chứa đích nơi chúng ta sẽ lưu trữ hình ảnh kết quả. Chúng tôi đang sử dụng API path tích hợp để xử lý tệp cục bộ, vì thư viện imagemagick sẽ tạo hình thu nhỏ cục bộ trong thư mục tạm thời /tmp. Chúng tôi await cho một lệnh gọi không đồng bộ để tải tệp hình ảnh đã tải lên xuống.
const resizeCrop = Promise.promisify(im.crop);
await resizeCrop({
srcPath: originalFile,
dstPath: thumbFile,
width: 400,
height: 400
});
console.log(`Created local thumbnail in ${thumbFile}`);
Mô-đun imagemagick không thân thiện với async / await, vì vậy, chúng tôi sẽ gói mô-đun này trong một lời hứa của Javascript (do mô-đun Bluebird cung cấp). Sau đó, chúng ta sẽ gọi hàm thay đổi kích thước / cắt không đồng bộ mà chúng ta đã tạo bằng các tham số cho tệp nguồn và đích, cũng như kích thước của hình thu nhỏ mà chúng ta muốn tạo.
await thumbBucket.upload(thumbFile);
console.log(`Uploaded thumbnail to Cloud Storage bucket ${process.env.BUCKET_THUMBNAILS}`);
Sau khi tệp hình thu nhỏ được tải lên Cloud Storage, chúng ta cũng sẽ cập nhật siêu dữ liệu trong Cloud Firestore để thêm một cờ boolean cho biết rằng hình thu nhỏ của hình ảnh này thực sự được tạo:
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`);
Sau khi yêu cầu của chúng tôi kết thúc, chúng tôi sẽ trả lời yêu cầu HTTP POST rằng tệp đã được xử lý đúng cách.
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started thumbnail generator on port ${PORT}`);
});
Ở cuối tệp nguồn, chúng ta có hướng dẫn để Express thực sự khởi động ứng dụng web trên cổng mặc định 8080.
7. Kiểm thử cục bộ
Kiểm thử mã cục bộ để đảm bảo mã hoạt động trước khi triển khai lên đám mây.
Trong thư mục thumbnails/nodejs, hãy cài đặt các phần phụ thuộc npm và khởi động máy chủ:
npm install; npm start
Nếu mọi thứ diễn ra suôn sẻ, thì máy chủ sẽ khởi động trên cổng 8080:
Started thumbnail generator on port 8080
Sử dụng CTRL-C để thoát.
8. Tạo và xuất bản hình ảnh vùng chứa
Cloud Run chạy các vùng chứa nhưng trước tiên bạn cần tạo hình ảnh vùng chứa (được xác định trong Dockerfile). Bạn có thể dùng Google Cloud Build để tạo hình ảnh vùng chứa rồi lưu trữ vào Google Container Registry.
Bên trong thư mục thumbnails/nodejs có Dockerfile, hãy phát lệnh sau để tạo hình ảnh vùng chứa:
gcloud builds submit --tag gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
Sau một hoặc hai phút, bản dựng sẽ thành công:

Phần "nhật ký" của Cloud Build cũng sẽ cho thấy bản dựng thành công:

Nhấp vào mã nhận dạng bản dựng để xem chế độ xem chi tiết. Trong thẻ "cấu phần phần mềm của bản dựng", bạn sẽ thấy rằng hình ảnh vùng chứa đã được tải lên Cloud Registry (GCR):

Nếu muốn, bạn có thể kiểm tra kỹ để đảm bảo rằng hình ảnh vùng chứa chạy cục bộ trong Cloud Shell:
docker run -p 8080:8080 gcr.io/$GOOGLE_CLOUD_PROJECT/thumbnail-service
Thao tác này sẽ khởi động máy chủ trên cổng 8080 trong vùng chứa:
Started thumbnail generator on port 8080
Sử dụng CTRL-C để thoát.
9. Triển khai lên Cloud Run
Trước khi triển khai lên Cloud Run, hãy đặt khu vực Cloud Run thành một trong các khu vực được hỗ trợ và nền tảng thành managed:
gcloud config set run/region europe-west1 gcloud config set run/platform managed
Bạn có thể kiểm tra để đảm bảo rằng cấu hình đã được thiết lập:
gcloud config list ... [run] platform = managed region = europe-west1
Chạy lệnh sau để triển khai hình ảnh vùng chứa trên 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
Lưu ý cờ --no-allow-unauthenticated. Điều này khiến dịch vụ Cloud Run trở thành một dịch vụ nội bộ và chỉ được kích hoạt bởi các tài khoản dịch vụ cụ thể.
Nếu triển khai thành công, bạn sẽ thấy kết quả sau:

Nếu truy cập vào giao diện người dùng bảng điều khiển đám mây, bạn cũng sẽ thấy rằng dịch vụ đã được triển khai thành công:

10. Sự kiện Cloud Storage đến Cloud Run thông qua Pub/Sub
Dịch vụ đã sẵn sàng, nhưng bạn vẫn cần tạo các sự kiện Cloud Storage cho dịch vụ Cloud Run mới tạo. Cloud Storage có thể gửi các sự kiện tạo tệp thông qua Cloud Pub/Sub, nhưng bạn cần thực hiện một số bước để tính năng này hoạt động.
Tạo một chủ đề Pub/Sub làm đường ống giao tiếp:
TOPIC_NAME=cloudstorage-cloudrun-topic gcloud pubsub topics create $TOPIC_NAME
Tạo thông báo Pub/Sub khi tệp được lưu trữ trong vùng lưu trữ:
BUCKET_PICTURES=uploaded-pictures-$GOOGLE_CLOUD_PROJECT gsutil notification create -t $TOPIC_NAME -f json gs://$BUCKET_PICTURES
Tạo một tài khoản dịch vụ cho gói thuê bao Pub/Sub mà chúng ta sẽ tạo sau:
SERVICE_ACCOUNT=$TOPIC_NAME-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
--display-name "Cloud Run Pub/Sub Invoker"
Cấp cho tài khoản dịch vụ quyền gọi một dịch vụ 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
Nếu bạn đã bật tài khoản dịch vụ Pub/Sub vào hoặc trước ngày 8 tháng 4 năm 2021, hãy cấp vai trò iam.serviceAccountTokenCreator cho tài khoản dịch vụ 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
Có thể mất vài phút để các thay đổi về IAM có hiệu lực.
Cuối cùng, hãy tạo một gói thuê bao Pub/Sub bằng tài khoản dịch vụ:
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
Bạn có thể kiểm tra để đảm bảo rằng gói thuê bao đã được tạo. Chuyển đến Pub/Sub trong bảng điều khiển, chọn chủ đề gcs-events và ở dưới cùng, bạn sẽ thấy gói thuê bao:

11. Kiểm thử dịch vụ
Để kiểm thử xem chế độ thiết lập có hoạt động hay không, hãy tải một hình ảnh mới lên vùng chứa uploaded-pictures và kiểm tra trong vùng chứa thumbnails để xem hình ảnh mới được đổi kích thước có xuất hiện như mong đợi hay không.
Bạn cũng có thể kiểm tra nhật ký để xem các thông báo ghi nhật ký xuất hiện khi các bước khác nhau của dịch vụ Cloud Run đang diễn ra:

12. Dọn dẹp (Không bắt buộc)
Nếu không có ý định tiếp tục với các phòng thí nghiệm khác trong loạt bài này, bạn có thể dọn dẹp tài nguyên để tiết kiệm chi phí và trở thành một công dân đám mây tốt. Bạn có thể dọn dẹp từng tài nguyên riêng lẻ như sau.
Xoá bộ chứa:
gsutil rb gs://$BUCKET_THUMBNAILS
Xoá dịch vụ:
gcloud run services delete $SERVICE_NAME -q
Xoá chủ đề Pub/Sub:
gcloud pubsub topics delete $TOPIC_NAME
Ngoài ra, bạn có thể xoá toàn bộ dự án:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
13. Xin chúc mừng!
Mọi thứ hiện đã sẵn sàng:
- Tạo một thông báo trong Cloud Storage để gửi thông báo Pub/Sub về một chủ đề khi có ảnh mới được tải lên.
- Xác định các liên kết và tài khoản IAM bắt buộc (không giống như Cloud Functions, nơi mọi thứ đều được tự động hoá, ở đây, bạn phải định cấu hình theo cách thủ công).
- Tạo một gói thuê bao để dịch vụ Cloud Run của chúng tôi nhận được thông báo Pub/Sub.
- Bất cứ khi nào một bức ảnh mới được tải lên nhóm, bức ảnh đó sẽ được đổi kích thước nhờ dịch vụ Cloud Run mới.
Nội dung đã đề cập
- Cloud Run
- Cloud Storage
- Cloud Pub/Sub