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:
- Thiết lập và yêu cầu
- 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
- Dọn dẹp
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 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 PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
Lấy 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 Cloud SQL. 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 10 phút. Bạn có thể tiếp tục thực hiện một vài bước tiếp theo trong khi quá trình thiết lập đang diễn ra.
./setup.sh
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
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=11 -d packageName=com.example.springboot -o sample-app.zip
- Giải nén ứng dụng
unzip sample-app.zip -d sample-app
- Chuyển sang thư mục sample-app rồi mở thư mục này trong không gian làm việc Cloud Shell IDE
cd sample-app && cloudshell workspace .
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 đoạn mã sau vào sau dòng nội dung 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>
Chọn Always nếu được nhắc về thay đổi tệp bản dựng.

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. Ở 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 hiện lệnh bên dưới để 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, cụ thể là skaffold.yaml và deployment.yaml
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 tính năng đồng bộ hoá nhanh
Để 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 cấu hình "sync" mà bạn đang định cấu hình trong cấu hình skaffold sẽ tận dụng Cấu hình "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/java:debug
sync:
auto: true
Thêm tuyến đường mặc định
Tạo một tệp có tên HelloController.java tại /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.
Triển khai lên Kubernetes
- Trong ngăn ở cuối Cloud Shell Editor, hãy chọn Cloud Code 

- Trong bảng điều khiển xuất hiện ở trên cùng, hãy chọn Gỡ lỗi trên Kubernetes. Nếu được nhắc, hãy chọn Có để sử dụng ngữ cảnh Kubernetes hiện tại.

- Lần đầu tiên bạn chạy lệnh này, một lời nhắc sẽ xuất hiện ở đầu màn hình để hỏi xem bạn có muốn sử dụng bối cảnh Kubernetes hiện tại hay không. Hãy chọn "Yes" (Có) để chấp nhận và sử dụng bối cảnh hiện tại.

- Tiếp theo, một lời nhắc sẽ xuất hiện để hỏi bạn muốn dùng sổ đăng ký vùng chứa nào. Nhấn Enter để chấp nhận giá trị mặc định được cung cấp

- 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

- 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
- 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 cửa sổ Cloud Code, hãy di chuột lên URL trong đầu ra (http://localhost:8080), sau đó chọn Open Web Preview (Mở bản xem trước trên web) trong chú thích xuất hiện.
Phản hồi sẽ là:
Hello from your local environment!
Khai thác điểm ngắt
- Mở ứng dụng HelloController.java 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 và cho phép bạn điều tra trạng thái và biến 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, hãy thay đổi giá trị thành một giá trị khác, chẳng hạn như "Cloud"
- 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.
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

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ụ còn lại 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>
Mã hoá dịch vụ còn lại
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 mã bên dưới. 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 javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.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 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 QuoteController.java tại src/main/java/com/example/springboot và sao chép nội dung sau
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) {
try {
quoteRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
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 một thư mục tại src/main/resources/db/migration/
Tạo 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
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
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) - Xem câu nói ngẫu nhiên
Trong cửa sổ dòng lệnh cloudshell, hãy chạy lệnh bên dưới nhiều lần đối với điểm cuối random-quote. 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 -v 127.0.0.1:8080/random-quote
- 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 -v -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 127.0.0.1:8080/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 127.0.0.1:8080/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 127.0.0.1:8080/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 xoá một mục khỏi cơ sở dữ liệu:
quoteRepository.deleteById(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 127.0.0.1:8080/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
và quan sát thấy một ngoại lệ được đưa ra - Lưu ý rằng
RuntimeException was caught.này rất chung chung. Điều 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 ngoại lệ cần được tái cấu trúc để bắt ngoại lệ EmptyResultDataAccessException và 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 thêm khối sau vào mã:
} catch (EmptyResultDataAccessException e){
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
Phương thức này sẽ có dạng như sau
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
try {
quoteRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch(EmptyResultDataAccessException e){
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
- Chạy lại lệnh xoá
curl -v -X DELETE 127.0.0.1:8080/quotes/6
- Bước qua trình gỡ lỗi và quan sát
EmptyResultDataAccessExceptionđang được bắt và HTTP 404 Not Found được trả về cho người 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. Dọn dẹp
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.
Cách dọn dẹp sau khi hoàn thành bài thực hành:
- Xoá các tệp được dùng trong phòng thí nghiệm
cd ~ && rm -rf container-developer-workshop
- Xoá dự án để loại bỏ tất cả cơ sở hạ tầng và tài nguyên liên quan