1. Tổng quan
Trong lớp học lập trình đầu tiên, bạn sẽ lưu trữ hình ảnh trong một vùng chứa. Thao tác này sẽ tạo một sự kiện tạo tệp và sự kiện này sẽ được xử lý bởi một dịch vụ được triển khai trong Cloud Run. Dịch vụ này sẽ gọi Vision API để phân tích hình ảnh và lưu kết quả vào một kho dữ liệu.

Kiến thức bạn sẽ học được
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
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 sử dụng. Bạn có thể cập nhật thông tin 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). Nếu không thích mã nhận dạng được tạo, bạn có thể tạo một mã nhận dạng ngẫu nhiên khác. Hoặc bạn có thể thử tên người dùng của riêng mình để xem tên đó có dùng được hay không. Bạn không thể thay đổi thông tin này sau bước này và thông tin này sẽ giữ nguyên trong suốt thời gian diễn ra dự án. - Để bạn biết, 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 phát sinh phí thanh toán ngoài hướng dẫn này, bạn có thể xoá các tài nguyên đã tạo hoặc xoá toàn bộ dự án. 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 Google Cloud, 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 có thể thực hiện mọi thao tác trong lớp học lập trình này trong trình duyệt. Bạn không cần cài đặt bất cứ thứ gì.
3. Bật API
Trong phòng thí nghiệm này, bạn sẽ sử dụng Cloud Functions và Vision API. Tuy nhiên, trước tiên, bạn cần bật các API này trong Cloud Console hoặc bằng gcloud.
Để bật Vision API trong Cloud Console, hãy tìm Cloud Vision API trong thanh tìm kiếm:

Bạn sẽ chuyển đến trang Cloud Vision API:

Nhấp vào nút ENABLE.
Ngoài ra, bạn cũng có thể bật Cloud Shell bằng công cụ dòng lệnh gcloud.
Trong Cloud Shell, hãy chạy lệnh sau:
gcloud services enable vision.googleapis.com
Bạn sẽ thấy thao tác hoàn tất thành công:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Bật Cloud Run và Cloud Build:
gcloud services enable cloudbuild.googleapis.com \ run.googleapis.com
4. Tạo vùng chứa (bảng điều khiển)
Tạo một bộ chứa lưu trữ cho các bức ảnh. Bạn có thể thực hiện việc này trên bảng điều khiển Google Cloud Platform ( console.cloud.google.com) hoặc bằng công cụ dòng lệnh gsutil trên Cloud Shell hoặc môi trường phát triển cục bộ.
Chuyển đến phần Bộ nhớ
Trong trình đơn "hamburger" (☰), hãy chuyển đến trang Storage.

Đặt tên cho bộ chứa
Nhấp vào nút CREATE BUCKET.

Nhấp vào CONTINUE.
Chọn vị trí

Tạo một vùng chứa đa khu vực ở khu vực mà bạn chọn (trong trường hợp này là Europe).
Nhấp vào CONTINUE.
Chọn lớp lưu trữ mặc định

Chọn lớp lưu trữ Standard cho dữ liệu của bạn.
Nhấp vào CONTINUE.
Thiết lập quyền kiểm soát truy cập

Vì bạn sẽ làm việc với những hình ảnh có thể truy cập công khai, nên bạn muốn tất cả hình ảnh được lưu trữ trong nhóm này đều có cùng một chế độ kiểm soát truy cập đồng nhất.
Chọn chế độ kiểm soát quyền truy cập Uniform.
Nhấp vào CONTINUE.
Thiết lập chế độ bảo vệ/mã hoá

Giữ nguyên chế độ mặc định (Google-managed key), vì bạn sẽ không sử dụng khoá mã hoá của riêng mình.
Nhấp vào CREATE để hoàn tất quá trình tạo nhóm.
Thêm allUsers làm người xem bộ nhớ
Chuyển đến thẻ Permissions:

Thêm một thành viên allUsers vào nhóm, với vai trò là Storage > Storage Object Viewer, như sau:

Nhấp vào SAVE.
5. Tạo vùng chứa (gsutil)
Bạn cũng có thể sử dụng công cụ dòng lệnh gsutil trong Cloud Shell để tạo các vùng chứa.
Trong Cloud Shell, hãy đặt một biến cho tên bộ chứa riêng biệ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.
Ví dụ:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Tạo một vùng tiêu chuẩn có nhiều khu vực ở Châu Âu:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Đảm bảo quyền truy cập ở cấp nhóm đồng nhất:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Đặt thùng chứa ở chế độ công khai:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Nếu chuyển đến mục Cloud Storage của bảng điều khiển, bạn sẽ thấy một vùng chứa uploaded-pictures công khai:

Kiểm tra để đảm bảo bạn có thể tải hình ảnh lên nhóm và hình ảnh đã tải lên được cung cấp công khai, như đã giải thích ở bước trước.
6. Kiểm thử quyền truy cập công khai vào vùng lưu trữ
Quay lại trình duyệt lưu trữ, bạn sẽ thấy nhóm của mình trong danh sách, với quyền truy cập "Công khai" (kể cả dấu cảnh báo nhắc bạn rằng bất kỳ ai cũng có quyền truy cập vào nội dung của nhóm đó).

Thùng của bạn hiện đã sẵn sàng nhận ảnh.
Nếu nhấp vào tên nhóm, bạn sẽ thấy thông tin chi tiết về nhóm.

Tại đây, bạn có thể thử nút Upload files để kiểm tra xem bạn có thể thêm ảnh vào nhóm hay không. Một cửa sổ bật lên chọn tệp sẽ yêu cầu bạn chọn một tệp. Sau khi bạn chọn, tệp đó sẽ được tải lên nhóm của bạn và bạn sẽ thấy lại quyền truy cập public được tự động gán cho tệp mới này.

Bên cạnh nhãn truy cập Public, bạn cũng sẽ thấy một biểu tượng đường liên kết nhỏ. Khi bạn nhấp vào đó, trình duyệt sẽ chuyển đến URL công khai của hình ảnh đó, có dạng như sau:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
Trong đó BUCKET_NAME là tên riêng biệt dùng chung mà bạn đã chọn cho nhóm của mình, sau đó là tên tệp của bức ảnh.
Khi bạn nhấp vào hộp đánh dấu bên cạnh tên ảnh, nút DELETE sẽ được bật và bạn có thể xoá hình ảnh đầu tiên này.
7. Chuẩn bị cơ sở dữ liệu
Bạn sẽ lưu trữ thông tin về hình ảnh do Vision API cung cấp vào cơ sở dữ liệu Cloud Firestore. Đây là một cơ sở dữ liệu tài liệu NoSQL nhanh, hoàn toàn trên đám mây, không máy chủ và được quản lý toàn diện. Chuẩn bị cơ sở dữ liệu bằng cách chuyển đến mục Firestore của Cloud Console:

Bạn có hai lựa chọn: Native mode hoặc Datastore mode. Sử dụng chế độ gốc để có thêm các tính năng như hỗ trợ khi không có mạng và đồng bộ hoá theo thời gian thực.
Nhấp vào biểu tượng SELECT NATIVE MODE.

Chọn một khu vực có nhiều vùng (ở đây là Châu Âu, nhưng lý tưởng nhất là ít nhất phải cùng khu vực với hàm và vùng lưu trữ của bạn).
Nhấp vào nút CREATE DATABASE.
Sau khi tạo cơ sở dữ liệu, bạn sẽ thấy những thông tin sau:

Tạo một bộ sưu tập mới bằng cách nhấp vào nút + START COLLECTION.
Bộ sưu tập tên pictures.

Bạn không cần tạo tài liệu. Bạn sẽ thêm các hình ảnh này theo phương thức lập trình khi hình ảnh mới được lưu trữ trong Cloud Storage và được Vision API phân tích.
Nhấp vào Save.
Firestore sẽ tạo một tài liệu mặc định đầu tiên trong tập hợp mới tạo. Bạn có thể xoá tài liệu đó một cách an toàn vì tài liệu này không chứa thông tin hữu ích nào:

Các tài liệu sẽ được tạo theo chương trình trong bộ sưu tập của chúng tôi sẽ chứa 4 trường:
- name (chuỗi): tên tệp của bức ảnh được tải lên, cũng là khoá của tài liệu
- labels (mảng chuỗi): nhãn của các mục được nhận dạng bằng Vision API
- color (chuỗi): mã màu thập lục phân của màu chủ đạo (ví dụ: #ab12ef)
- created (ngày): dấu thời gian cho biết thời điểm siêu dữ liệu của hình ảnh này được lưu trữ
- thumbnail (boolean): một trường không bắt buộc sẽ xuất hiện và có giá trị true nếu hình thu nhỏ đã được tạo cho bức ảnh này
Vì chúng ta sẽ tìm kiếm trong Firestore để tìm những bức ảnh có hình thu nhỏ và sắp xếp theo ngày tạo, nên chúng ta cần tạo một chỉ mục tìm kiếm.
Bạn có thể tạo chỉ mục bằng lệnh sau trong Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Hoặc bạn cũng có thể thực hiện việc này trên Cloud Console bằng cách nhấp vào Indexes trong cột điều hướng ở bên trái, rồi tạo một chỉ mục kết hợp như minh hoạ dưới đây:

Nhấp vào Create. Quá trình tạo chỉ mục có thể mất vài phút.
8. Sao chép mã
Sao chép mã nếu bạn chưa thực hiện trong lớp học lập trình trước:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Sau đó, bạn có thể chuyển đến thư mục chứa dịch vụ để bắt đầu tạo phòng thí nghiệm:
cd serverless-photosharing-workshop/services/image-analysis/java
Bạn sẽ có bố cục tệp sau cho dịch vụ:

9. Khám phá mã dịch vụ
Bạn bắt đầu bằng cách xem cách Thư viện ứng dụng Java được bật trong pom.xml bằng BOM:
Trước tiên, hãy chỉnh sửa tệp pom.xml liệt kê các phần phụ thuộc của hàm Java. Cập nhật mã để thêm phần phụ thuộc Cloud Vision API Maven:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cloudfunctions</groupId>
<artifactId>gcs-function</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.4</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-firestore</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vision</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
</dependency>
</dependencies>
Chức năng này được triển khai trong lớp EventController. Mỗi khi có một hình ảnh mới được tải lên nhóm, dịch vụ sẽ nhận được thông báo để xử lý:
@RestController
public class EventController {
private static final Logger logger = Logger.getLogger(EventController.class.getName());
private static final List<String> requiredFields = Arrays.asList("ce-id", "ce-source", "ce-type", "ce-specversion");
@RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<String> receiveMessage(
@RequestBody Map<String, Object> body, @RequestHeader Map<String, String> headers) throws IOException, InterruptedException, ExecutionException {
...
}
Mã này sẽ tiếp tục xác thực các tiêu đề Cloud Events:
System.out.println("Header elements");
for (String field : requiredFields) {
if (headers.get(field) == null) {
String msg = String.format("Missing expected header: %s.", field);
System.out.println(msg);
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
} else {
System.out.println(field + " : " + headers.get(field));
}
}
System.out.println("Body elements");
for (String bodyField : body.keySet()) {
System.out.println(bodyField + " : " + body.get(bodyField));
}
if (headers.get("ce-subject") == null) {
String msg = "Missing expected header: ce-subject.";
System.out.println(msg);
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
Giờ đây, bạn có thể tạo một yêu cầu và mã sẽ chuẩn bị một yêu cầu như vậy để gửi đến Vision API:
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
List<AnnotateImageRequest> requests = new ArrayList<>();
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
requests.add(request);
Chúng tôi yêu cầu 3 chức năng chính của Vision API:
- Phát hiện nhãn: để hiểu nội dung trong những bức ảnh đó
- Thuộc tính hình ảnh: để cung cấp các thuộc tính thú vị của hình ảnh (chúng tôi quan tâm đến màu sắc chủ đạo của hình ảnh)
- Tìm kiếm an toàn: để biết liệu hình ảnh có an toàn để hiển thị hay không (không được chứa nội dung người lớn / y tế / không phù hợp / nội dung bạo lực)
Đến đây, chúng ta có thể gọi Vision API:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Để tham khảo, sau đây là phản hồi từ Vision API:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
Nếu không có lỗi nào được trả về, chúng ta có thể chuyển sang bước tiếp theo. Đó là lý do chúng ta có khối if này:
if (responses.size() == 0) {
logger.info("No response received from Vision API.");
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
}
Chúng ta sẽ lấy nhãn của những vật thể, danh mục hoặc chủ đề được nhận dạng trong bức ảnh:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Chúng tôi muốn biết màu chủ đạo của bức ảnh:
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
Hãy kiểm tra xem bức ảnh có an toàn để hiển thị hay không:
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
Chúng tôi đang kiểm tra các đặc điểm người lớn / giả mạo / y tế / bạo lực / không phù hợp cho người xem chưa đến tuổi trưởng thành để xem chúng có khả năng hoặc rất có khả năng xuất hiện hay không.
Nếu kết quả tìm kiếm an toàn là ổn, chúng ta có thể lưu trữ siêu dữ liệu trong Firestore:
// Saving result to Firestore
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
10. Tạo hình ảnh ứng dụng bằng GraalVM (không bắt buộc)
Trong bước không bắt buộc này, bạn sẽ tạo một JIT(JVM) based app image, sau đó là một AOT(Native) Java app image bằng GraalVM.
Để chạy bản dựng, bạn cần đảm bảo rằng bạn có JDK phù hợp và trình tạo native-image đã được cài đặt và định cấu hình. Có một số lựa chọn.
To start, hãy tải GraalVM 22.2.x Community Edition xuống và làm theo hướng dẫn trên trang cài đặt GraalVM.
Bạn có thể đơn giản hoá đáng kể quy trình này nhờ SDKMAN!
Để cài đặt bản phân phối JDK thích hợp bằng SDKman, hãy bắt đầu bằng cách sử dụng lệnh cài đặt:
sdk install java 22.2.r17-grl
Hướng dẫn SDKman sử dụng phiên bản này cho cả bản dựng JIT và AOT:
sdk use java 22.2.0.r17-grl
Cài đặt native-image utility cho GraalVM:
gu install native-image
Trong Cloudshell, để thuận tiện, bạn có thể cài đặt GraalVM và tiện ích native-image bằng các lệnh đơn giản sau:
# install GraalVM in your home directory cd ~ # download GraalVM wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.2.0/graalvm-ce-java17-linux-amd64-22.2.0.tar.gz ls tar -xzvf graalvm-ce-java17-linux-amd64-22.2.0.tar.gz # configure Java 17 and GraalVM 22.2 echo Existing JVM: $JAVA_HOME cd graalvm-ce-java17-22.2.0 export JAVA_HOME=$PWD cd bin export PATH=$PWD:$PATH echo JAVA HOME: $JAVA_HOME echo PATH: $PATH # install the native image utility java -version gu install native-image cd ../..
Trước tiên, hãy đặt các biến môi trường dự án GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Sau đó, bạn có thể chuyển đến thư mục chứa dịch vụ để bắt đầu tạo phòng thí nghiệm:
cd serverless-photosharing-workshop/services/image-analysis/java
Tạo hình ảnh ứng dụng JIT(JVM):
./mvnw package -Pjvm
Quan sát nhật ký bản dựng trong thiết bị đầu cuối:
... [INFO] --- spring-boot-maven-plugin:2.7.3:repackage (repackage) @ image-analysis --- [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 24.009 s [INFO] Finished at: 2022-09-26T22:17:32-04:00 [INFO] ------------------------------------------------------------------------
Tạo hình ảnh AOT(Native):.
./mvnw package -Pnative -DskipTests
Quan sát nhật ký bản dựng trong thiết bị đầu cuối, bao gồm cả nhật ký bản dựng hình ảnh gốc:
Xin lưu ý rằng bản dựng sẽ mất nhiều thời gian hơn, tuỳ thuộc vào máy mà bạn đang kiểm thử.
...
[2/7] Performing analysis... [**********] (95.4s @ 3.57GB)
23,346 (94.42%) of 24,725 classes reachable
44,625 (68.71%) of 64,945 fields reachable
163,759 (70.79%) of 231,322 methods reachable
989 classes, 1,402 fields, and 11,032 methods registered for reflection
63 classes, 69 fields, and 55 methods registered for JNI access
5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z
[3/7] Building universe... (10.0s @ 5.35GB)
[4/7] Parsing methods... [***] (9.7s @ 3.13GB)
[5/7] Inlining methods... [***] (4.5s @ 3.29GB)
[6/7] Compiling methods... [[6/7] Compiling methods... [********] (67.6s @ 5.72GB)
[7/7] Creating image... (8.7s @ 4.59GB)
62.21MB (54.80%) for code area: 100,371 compilation units
50.98MB (44.91%) for image heap: 465,035 objects and 365 resources
337.09KB ( 0.29%) for other data
113.52MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area: Top 10 object types in image heap:
2.36MB com.google.protobuf 12.70MB byte[] for code metadata
1.90MB i.g.xds.shaded.io.envoyproxy.envoy.config.core.v3 6.66MB java.lang.Class
1.73MB i.g.x.shaded.io.envoyproxy.envoy.config.route.v3 6.47MB byte[] for embedded resources
1.67MB sun.security.ssl 4.61MB byte[] for java.lang.String
1.54MB com.google.cloud.vision.v1 4.37MB java.lang.String
1.46MB com.google.firestore.v1 3.38MB byte[] for general heap data
1.37MB io.grpc.xds.shaded.io.envoyproxy.envoy.api.v2.core 1.96MB com.oracle.svm.core.hub.DynamicHubCompanion
1.32MB i.g.xds.shaded.io.envoyproxy.envoy.api.v2.route 1.80MB byte[] for reflection metadata
1.09MB java.util 911.80KB java.lang.String[]
1.08MB com.google.re2j 826.48KB c.o.svm.core.hub.DynamicHub$ReflectionMetadata
45.91MB for 772 more packages 6.45MB for 3913 more object types
------------------------------------------------------------------------------------------------------------------------
15.1s (6.8% of total time) in 56 GCs | Peak RSS: 7.72GB | CPU load: 4.37
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
/Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis (executable)
/Users/ddobrin/work/dan/serverless-photosharing-workshop/services/image-analysis/java/target/image-analysis.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'image-analysis' in 3m 41s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:56 min
[INFO] Finished at: 2022-09-26T22:22:29-04:00
[INFO] ------------------------------------------------------------------------
11. Tạo và xuất bản hình ảnh vùng chứa
Hãy tạo một hình ảnh vùng chứa theo 2 phiên bản: một phiên bản là JIT(JVM) image và phiên bản còn lại là AOT(Native) Java image.
Trước tiên, hãy đặt các biến môi trường dự án GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
Tạo hình ảnh JIT(JVM):.
./mvnw package -Pjvm-image
Quan sát nhật ký bản dựng trong thiết bị đầu cuối:
[INFO] [creator] Adding layer 'process-types' [INFO] [creator] Adding label 'io.buildpacks.lifecycle.metadata' [INFO] [creator] Adding label 'io.buildpacks.build.metadata' [INFO] [creator] Adding label 'io.buildpacks.project.metadata' [INFO] [creator] Adding label 'org.opencontainers.image.title' [INFO] [creator] Adding label 'org.opencontainers.image.version' [INFO] [creator] Adding label 'org.springframework.boot.version' [INFO] [creator] Setting default process type 'web' [INFO] [creator] Saving docker.io/library/image-analysis-jvm:r17... [INFO] [creator] *** Images (03a44112456e): [INFO] [creator] docker.io/library/image-analysis-jvm:r17 [INFO] [creator] Adding cache layer 'paketo-buildpacks/syft:syft' [INFO] [creator] Adding cache layer 'cache.sbom' [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-jvm:r17' [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 02:11 min [INFO] Finished at: 2022-09-26T13:09:34-04:00 [INFO] ------------------------------------------------------------------------
Tạo hình ảnh AOT(Native):.
./mvnw package -Pnative-image
Quan sát nhật ký bản dựng trong thiết bị đầu cuối, bao gồm cả nhật ký bản dựng hình ảnh gốc và quá trình nén hình ảnh bằng UPX.
Lưu ý rằng bản dựng sẽ mất nhiều thời gian hơn, tuỳ thuộc vào máy mà bạn đang kiểm thử
... [INFO] [creator] [2/7] Performing analysis... [***********] (147.6s @ 3.10GB) [INFO] [creator] 23,362 (94.34%) of 24,763 classes reachable [INFO] [creator] 44,657 (68.67%) of 65,029 fields reachable [INFO] [creator] 163,926 (70.76%) of 231,656 methods reachable [INFO] [creator] 981 classes, 1,402 fields, and 11,026 methods registered for reflection [INFO] [creator] 63 classes, 68 fields, and 55 methods registered for JNI access [INFO] [creator] 4 native libraries: dl, pthread, rt, z [INFO] [creator] [3/7] Building universe... (21.1s @ 2.66GB) [INFO] [creator] [4/7] Parsing methods... [****] (13.7s @ 4.16GB) [INFO] [creator] [5/7] Inlining methods... [***] (9.6s @ 4.20GB) [INFO] [creator] [6/7] Compiling methods... [**********] (107.6s @ 3.36GB) [INFO] [creator] [7/7] Creating image... (14.7s @ 4.87GB) [INFO] [creator] 62.24MB (51.35%) for code area: 100,499 compilation units [INFO] [creator] 51.99MB (42.89%) for image heap: 473,948 objects and 473 resources [INFO] [creator] 6.98MB ( 5.76%) for other data [INFO] [creator] 121.21MB in total [INFO] [creator] -------------------------------------------------------------------------------- [INFO] [creator] Top 10 packages in code area: Top 10 object types in image heap: [INFO] [creator] 2.36MB com.google.protobuf 12.71MB byte[] for code metadata [INFO] [creator] 1.90MB i.g.x.s.i.e.e.config.core.v3 7.59MB byte[] for embedded resources [INFO] [creator] 1.73MB i.g.x.s.i.e.e.config.route.v3 6.66MB java.lang.Class [INFO] [creator] 1.67MB sun.security.ssl 4.62MB byte[] for java.lang.String [INFO] [creator] 1.54MB com.google.cloud.vision.v1 4.39MB java.lang.String [INFO] [creator] 1.46MB com.google.firestore.v1 3.66MB byte[] for general heap data [INFO] [creator] 1.37MB i.g.x.s.i.e.envoy.api.v2.core 1.96MB c.o.s.c.h.DynamicHubCompanion [INFO] [creator] 1.32MB i.g.x.s.i.e.e.api.v2.route 1.80MB byte[] for reflection metadata [INFO] [creator] 1.09MB java.util 910.41KB java.lang.String[] [INFO] [creator] 1.08MB com.google.re2j 826.95KB c.o.s.c.h.DynamicHu~onMetadata [INFO] [creator] 45.94MB for 776 more packages 6.69MB for 3916 more object types [INFO] [creator] -------------------------------------------------------------------------------- [INFO] [creator] 20.4s (5.6% of total time) in 81 GCs | Peak RSS: 6.75GB | CPU load: 4.53 [INFO] [creator] -------------------------------------------------------------------------------- [INFO] [creator] Produced artifacts: [INFO] [creator] /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication (executable) [INFO] [creator] /layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication.build_artifacts.txt (txt) [INFO] [creator] ================================================================================ [INFO] [creator] Finished generating '/layers/paketo-buildpacks_native-image/native-image/services.ImageAnalysisApplication' in 5m 59s. [INFO] [creator] Executing upx to compress native image [INFO] [creator] Ultimate Packer for eXecutables [INFO] [creator] Copyright (C) 1996 - 2020 [INFO] [creator] UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 [INFO] [creator] [INFO] [creator] File size Ratio Format Name [INFO] [creator] -------------------- ------ ----------- ----------- 127099880 -> 32416676 25.50% linux/amd64 services.ImageAnalysisApplication ... [INFO] [creator] ===> EXPORTING ... [INFO] [creator] Adding cache layer 'paketo-buildpacks/native-image:native-image' [INFO] [creator] Adding cache layer 'cache.sbom' [INFO] [INFO] Successfully built image 'docker.io/library/image-analysis-native:r17' ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 05:28 min [INFO] Finished at: 2022-09-26T13:19:53-04:00 [INFO] ------------------------------------------------------------------------
Xác thực rằng các hình ảnh đã được tạo:
docker images | grep image-analysis
Gắn thẻ và đẩy 2 hình ảnh lên GCR:
# JIT(JVM) image
docker tag image-analysis-jvm:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17
# AOT(Native) image
docker tag image-analysis-native:r17 gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17
12. Triển khai lên Cloud Run
Thời gian triển khai dịch vụ.
Bạn sẽ triển khai dịch vụ hai lần, một lần bằng hình ảnh JIT(JVM) và lần thứ hai bằng hình ảnh AOT(Native). Cả hai hoạt động triển khai dịch vụ sẽ xử lý cùng một hình ảnh từ nhóm theo cách song song để so sánh.
Trước tiên, hãy đặt các biến môi trường dự án GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1
Triển khai hình ảnh JIT(JVM) và quan sát nhật ký triển khai trong bảng điều khiển:
gcloud run deploy image-analysis-jvm \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-jvm:r17 \
--region europe-west1 \
--memory 2Gi --allow-unauthenticated
...
Deploying container to Cloud Run service [image-analysis-jvm] in project [...] region [europe-west1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [image-analysis-jvm] revision [image-analysis-jvm-00009-huc] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-jvm-...-ew.a.run.app
Triển khai hình ảnh AOT(Native) và quan sát nhật ký triển khai trong bảng điều khiển:
gcloud run deploy image-analysis-native \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/image-analysis-native:r17 \
--region europe-west1 \
--memory 2Gi --allow-unauthenticated
...
Deploying container to Cloud Run service [image-analysis-native] in project [...] region [europe-west1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [image-analysis-native] revision [image-analysis-native-00005-ben] has been deployed and is serving 100 percent of traffic.
Service URL: https://image-analysis-native-...-ew.a.run.app
13. Thiết lập điều kiện kích hoạt Eventarc
Eventarc cung cấp một giải pháp tiêu chuẩn hoá để quản lý luồng thay đổi trạng thái (gọi là sự kiện) giữa các vi dịch vụ tách rời. Khi được kích hoạt, Eventarc sẽ định tuyến các sự kiện này thông qua các thuê bao Pub/Sub đến nhiều đích đến (trong tài liệu này, hãy xem phần Đích đến của sự kiện) trong khi quản lý việc phân phối, bảo mật, uỷ quyền, khả năng ghi nhận và xử lý lỗi cho bạn.
Bạn có thể tạo một trình kích hoạt Eventarc để dịch vụ Cloud Run nhận được thông báo về một sự kiện hoặc một nhóm sự kiện cụ thể. Bằng cách chỉ định bộ lọc cho điều kiện kích hoạt, bạn có thể định cấu hình hoạt động định tuyến của sự kiện, bao gồm cả nguồn sự kiện và dịch vụ Cloud Run đích.
Trước tiên, hãy đặt các biến môi trường dự án GCP:
export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
gcloud config set project ${GOOGLE_CLOUD_PROJECT}
gcloud config set run/region
gcloud config set run/platform managed
gcloud config set eventarc/location europe-west1
Cấp pubsub.publisher cho tài khoản dịch vụ Cloud Storage:
SERVICE_ACCOUNT="$(gsutil kms serviceaccount -p ${GOOGLE_CLOUD_PROJECT})"
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \
--member="serviceAccount:${SERVICE_ACCOUNT}" \
--role='roles/pubsub.publisher'
Thiết lập các trình kích hoạt Eventarc cho cả hình ảnh dịch vụ JVM(JIT) và AOT(Native) để xử lý hình ảnh:
gcloud eventarc triggers list --location=eu
gcloud eventarc triggers create image-analysis-jvm-trigger \
--destination-run-service=image-analysis-jvm \
--destination-run-region=europe-west1 \
--location=eu \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
--service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
gcloud eventarc triggers create image-analysis-native-trigger \
--destination-run-service=image-analysis-native \
--destination-run-region=europe-west1 \
--location=eu \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}" \
--service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
Quan sát thấy 2 điều kiện kích hoạt đã được tạo:
gcloud eventarc triggers list --location=eu
14. Phiên bản dịch vụ thử nghiệm
Sau khi triển khai dịch vụ thành công, bạn sẽ đăng một bức ảnh lên Cloud Storage, xem các dịch vụ của chúng tôi có được gọi hay không, Vision API trả về những gì và liệu siêu dữ liệu có được lưu trữ trong Firestore hay không.
Quay lại Cloud Storage rồi nhấp vào nhóm chúng ta đã tạo ở đầu phòng thí nghiệm:

Khi bạn chuyển đến trang thông tin chi tiết về bộ chứa, hãy nhấp vào nút Upload files để tải ảnh lên.
Ví dụ: hình ảnh GeekHour.jpeg được cung cấp cùng với cơ sở mã của bạn trong /services/image-analysis/java. Chọn một hình ảnh rồi nhấn phím Open button:

Giờ đây, bạn có thể kiểm tra quá trình thực thi dịch vụ, bắt đầu bằng image-analysis-jvm, sau đó là image-analysis-native.
Trong trình đơn "hamburger" (☰), hãy chuyển đến dịch vụ Cloud Run > image-analysis-jvm.
Nhấp vào Nhật ký và quan sát kết quả:

Và thực tế là trong danh sách nhật ký, tôi có thể thấy rằng dịch vụ JIT(JVM) image-analysis-jvm đã được gọi.
Nhật ký cho biết thời điểm bắt đầu và kết thúc quá trình thực thi dịch vụ. Và ở giữa, chúng ta có thể thấy các nhật ký mà chúng ta đưa vào hàm bằng các câu lệnh nhật ký ở cấp INFO. Chúng ta thấy:
- Thông tin chi tiết về sự kiện kích hoạt hàm của chúng ta,
- Kết quả thô từ lệnh gọi Vision API,
- Các nhãn được tìm thấy trong bức ảnh mà chúng tôi đã tải lên,
- Thông tin về màu chủ đạo,
- Liệu bức ảnh có an toàn để hiển thị hay không,
- Và cuối cùng, những siêu dữ liệu đó về bức ảnh đã được lưu trữ trong Firestore.
Bạn sẽ lặp lại quy trình này cho dịch vụ image-analysis-native.
Trong trình đơn "hamburger" (☰), hãy chuyển đến dịch vụ Cloud Run > image-analysis-native.
Nhấp vào Nhật ký và quan sát kết quả:

Bây giờ, bạn sẽ muốn quan sát xem siêu dữ liệu hình ảnh đã được lưu trữ trong Fiorestore hay chưa.
Trong trình đơn "bánh hamburger" (☰), hãy chuyển đến phần Firestore. Trong mục con Data (hiển thị theo mặc định), bạn sẽ thấy bộ sưu tập pictures có một tài liệu mới được thêm vào, tương ứng với bức ảnh bạn vừa tải lên:

15. 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_PICTURES}
Xoá hàm:
gcloud functions delete picture-uploaded --region europe-west1 -q
Xoá bộ sưu tập Firestore bằng cách chọn Xoá bộ sưu tập trong bộ sưu tập:

Ngoài ra, bạn có thể xoá toàn bộ dự án:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
16. Xin chúc mừng!
Xin chúc mừng! Bạn đã triển khai thành công dịch vụ mã khoá đầu tiên của dự án!
Nội dung đã đề cập
- Cloud Storage
- Cloud Run
- Cloud Vision API
- Cloud Firestore
- Hình ảnh Java gốc