Pic-a-daily: Lab 2 — Tạo hình thu nhỏ của ảnh

1. Tổng quan

Trong lớp học lập trình này, bạn sẽ xây dựng dựa trên lớp học lập trình trước đó và thêm dịch vụ hình thu nhỏ. Dịch vụ hình thu nhỏ là một vùng chứa trên web dùng để chụp ảnh lớn và tạo hình thu nhỏ từ đó.

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. Sau đó, vùng chứa này 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.

31fa4f8a294d90df.png.

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 tiến độ riêng

  1. Đăng nhập vào Google Cloud Console rồi tạo dự án mới hoặc sử dụng lại 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.

96a9c957bc475304.pngs

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

  • 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ự không được API của Google sử 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 trong tất cả các dự án Google Cloud và không thể thay đổi (không thể thay đổi sau khi đã đặt). Cloud Console sẽ tự động tạo một chuỗi duy nhất; thường bạn không quan tâm đến sản phẩm đó là gì. 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 (và mã này thường được xác định là PROJECT_ID). Vì vậy, nếu không thích, bạn có thể tạo một mã ngẫu nhiên khác hoặc bạn có thể thử mã của riêng mình để xem có mã này chưa. Sau đó, video sẽ được "đóng băng" sau khi tạo dự án.
  • Có giá trị thứ ba là Project Number (Số dự án) mà một số API sử dụng. Tìm hiểu thêm về cả ba giá trị này trong tài liệu này.
  1. Tiếp theo, bạn sẽ cần bật tính năng thanh toán trong Cloud Console để sử dụng tài nguyên/API trên Cloud. Việc chạy qua lớp học lập trình này sẽ không tốn nhiều chi phí. Để tắt các tài nguyên để bạn không phải chịu thanh toán ngoài hướng dẫn này, hãy làm theo mọi thao tác "dọn dẹp" hướng dẫn ở 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í 300 USD.

Khởi động Cloud Shell

Mặc dù bạn 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 trong Đám mây.

Trong Bảng điều khiển GCP, hãy nhấp vào biểu tượng Cloud Shell ở thanh công cụ trên cùng bên phải:

bce75f34b2c53987.png

Sẽ chỉ mất một chút thời gian để cấp phép và kết nối với môi trường. Sau khi hoàn tất, bạn sẽ thấy như sau:

f6ef2b5f13479f3a.png

Máy ảo này chứa tất cả các công cụ phát triển mà bạn cần. Phiên bản này cung cấp thư mục gốc có dung lượng ổn định 5 GB và chạy trên Google Cloud, giúp nâng cao đáng kể hiệu suất và khả năng xác thực của mạng. Bạn có thể thực hiện tất cả công việc trong phòng thí nghiệm này chỉ bằng một trình duyệt.

3. Bật API

Trong phòng thí nghiệm này, bạn sẽ cần có 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 từ 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 được tải lên trong một bộ chứa khác. Hãy sử dụng gsutil để tạo bộ chứa thứ hai.

Bên 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 tên đó vào tên bộ chứa. Sau đó, hãy tạo một bộ chứa công khai đa khu vực ở Châu Âu với quyền truy 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 bộ chứa công khai mới:

8e75c8099938e972.pngs

5. Sao chép mã

Sao chép mã rồi 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 đây cho dịch vụ:

services
 |
 ├── thumbnails
      |
      ├── nodejs
           |
           ├── Dockerfile
           ├── index.js
           ├── package.json

Bên trong thư mục thumbnails/nodejs, bạn có 3 tệp:

  • index.js chứa mã Node.js
  • package.json xác định các phần phụ thuộc của thư viện
  • Dockerfile xác định hình ảnh vùng chứa

6. Khám phá đoạn mã

Để khám phá mã này, bạn có thể sử dụng trình chỉnh sửa văn bản tích hợp sẵn bằng cách nhấp vào nút Open Editor ở đầu cửa sổ Cloud Shell:

3d145fe299dd8b3e.png.

Bạn cũng có thể mở trình chỉnh sửa trong một cửa sổ trình duyệt chuyên dụng để hiển thị nhiều không gian màn hình hơn.

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 dùng để đọc và lưu tệp hình ảnh trong Cloud Storage. Firestore để cập nhật siêu dữ liệu hình ảnh. Express là một khung web dành cho JavaScript / Nút. Mô-đun trình phân tích cú pháp nội dung 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ý lời hứa, còn Imagemagick là thư viện để chỉnh sửa 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 gốc là Nút 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 để chứa các tệp ảnh gốc và hình thu nhỏ. Sau đó, các mô-đun GMS 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 để chúng ta có thể hiểu rõ hơn về hoạt động của chương trình này.

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 sẽ yêu cầu các phần phụ thuộc cần thiết và tạo ứng dụng web Express, đồng thời 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à các tải trọng JSON được gửi qua yêu cầu POST tới ứ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 các tải trọng đến đó trên URL cơ sở / và chúng tôi sẽ gói mã của mình bằng một số cách xử lý logic lỗi để có thông tin rõ hơn về lý do có thể dẫn đến lỗi trong mã bằng cách xem nhật ký sẽ hiển thị từ giao diện Ghi nhật ký Stackdriver trong bảng điều khiển web của 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, các tin nhắn Pub/Sub được gửi qua các yêu cầu POST qua HTTP, dưới dạng các tải trọng JSON có trong biểu mẫu:

{
  "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 thực ra là nội dung có trong thuộc tính message.data, chỉ là một chuỗi nhưng mã hoá tải trọng thực tế vào Base 64. Đó là lý do tại sao mã của chúng tôi ở trên giải mã nội dung Base 64 của thuộc tính này. Sau khi được giải mã, thuộc tính data đó có chứa một tài liệu JSON khác đại diện cho thông tin chi tiết về sự kiện trên Cloud Storage, trong đó có các siêu dữ liệu khác cho biết tên tệp và tên bộ 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 sẽ quan tâm đến tên hình ảnh và tên bộ chứa, vì mã của chúng ta sẽ tìm nạp hình ảnh đó từ bộ chứa để 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 bộ chứa lưu trữ đầu ra từ một biến môi trường.

Chúng ta có bộ chứa gốc có hoạt động tạo tệp đã kích hoạt dịch vụ Cloud Run và bộ chứa đích sẽ lưu trữ hình ảnh thu được. Chúng ta đang dùng API tích hợp path để 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 ta await cho 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 lắm, vì vậy chúng tôi sẽ gói gọn mô-đun này trong lời hứa về JavaScript (do mô-đun Bluebird cung cấp). Sau đó, chúng ta gọi hàm đổi kích thước / cắt không đồng bộ mà chúng ta đã tạo với các tham số cho tệp nguồn và tệp đí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 tôi cũng sẽ cập nhật siêu dữ liệu trong Cloud Firestore để thêm một cờ boolean cho biết 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 kết thúc, chúng tôi sẽ phản hồi yêu cầu HTTP POST để thông báo 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ã trên thiết bị để đảm bảo mã hoạt động trước khi triển khai lên đám mây.

Bên 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ẻ, 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). Google Cloud Build có thể được dùng để 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 chứa 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 vài phút, bản dựng sẽ thành công:

b354b3a9a3631097.png

"Nhật ký" Cloud Build cũng sẽ hiển thị bản dựng thành công:

df00f198dd2bf6bf.png

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

a4577ce0744f73e2.png

Nếu muốn, bạn có thể kiểm tra kỹ để đảm bảo 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 những khu vực và nền tảng được hỗ trợ 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 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. Việc này khiến dịch vụ Cloud Run trở thành một dịch vụ nội bộ chỉ được kích hoạt bằng một số 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:

c0f28e7d6de0024.png

Nếu chuyển đến giao diện người dùng của Cloud Console, bạn cũng sẽ thấy dịch vụ đã được triển khai thành công:

9bfe48e3c8b597e5.pngS

10. Các sự kiện trong Cloud Storage để Cloud Run qua Pub/Sub

Dịch vụ này đã sẵn sàng, nhưng bạn vẫn cần chuyển các sự kiện trên Cloud Storage sang dịch vụ Cloud Run mới tạo. Cloud Storage có thể gửi các sự kiện tạo tệp qua Cloud Pub/Sub, nhưng bạn cần thực hiện một vài bước để tính năng này hoạt động được.

Tạo một chủ đề Pub/Sub làm quy trình liên lạc:

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 bộ chứa:

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 này:

SERVICE_ACCOUNT=$TOPIC_NAME-sa
gcloud iam service-accounts create $SERVICE_ACCOUNT \
     --display-name "Cloud Run Pub/Sub Invoker"

Cấp quyền cho tài khoản dịch vụ để 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 từ ngày 8 tháng 4 năm 2021 trở về trước, 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 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 một 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:

e8ab86dccb8d890.png

11. Kiểm thử dịch vụ

Để kiểm tra xem thiết lập có hoạt động hay không, hãy tải ảnh mới lên bộ chứa uploaded-pictures và kiểm tra trong bộ chứa thumbnails xem các ảnh mới đã đổi kích thước có hiển thị như mong đợi không.

Bạn cũng có thể kiểm tra kỹ nhật ký để xem thông báo ghi nhật ký xuất hiện khi thực hiện các bước khác nhau của dịch vụ Cloud Run:

42c025e2d7d6ca3a.pngS

12. Dọn dẹp (Không bắt buộc)

Nếu không có ý định tiếp tục sử dụng các phòng thí nghiệm khác trong chuỗi chương trình này, bạn có thể dọn dẹp các tài nguyên để tiết kiệm chi phí và trở thành một công dân tốt về công nghệ đám mây. Bạn có thể dọn dẹp từng tài nguyên 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 theo cách sau:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

13. Xin chúc mừng!

Mọi thứ hiện đã sẵn sàng:

  • Đã tạo thông báo trong Cloud Storage để gửi tin nhắn Pub/Sub về một chủ đề khi có ảnh mới được tải lên.
  • Xác định các mối liên kết IAM và tài khoản bắt buộc (không giống như các chức năng đám mây mà hoàn toàn tự động), thì các tài khoản này được định cấu hình theo cách thủ công tại đây).
  • Đã 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 ảnh mới được tải lên bộ chứa, ả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

Các bước tiếp theo