1. Tổng quan
Phòng thí nghiệm này minh hoạ các tính năng và chức năng được thiết kế để đơn giản hoá quy trình làm việc cho các kỹ sư phần mềm có nhiệm vụ phát triển ứng dụng Java trong môi trường vùng chứa. Việc phát triển vùng chứa thông thường đòi hỏi người dùng phải hiểu rõ thông tin chi tiết về vùng chứa và quy trình xây dựng vùng chứa. Ngoài ra, nhà phát triển thường phải ngắt quy trình của mình, chuyển ra khỏi IDE để kiểm thử và gỡ lỗi ứng dụng trong môi trường từ xa. Với các công cụ và công nghệ được đề cập trong hướng dẫn này, nhà phát triển có thể làm việc hiệu quả với các ứng dụng được chứa trong vùng chứa mà không cần rời khỏi IDE.
Kiến thức bạn sẽ học được
Trong phòng thí nghiệm này, bạn sẽ tìm hiểu các phương pháp phát triển bằng vùng chứa trong GCP, bao gồm:
- Phát triển InnerLoop bằng Cloud Workstations
- Tạo một ứng dụng khởi động Java mới
- Tìm hiểu quy trình phát triển
- Phát triển một Dịch vụ REST CRUD đơn giản
- Gỡ lỗi ứng dụng trên cụm GKE
- Kết nối ứng dụng với cơ sở dữ liệu CloudSQL

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 là mã 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 mã này 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 Trình chỉnh sửa Cloud Shell
Bài tập thực hành này được thiết kế và thử nghiệm để sử dụng với Trình chỉnh sửa Google Cloud Shell. Để truy cập vào trình chỉnh sửa,
- truy cập vào dự án của bạn trên Google tại https://console.cloud.google.com.
- Ở góc trên cùng bên phải, hãy nhấp vào biểu tượng trình chỉnh sửa Cloud Shell

- Một ngăn mới sẽ mở ra ở cuối cửa sổ
- Nhấp vào nút Mở trình chỉnh sửa

- Trình chỉnh sửa sẽ mở ra với một trình khám phá ở bên phải và trình chỉnh sửa ở khu vực trung tâm
- Một ngăn cửa sổ dòng lệnh cũng phải có ở cuối màn hình
- Nếu thiết bị đầu cuối CHƯA mở, hãy dùng tổ hợp phím "Ctrl+" để mở một cửa sổ dòng lệnh mới
Thiết lập gcloud
Trong Cloud Shell, hãy đặt mã dự án và khu vực mà bạn muốn triển khai ứng dụng. Lưu các giá trị này dưới dạng biến PROJECT_ID và REGION.
export REGION=us-central1
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
Nhân bản mã nguồn
Mã nguồn cho lớp học này nằm trong container-developer-workshop trong GoogleCloudPlatform trên GitHub. Sao chép bằng lệnh bên dưới, sau đó thay đổi thành thư mục.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot
Cung cấp cơ sở hạ tầng được dùng trong phòng thí nghiệm này
Trong phòng thí nghiệm này, bạn sẽ triển khai mã cho GKE và truy cập vào dữ liệu được lưu trữ trong cơ sở dữ liệu CloudSQL. Tập lệnh thiết lập bên dưới sẽ chuẩn bị cơ sở hạ tầng này cho bạn. Quá trình cấp phép sẽ mất hơn 25 phút. Đợi tập lệnh hoàn tất rồi mới chuyển sang phần tiếp theo.
./setup_with_cw.sh &
Cụm Cloud Workstations
Mở Cloud Workstations trong Cloud Console. Chờ cụm ở trạng thái READY.
Tạo cấu hình trạm làm việc
Nếu phiên Cloud Shell của bạn bị ngắt kết nối, hãy nhấp vào "Kết nối lại" rồi chạy lệnh gcloud cli để đặt mã dự án. Thay thế mã dự án mẫu bên dưới bằng mã dự án qwiklabs của bạn trước khi chạy lệnh.
gcloud config set project qwiklabs-gcp-project-id
Chạy tập lệnh bên dưới trong thiết bị đầu cuối để tạo cấu hình Cloud Workstations.
cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh
Xác minh kết quả trong phần Cấu hình. Quá trình chuyển sang trạng thái SẴN SÀNG sẽ mất 2 phút.

Mở Cloud Workstations trong Bảng điều khiển rồi tạo thực thể mới.

Đổi tên thành my-workstation và chọn cấu hình hiện có: codeoss-java.

Xác minh kết quả trong phần Máy trạm.

Khởi chạy Workstation
Khởi động và chạy máy trạm.

Cho phép cookie của bên thứ ba bằng cách nhấp vào biểu tượng trên thanh địa chỉ. 

Nhấp vào "Trang web không hoạt động?".

Nhấp vào "Cho phép cookie".

Sau khi máy trạm khởi động, bạn sẽ thấy IDE Code OSS xuất hiện. Nhấp vào "Đánh dấu là đã hoàn tất" trên trang Bắt đầu của IDE máy trạm

3. Tạo một ứng dụng khởi động Java mới
Trong phần này, bạn sẽ tạo một ứng dụng Java Spring Boot mới từ đầu bằng cách sử dụng một ứng dụng mẫu do spring.io cung cấp. Mở một cửa sổ dòng lệnh mới.

Nhân bản ứng dụng mẫu
- Tạo một ứng dụng khởi động
curl https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=17 -d packageName=com.example.springboot -o sample-app.zip
Nhấp vào nút Cho phép nếu bạn thấy thông báo này để có thể sao chép và dán vào máy trạm.

- Giải nén ứng dụng
unzip sample-app.zip -d sample-app
- Mở thư mục "sample-app"
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"
Thêm spring-boot-devtools và Jib
Để bật Spring Boot DevTools, hãy tìm và mở pom.xml trong trình khám phá của trình chỉnh sửa. Tiếp theo, hãy dán mã sau vào sau dòng mô tả có nội dung <description>Demo project for Spring Boot</description>
- Thêm spring-boot-devtools vào pom.xml
Mở pom.xml trong thư mục gốc của dự án. Thêm cấu hình sau vào mục Description.
pom.xml
<!-- Spring profiles-->
<profiles>
<profile>
<id>sync</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
- Bật jib-maven-plugin trong pom.xml
Jib là một công cụ tạo vùng chứa Java nguồn mở của Google, cho phép nhà phát triển Java tạo vùng chứa bằng các công cụ Java mà họ biết. Jib là một trình tạo hình ảnh vùng chứa đơn giản và nhanh chóng, xử lý tất cả các bước đóng gói ứng dụng của bạn vào một hình ảnh vùng chứa. Bạn không cần phải viết Dockerfile hoặc cài đặt Docker, đồng thời, nó được tích hợp trực tiếp vào Maven và Gradle.
Di chuyển xuống trong tệp pom.xml và cập nhật phần Build để thêm trình bổ trợ Jib. Phần bản dựng phải khớp với phần sau khi hoàn tất.
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Jib Plugin-->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<!-- Maven Resources Plugin-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
Tạo tệp kê khai
Skaffold cung cấp các công cụ tích hợp để đơn giản hoá quá trình phát triển vùng chứa. Trong bước này, bạn sẽ khởi động Skaffold. Skaffold sẽ tự động tạo các tệp YAML cơ sở của Kubernetes. Quy trình này cố gắng xác định các thư mục có định nghĩa hình ảnh vùng chứa, chẳng hạn như tệp Docker, rồi tạo một tệp kê khai triển khai và dịch vụ cho từng thư mục.
Thực thi lệnh bên dưới trong Terminal để bắt đầu quy trình.

- Thực thi lệnh sau trong dòng lệnh
skaffold init --generate-manifests
- Khi thấy lời nhắc:
- Dùng các mũi tên để di chuyển con trỏ đến biểu tượng
Jib Maven Plugin - Nhấn phím cách để chọn chế độ này.
- Nhấn phím Enter để tiếp tục
- Nhập 8080 cho cổng
- Nhập y để lưu cấu hình
Hai tệp được thêm vào không gian làm việc skaffold.yaml và deployment.yaml
Đầu ra của Skaffold:

Cập nhật tên ứng dụng
Các giá trị mặc định có trong cấu hình hiện không khớp với tên ứng dụng của bạn. Cập nhật các tệp để tham chiếu đến tên ứng dụng của bạn thay vì các giá trị mặc định.
- Thay đổi các mục trong cấu hình Skaffold
- Mở
skaffold.yaml - Chọn tên hình ảnh hiện được đặt làm
pom-xml-image - Nhấp chuột phải rồi chọn Thay đổi tất cả các lần xuất hiện
- Nhập tên mới bằng
demo-app
- Thay đổi các mục trong cấu hình Kubernetes
- Mở tệp
deployment.yaml - Chọn tên hình ảnh hiện được đặt làm
pom-xml-image - Nhấp chuột phải rồi chọn Thay đổi tất cả các lần xuất hiện
- Nhập tên mới bằng
demo-app
Bật chế độ Tự động đồng bộ hoá
Để tạo điều kiện cho trải nghiệm tải lại nóng được tối ưu hoá, bạn sẽ sử dụng tính năng Đồng bộ hoá do Jib cung cấp. Trong bước này, bạn sẽ định cấu hình Skaffold để sử dụng tính năng đó trong quy trình xây dựng.
Xin lưu ý rằng hồ sơ "sync" mà bạn đang định cấu hình trong cấu hình Skaffold sẽ tận dụng Hồ sơ "sync" của Spring mà bạn đã định cấu hình ở bước trước, trong đó bạn đã bật tính năng hỗ trợ cho spring-dev-tools.
- Cập nhật cấu hình Skaffold
Trong tệp skaffold.yaml, hãy thay thế toàn bộ phần bản dựng của tệp bằng quy cách sau. Không sửa đổi các phần khác của tệp.
skaffold.yaml
build:
artifacts:
- image: demo-app
jib:
project: com.example:demo
type: maven
args:
- --no-transfer-progress
- -Psync
fromImage: gcr.io/distroless/java17-debian11:debug
sync:
auto: true
Thêm tuyến đường mặc định
Tạo một tệp có tên HelloController.java trong thư mục /src/main/java/com/example/springboot/.

Dán nội dung sau vào tệp để tạo một tuyến đường http mặc định.
HelloController.java
package com.example.springboot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;
@RestController
public class HelloController {
@Value("${target:local}")
String target;
@GetMapping("/")
public String hello()
{
return String.format("Hello from your %s environment!", target);
}
}
4. Tìm hiểu quy trình phát triển
Trong phần này, bạn sẽ thực hiện một số bước bằng cách sử dụng trình bổ trợ Cloud Code để tìm hiểu các quy trình cơ bản và xác thực cấu hình cũng như chế độ thiết lập của ứng dụng khởi đầu.
Cloud Code tích hợp với Skaffold để tinh giản quy trình phát triển của bạn. Khi bạn triển khai đến GKE trong các bước sau, Cloud Code và Skaffold sẽ tự động tạo hình ảnh vùng chứa, đẩy hình ảnh đó vào một Container Registry, rồi triển khai ứng dụng của bạn đến GKE. Quá trình này diễn ra ở chế độ nền, giúp loại bỏ các thông tin chi tiết khỏi quy trình của nhà phát triển. Cloud Code cũng giúp nâng cao quy trình phát triển của bạn bằng cách cung cấp các chức năng gỡ lỗi và đồng bộ hoá nóng truyền thống cho quá trình phát triển dựa trên vùng chứa.
Đăng nhập vào Google Cloud
Nhấp vào biểu tượng Cloud Code rồi chọn "Đăng nhập vào Google Cloud":

Nhấp vào "Tiếp tục đăng nhập".

Kiểm tra đầu ra trong Terminal và mở đường liên kết:

Đăng nhập bằng thông tin đăng nhập của học viên Qwiklabs.

Chọn "Cho phép":

Sao chép mã xác minh rồi quay lại thẻ Workstation.

Dán mã xác minh rồi nhấn Enter.

Thêm cụm Kubernetes
- Thêm một Cụm

- Chọn Google Kubernetes Engine:

- Chọn dự án.

- Chọn "quote-cluster" được tạo trong quá trình thiết lập ban đầu.


Đặt mã dự án hiện tại bằng giao diện dòng lệnh gcloud
Sao chép mã dự án cho bài thực hành này từ trang qwiklabs.

Chạy lệnh gcloud cli để đặt mã dự án. Thay thế mã dự án mẫu trước khi chạy lệnh.
gcloud config set project qwiklabs-gcp-project-id
Kết quả mẫu:

Gỡ lỗi trên Kubernetes
- Ở ngăn bên trái dưới cùng, hãy chọn Cloud Code.

- Trong bảng điều khiển xuất hiện trong phần DEVELOPMENT SESSIONS (PHIÊN PHÁT TRIỂN), hãy chọn Debug on Kubernetes (Gỡ lỗi trên Kubernetes).
Cuộn xuống nếu bạn không thấy lựa chọn này.

- Chọn "Có" để sử dụng ngữ cảnh hiện tại.

- Chọn "quote-cluster" mà bạn đã tạo trong quá trình thiết lập ban đầu.

- Chọn Container Repository (Kho lưu trữ vùng chứa).

- Chọn thẻ Đầu ra trong ngăn dưới cùng để xem tiến trình và thông báo
- Chọn "Kubernetes: Run/Debug – Detailed" (Kubernetes: Chạy/Gỡ lỗi – Chi tiết) trong trình đơn thả xuống ở bên phải để xem thêm thông tin chi tiết và nhật ký phát trực tiếp từ các vùng chứa

Đợi ứng dụng được triển khai.

- Xem lại ứng dụng đã triển khai trên GKE trong Cloud Console.

- Quay lại chế độ xem đơn giản bằng cách chọn "Kubernetes: Run/Debug" (Kubernetes: Chạy/Gỡ lỗi) trong trình đơn thả xuống trên thẻ OUTPUT (ĐẦU RA).
- Khi quá trình tạo và kiểm thử hoàn tất, thẻ Output (Đầu ra) sẽ có nội dung:
Resource deployment/demo-app status completed successfullyvà một URL được liệt kê: "Forwarded URL from service demo-app: http://localhost:8080" (URL được chuyển tiếp từ ứng dụng minh hoạ dịch vụ: http://localhost:8080) - Trong thiết bị đầu cuối Cloud Code, hãy di chuột qua URL trong đầu ra (http://localhost:8080), sau đó trong chú thích xuất hiện, hãy chọn Follow link (Truy cập đường liên kết).

Thẻ mới sẽ mở ra và bạn sẽ thấy đầu ra như bên dưới:

Khai thác điểm ngắt
- Mở ứng dụng
HelloController.javanằm tại/src/main/java/com/example/springboot/HelloController.java - Tìm câu lệnh trả về cho đường dẫn gốc có nội dung
return String.format("Hello from your %s environment!", target); - Thêm một điểm ngắt vào dòng đó bằng cách nhấp vào khoảng trống ở bên trái số dòng. Một chỉ báo màu đỏ sẽ xuất hiện để cho biết điểm ngắt đã được đặt

- Tải lại trình duyệt và lưu ý rằng trình gỡ lỗi sẽ dừng quy trình tại điểm ngắt, đồng thời cho phép bạn kiểm tra các biến và trạng thái của ứng dụng đang chạy từ xa trong GKE

- Nhấp vào phần biến cho đến khi bạn tìm thấy biến "Target" (Mục tiêu).
- Quan sát giá trị hiện tại là "local"

- Nhấp đúp vào tên biến "target" và trong cửa sổ bật lên,
thay đổi giá trị thành "Cloud Workstations"

- Nhấp vào nút Tiếp tục trong bảng điều khiển gỡ lỗi

- Xem lại phản hồi trong trình duyệt. Lúc này, trình duyệt sẽ cho thấy giá trị mới cập nhật mà bạn vừa nhập.

- Xoá điểm ngắt bằng cách nhấp vào chỉ báo màu đỏ ở bên trái số dòng. Điều này sẽ ngăn mã của bạn dừng thực thi ở dòng này khi bạn chuyển sang bước tiếp theo trong lớp học này.
Tải lại nóng
- Thay đổi câu lệnh để trả về một giá trị khác, chẳng hạn như "Xin chào từ %s Code"
- Tệp này sẽ tự động được lưu và đồng bộ hoá vào các vùng chứa từ xa trong GKE
- Hãy làm mới trình duyệt để xem kết quả mới nhất.
- Dừng phiên gỡ lỗi bằng cách nhấp vào hình vuông màu đỏ trên thanh công cụ gỡ lỗi

Chọn "Có, dọn dẹp sau mỗi lần chạy".

5. Phát triển một Dịch vụ REST CRUD đơn giản
Đến đây, ứng dụng của bạn đã được định cấu hình đầy đủ cho quá trình phát triển theo mô hình vùng chứa và bạn đã tìm hiểu quy trình phát triển cơ bản bằng Cloud Code. Trong các phần sau, bạn sẽ thực hành những gì đã học bằng cách thêm các điểm cuối dịch vụ REST kết nối với một cơ sở dữ liệu được quản lý trong Google Cloud.
Định cấu hình các phần phụ thuộc
Mã xử lý ứng dụng sử dụng cơ sở dữ liệu để duy trì dữ liệu dịch vụ còn lại. Đảm bảo các phần phụ thuộc có sẵn bằng cách thêm nội dung sau vào pom.xl
- Mở tệp
pom.xmlrồi thêm nội dung sau vào phần phụ thuộc của cấu hình
pom.xml
<!-- Database dependencies-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
Dịch vụ REST của mã
Quote.java
Tạo một tệp có tên là Quote.java trong /src/main/java/com/example/springboot/ rồi sao chép đoạn mã bên dưới vào. Thao tác này xác định mô hình Thực thể cho đối tượng Báo giá được dùng trong ứng dụng.
package com.example.springboot;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Objects;
@Entity
@Table(name = "quotes")
public class Quote
{
@Id
@Column(name = "id")
private Integer id;
@Column(name="quote")
private String quote;
@Column(name="author")
private String author;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getQuote() {
return quote;
}
public void setQuote(String quote) {
this.quote = quote;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Quote quote1 = (Quote) o;
return Objects.equals(id, quote1.id) &&
Objects.equals(quote, quote1.quote) &&
Objects.equals(author, quote1.author);
}
@Override
public int hashCode() {
return Objects.hash(id, quote, author);
}
}
QuoteRepository.java
Tạo một tệp có tên là QuoteRepository.java tại src/main/java/com/example/springboot rồi sao chép đoạn mã sau vào
package com.example.springboot;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface QuoteRepository extends JpaRepository<Quote,Integer> {
@Query( nativeQuery = true, value =
"SELECT id,quote,author FROM quotes ORDER BY RANDOM() LIMIT 1")
Quote findRandomQuote();
}
Mã này sử dụng JPA để duy trì dữ liệu. Lớp này mở rộng giao diện Spring JPARepository và cho phép tạo mã tuỳ chỉnh. Trong mã, bạn đã thêm một phương thức tuỳ chỉnh findRandomQuote.
QuoteController.java
Để hiển thị điểm cuối cho dịch vụ, lớp QuoteController sẽ cung cấp chức năng này.
Tạo một tệp có tên là QuoteController.java tại src/main/java/com/example/springboot rồi sao chép nội dung sau vào
package com.example.springboot;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class QuoteController {
private final QuoteRepository quoteRepository;
public QuoteController(QuoteRepository quoteRepository) {
this.quoteRepository = quoteRepository;
}
@GetMapping("/random-quote")
public Quote randomQuote()
{
return quoteRepository.findRandomQuote();
}
@GetMapping("/quotes")
public ResponseEntity<List<Quote>> allQuotes()
{
try {
List<Quote> quotes = new ArrayList<Quote>();
quoteRepository.findAll().forEach(quotes::add);
if (quotes.size()==0 || quotes.isEmpty())
return new ResponseEntity<List<Quote>>(HttpStatus.NO_CONTENT);
return new ResponseEntity<List<Quote>>(quotes, HttpStatus.OK);
} catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<List<Quote>>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PostMapping("/quotes")
public ResponseEntity<Quote> createQuote(@RequestBody Quote quote) {
try {
Quote saved = quoteRepository.save(quote);
return new ResponseEntity<Quote>(saved, HttpStatus.CREATED);
} catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PutMapping("/quotes/{id}")
public ResponseEntity<Quote> updateQuote(@PathVariable("id") Integer id, @RequestBody Quote quote) {
try {
Optional<Quote> existingQuote = quoteRepository.findById(id);
if(existingQuote.isPresent()){
Quote updatedQuote = existingQuote.get();
updatedQuote.setAuthor(quote.getAuthor());
updatedQuote.setQuote(quote.getQuote());
return new ResponseEntity<Quote>(updatedQuote, HttpStatus.OK);
} else {
return new ResponseEntity<Quote>(HttpStatus.NOT_FOUND);
}
} catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/quotes/{id}")
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
Optional<Quote> quote = quoteRepository.findById(id);
if (quote.isPresent()) {
quoteRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
Thêm cấu hình cơ sở dữ liệu
application.yaml
Thêm cấu hình cho cơ sở dữ liệu phụ trợ mà dịch vụ truy cập. Chỉnh sửa (hoặc tạo nếu chưa có) tệp có tên là application.yaml trong src/main/resources và thêm cấu hình Spring được tham số hoá cho phần phụ trợ.
target: local
spring:
config:
activate:
on-profile: cloud-dev
datasource:
url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_NAME:quote_db}'
username: '${DB_USER:user}'
password: '${DB_PASS:password}'
jpa:
properties:
hibernate:
jdbc:
lob:
non_contextual_creation: true
dialect: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: update
Thêm Database Migration
Tạo thư mục db/migration trong src/main/resources
Tạo một tệp SQL: V1__create_quotes_table.sql
Dán nội dung sau vào tệp
V1__create_quotes_table.sql
CREATE TABLE quotes(
id INTEGER PRIMARY KEY,
quote VARCHAR(1024),
author VARCHAR(256)
);
INSERT INTO quotes (id,quote,author) VALUES (1,'Never, never, never give up','Winston Churchill');
INSERT INTO quotes (id,quote,author) VALUES (2,'While there''s life, there''s hope','Marcus Tullius Cicero');
INSERT INTO quotes (id,quote,author) VALUES (3,'Failure is success in progress','Anonymous');
INSERT INTO quotes (id,quote,author) VALUES (4,'Success demands singleness of purpose','Vincent Lombardi');
INSERT INTO quotes (id,quote,author) VALUES (5,'The shortest answer is doing','Lord Herbert');
Cấu hình Kubernetes
Việc bổ sung những nội dung sau vào tệp deployment.yaml cho phép ứng dụng kết nối với các phiên bản CloudSQL.
- TARGET – định cấu hình biến để cho biết môi trường mà ứng dụng được thực thi
- SPRING_PROFILES_ACTIVE – cho biết hồ sơ Spring đang hoạt động, hồ sơ này sẽ được định cấu hình thành
cloud-dev - DB_HOST – IP riêng cho cơ sở dữ liệu, được ghi chú khi thực thể cơ sở dữ liệu được tạo hoặc bằng cách nhấp vào
SQLtrong Trình đơn điều hướng của Google Cloud Console – vui lòng thay đổi giá trị! - DB_USER và DB_PASS – được thiết lập trong cấu hình phiên bản CloudSQL, được lưu trữ dưới dạng một Secret trong GCP
Cập nhật deployment.yaml bằng nội dung bên dưới.
deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-app
labels:
app: demo-app
spec:
ports:
- port: 8080
protocol: TCP
clusterIP: None
selector:
app: demo-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
labels:
app: demo-app
spec:
replicas: 1
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: demo-app
env:
- name: PORT
value: "8080"
- name: TARGET
value: "Local Dev - CloudSQL Database - K8s Cluster"
- name: SPRING_PROFILES_ACTIVE
value: cloud-dev
- name: DB_HOST
value: ${DB_INSTANCE_IP}
- name: DB_PORT
value: "5432"
- name: DB_USER
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: password
- name: DB_NAME
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: database
Thay thế giá trị DB_HOST bằng địa chỉ của Cơ sở dữ liệu bằng cách chạy các lệnh bên dưới trong thiết bị đầu cuối:
export DB_INSTANCE_IP=$(gcloud sql instances describe quote-db-instance \
--format=json | jq \
--raw-output ".ipAddresses[].ipAddress")
envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml
Mở deployment.yaml và xác minh rằng giá trị DB_HOST đã được cập nhật bằng IP của thực thể.

Triển khai và xác thực ứng dụng
- Trong ngăn ở cuối Cloud Shell Editor, hãy chọn Cloud Code rồi chọn Debug on Kubernetes (Gỡ lỗi trên Kubernetes) ở đầu màn hình.

- Khi quá trình tạo và kiểm thử hoàn tất, thẻ Output (Đầu ra) sẽ có nội dung:
Resource deployment/demo-app status completed successfullyvà một URL được liệt kê: "Forwarded URL from service demo-app: http://localhost:8080" ("URL được chuyển tiếp từ ứng dụng minh hoạ dịch vụ: http://localhost:8080"). Xin lưu ý rằng đôi khi cổng có thể khác, chẳng hạn như 8081. Nếu có, hãy đặt giá trị thích hợp. Đặt giá trị của URL trong thiết bị đầu cuối
export URL=localhost:8080
- Xem câu nói ngẫu nhiên
Trong Terminal, hãy chạy lệnh bên dưới nhiều lần đối với điểm cuối trích dẫn ngẫu nhiên. Quan sát lệnh gọi lặp lại trả về các câu trích dẫn khác nhau
curl $URL/random-quote | jq
- Thêm câu trích dẫn
Tạo một câu trích dẫn mới, với id=6 bằng cách sử dụng lệnh được liệt kê bên dưới và quan sát yêu cầu được phản hồi
curl -H 'Content-Type: application/json' -d '{"id":"6","author":"Henry David Thoreau","quote":"Go confidently in the direction of your dreams! Live the life you have imagined"}' -X POST $URL/quotes
- Xoá câu trích dẫn
Giờ đây, hãy xoá câu trích dẫn mà bạn vừa thêm bằng phương thức xoá và quan sát mã phản hồi HTTP/1.1 204.
curl -v -X DELETE $URL/quotes/6
- Lỗi Máy chủ
Gặp phải trạng thái lỗi bằng cách chạy lại yêu cầu cuối cùng sau khi mục nhập đã bị xoá
curl -v -X DELETE $URL/quotes/6
Lưu ý rằng phản hồi trả về một HTTP:500 Internal Server Error.
Gỡ lỗi ứng dụng
Ở phần trước, bạn đã thấy trạng thái lỗi trong ứng dụng khi cố gắng xoá một mục không có trong cơ sở dữ liệu. Trong phần này, bạn sẽ đặt một điểm ngắt để xác định vị trí của vấn đề. Lỗi xảy ra trong thao tác XOÁ, vì vậy, bạn sẽ làm việc với lớp QuoteController.
- Mở
src/main/java/com/example/springboot/QuoteController.java - Tìm phương thức
deleteQuote() - Tìm dòng:
Optional<Quote> quote = quoteRepository.findById(id); - Đặt một điểm ngắt trên dòng đó bằng cách nhấp vào khoảng trống ở bên trái số dòng.
- Một chỉ báo màu đỏ sẽ xuất hiện cho biết điểm ngắt đã được đặt
- Chạy lại lệnh
delete
curl -v -X DELETE $URL/quotes/6
- Chuyển về chế độ xem gỡ lỗi bằng cách nhấp vào biểu tượng trong cột bên trái
- Quan sát dòng gỡ lỗi đã dừng trong lớp QuoteController.
- Trong trình gỡ lỗi, hãy nhấp vào biểu tượng
step over
- Lưu ý rằng mã này trả về Lỗi máy chủ nội bộ HTTP 500 cho ứng dụng, điều này không lý tưởng.
Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > DELETE /quotes/6 HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.74.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 500 < Content-Length: 0 < Date: < * Connection #0 to host 127.0.0.1 left intact
Cập nhật mã
Mã này không chính xác và khối else cần được tái cấu trúc để gửi lại mã trạng thái HTTP 404 không tìm thấy.
Sửa lỗi.
- Khi phiên gỡ lỗi vẫn đang chạy, hãy hoàn tất yêu cầu bằng cách nhấn nút "tiếp tục" trong bảng điều khiển gỡ lỗi.
- Tiếp theo, hãy thay đổi khối
elsethành mã sau:
else {
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
Phương thức này sẽ có dạng như sau
@DeleteMapping("/quotes/{id}")
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
Optional<Quote> quote = quoteRepository.findById(id);
if (quote.isPresent()) {
quoteRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
}
- Chạy lại lệnh xoá
curl -v -X DELETE $URL/quotes/6
- Bước qua trình gỡ lỗi và quan sát thông báo HTTP 404 Not Found (Không tìm thấy) được trả về cho phương thức gọi.
Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > DELETE /quotes/6 HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.74.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 404 < Content-Length: 0 < Date: < * Connection #0 to host 127.0.0.1 left intact
- Dừng phiên gỡ lỗi bằng cách nhấp vào hình vuông màu đỏ trên thanh công cụ gỡ lỗi


6. Xin chúc mừng
Xin chúc mừng! Trong phòng thí nghiệm này, bạn đã tạo một ứng dụng Java mới từ đầu và định cấu hình ứng dụng đó để hoạt động hiệu quả với các vùng chứa. Sau đó, bạn đã triển khai và gỡ lỗi ứng dụng của mình cho một cụm GKE từ xa theo quy trình phát triển tương tự trong các ngăn xếp ứng dụng truyền thống.
Kiến thức bạn học được
- Phát triển InnerLoop bằng Cloud Workstations
- Tạo một ứng dụng khởi động Java mới
- Tìm hiểu quy trình phát triển
- Phát triển một dịch vụ REST CRUD đơn giản
- Gỡ lỗi ứng dụng trên cụm GKE
- Kết nối ứng dụng với cơ sở dữ liệu CloudSQL
