Phát triển innerLoop bằng Java – SpringBoot

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

  1. Đă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.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.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ự 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.
  1. 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,

  1. truy cập vào dự án của bạn trên Google tại https://console.cloud.google.com.
  2. Ở 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

8560cc8d45e8c112.png

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

9e504cb98a6a8005.png

  1. 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
  2. Một ngăn cửa sổ dòng lệnh cũng phải có ở cuối màn hình
  3. 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_IDREGION.

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

  1. 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
  1. Giải nén ứng dụng
unzip sample-app.zip -d sample-app
  1. 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>

  1. 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>
  1. 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.

447a90338f51931f.png

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.

  1. Thực thi lệnh sau trong dòng lệnh
skaffold init --generate-manifests
  1. 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
  1. Nhập 8080 cho cổng
  2. 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.yamldeployment.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.

  1. 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
  1. 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.

  1. 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

  1. Trong ngăn ở cuối Cloud Shell Editor, hãy chọn Cloud Code 

fdc797a769040839.png

  1. 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.

cfce0d11ef307087.png

  1. 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.

817ee33b5b412ff8.png

  1. 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

eb4469aed97a25f6.png

  1. Chọn thẻ Đầu ra trong ngăn dưới cùng để xem tiến trình và thông báo

f95b620569ba96c5.png

  1. 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

94acdcdda6d2108.png

  1. 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
  2. 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 successfully và 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)
  3. 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

  1. Mở ứng dụng HelloController.java tại /src/main/java/com/example/springboot/HelloController.java
  2. 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);
  3. 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
  4. 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
  5. Nhấp vào phần biến cho đến khi bạn tìm thấy biến "Target" (Mục tiêu).
  6. Quan sát giá trị hiện tại là "local"
  7. 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"
  8. Nhấp vào nút Tiếp tục trong bảng điều khiển gỡ lỗi
  9. 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

  1. 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"
  2. 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
  3. Hãy làm mới trình duyệt để xem kết quả mới nhất.
  4. 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 a13d42d726213e6c.png

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

  1. Mở tệp pom.xml rồ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 SQL trong 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

  1. 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.
  2. 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 successfully và 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)
  3. 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
  1. 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
  1. 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
  1. 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.

  1. Mở src.main.java.com.example.springboot.QuoteController.java
  2. Tìm phương thức deleteQuote()
  3. Tìm dòng xoá một mục khỏi cơ sở dữ liệu: quoteRepository.deleteById(id);
  4. Đặ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.
  5. Một chỉ báo màu đỏ sẽ xuất hiện cho biết điểm ngắt đã được đặt
  6. Chạy lại lệnh delete
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. 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
  2. Quan sát dòng gỡ lỗi đã dừng trong lớp QuoteController.
  3. Trong trình gỡ lỗi, hãy nhấp vào biểu tượng step over b814d39b2e5f3d9e.png và quan sát thấy một ngoại lệ được đưa ra
  4. 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.

  1. 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.
  2. 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);
        }
    }
  1. Chạy lại lệnh xoá
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. 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
  1. 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 a13d42d726213e6c.png

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:

  1. Xoá các tệp được dùng trong phòng thí nghiệm
cd ~ && rm -rf container-developer-workshop
  1. Xoá dự án để loại bỏ tất cả cơ sở hạ tầng và tài nguyên liên quan