Phát triển bằng Cloud Workstation và Cloud Code

1. Tổng quan

Phòng thí nghiệm này trình bày các tính năng và khả năng được thiết kế để hợp lý hoá quy trình phát triển dành cho các kỹ sư phần mềm được giao nhiệm vụ phát triển các ứng dụng Java trong môi trường vùng chứa. Thông thường, quá trình phát triển vùng chứa yêu cầu người dùng hiểu 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, các nhà phát triển thường phải ngắt luồng của mình, rời 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 trong vùng chứa mà không cần rời khỏi IDE của họ.

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 InsideLoop với Cloud Workstation
  • Tạo ứng dụng khởi động Java mới
  • Tìm hiểu về quá trình phát triển
  • Phát triển một dịch vụ nghỉ ngơi 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

58a4cdd3ed7a123a.pngS

2. Thiết lập và yêu cầu

Thiết lập môi trường theo tiến độ riêng

  1. Đăng nhập vào Google Cloud Console rồi tạo dự án mới hoặc sử dụng lại dự án hiện có. Nếu chưa có tài khoản Gmail hoặc Google Workspace, bạn phải tạo một tài khoản.

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

Khởi động Cloudshell Editor

Phòng thí nghiệm này được thiết kế và thử nghiệm để sử dụng với Google Cloud Shell Editor. Để truy cập vào trình chỉnh sửa,

  1. truy cập vào dự án google của bạn 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 vỏ đám mây

8560cc8d45e8c112.pngS

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

9e504cb98a6a8005.pngS

  1. Trình chỉnh sửa sẽ mở ra cùng 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. Ngăn thiết bị đầu cuối cũng sẽ xuất hiện ở cuối màn hình
  3. Nếu cửa sổ dòng lệnh KHÔNG mở, hãy sử dụng tổ hợp phím "ctrl+" để mở 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. Hãy lưu các biến này dưới dạng biến PROJECT_IDREGION.

export REGION=us-central1
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

Sao chép mã nguồn

Mã nguồn cho phòng thí nghiệm này nằm tại Container-developer-workshop trong GoogleCloudPlatform trên GitHub. Hãy sao chép tệp đó bằng lệnh bên dưới rồi thay đổi vào 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 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 dưới đây 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. Hãy chờ tập lệnh hoàn tất trước khi chuyển sang phần tiếp theo.

./setup_with_cw.sh &

Cụm Cloud Workstation

Mở Cloud Workstations trong Cloud Console. Chờ cụm ở trạng thái READY.

305e1a3d63ac7ff6.pngS

Tạo cấu hình máy trạm

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 cửa sổ dòng lệnh để 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. Sẽ mất 2 phút để chuyển sang trạng thái SẴN SÀNG.

7a6af5aa2807a5f2.pngS

Mở Cloud Workstations trong Console rồi tạo phiên bản mới.

a53adeeac81a78c8.png

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

f21c216997746097.png

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

66a9fc8b20543e32.pngS

Khởi chạy máy trạm

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

c91bb69b61ec8635.png

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ỉ. 1b8923e2943f9bc4.pngS

fcf9405b6957b7d7.png

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

36a84c0e2e3b85b.png.

Nhấp vào "Allow cookies".

2259694328628fba.png.

Sau khi máy trạm chạy, bạn sẽ thấy IDE Mã OSS xuất hiện. Nhấp vào "Đánh dấu là đã xong" trên trang Bắt đầu một, IDE máy trạm

94874fba9b74cc22.pngs

3. Tạo ứng dụng khởi đầu 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 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.

c31d48f2e4938c38.png

Sao chép ứng dụng mẫu

  1. Tạo ứng dụng khởi đầu
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 để bạn có thể sao chép và dán vào máy trạm.

58149777e5cc350a.pngS

  1. Giải nén ứng dụng
unzip sample-app.zip -d sample-app
  1. Mở "ứng dụng mẫu" thư mục
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

Thêm spring-boot-devtools và Kim chỉ nam

Để bật Công cụ cho nhà phát triển khởi động Spring, hãy tìm và mở tệp pom.xml qua trình khám phá trong trình chỉnh sửa của bạn. 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>

  1. Thêm spring-boot-devtools trong pom.xml

Mở pom.xml trong thư mục gốc của dự án. Thêm cấu hình sau đây sau mục nhập 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ụ chứa Java nguồn mở của Google, cho phép các nhà phát triển Java xây dựng vùng chứa bằng các công cụ Java mà họ biết. Jib là trình tạo hình ảnh vùng chứa nhanh và đơn giản, xử lý tất cả các bước đóng gói ứng dụng của bạn vào hình ảnh vùng chứa. Cấu hình này không yêu cầu bạn phải viết Dockerfile hoặc cài đặt docker, đồng thời được tích hợp trực tiếp vào Maven và Gradle.

Di chuyển xuống tệp pom.xml rồi 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. Ở bước này, bạn sẽ khởi chạy Skaffold sẽ tự động tạo các tệp YAML của Kubernetes cơ bản. Quá trình này sẽ 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ư Dockerfile, sau đó tạo một tệp kê khai dịch vụ và triển khai cho mỗi thư mục.

Thực thi lệnh dưới đây trong Cửa sổ dòng lệnh để bắt đầu quá trình.

d869e0cd38e983d7.png

  1. Thực thi lệnh sau trong dòng lệnh
skaffold init --generate-manifests
  1. Khi được nhắc:
  • Sử dụng các mũi tên để di chuyển con trỏ đến Jib Maven Plugin
  • Nhấn phím cách để chọn tuỳ chọn.
  • 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 skaffold.yamldeployment.yaml

Đầu ra Skaffold:

b33cc1e0c2077ab8.png

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 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à pom-xml-image
  • Nhấp chuột phải rồi chọn Thay đổi tất cả lần xuất hiện
  • Nhập tên mới là 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à pom-xml-image
  • Nhấp chuột phải rồi chọn Thay đổi tất cả lần xuất hiện
  • Nhập tên mới là demo-app

Bật chế độ tự động đồng bộ hoá

Để tối ưu hoá trải nghiệm tải lại nóng, bạn cần dùng tính năng Đồng bộ hoá do Jib cung cấp. Ở 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.

Lưu ý rằng thao tác "đồng bộ hoá" hồ sơ mà bạn đang định cấu hình trong cấu hình Skaffold tận dụng tính năng "đồng bộ hoá" Spring Hồ sơ 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 thông số kỹ thuật sau. Đừng thay đổ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 là HelloController.java trong thư mục /src/main/java/com/example/springboot/.

a624f5dd0c477c09.png

Dán những nội dung sau vào tệp để tạo tuyến 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 về quá trình phát triển

Trong phần này, bạn sẽ tìm hiểu một số bước sử dụng trình bổ trợ Mã đám mây để tìm hiểu các quy trình cơ bản, cũng như để xác thực cấu hình và chế độ thiết lập ứng dụng khởi đầu.

Cloud Code tích hợp với Skaffold để đơn giản hoá quy trình phát triển của bạn. Khi bạn triển khai GKE ở các bước sau, Cloud Code và Skaffold sẽ tự động tạo hình ảnh vùng chứa của bạn, đẩy hình ảnh vùng chứa đó vào Container Registry, sau đó triển khai ứng dụng của bạn lên GKE. Quá trình này xảy ra trong nền, trừu tượng chi tiết khỏi quy trình của nhà phát triển. Cloud Code cũng giúp cải thiện quá trình phát triể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 hoạt động 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 Mã đám mây rồi chọn "Đăng nhập vào Google Cloud":

1769afd39be372ff.pngS

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

923bb1c8f63160f9.pngS

Kiểm tra kết quả trong dòng lệnh và mở đường liên kết:

517fdd579c34aa21.pngS

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

db99b345f7a8e72c.png

Chọn "Cho phép":

a5376553c430ac84.png

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

6719421277b92eac.png.

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

e9847cfe3fa8a2ce.png

Thêm cụm Kubernetes

  1. Thêm một cụm

62a3b97bdbb427e5.pngs

  1. Chọn Google Kubernetes Engine:

9577de423568bbaa.pngS

  1. Chọn dự án.

c5202fcbeebcd41c.png

  1. Chọn " trích dẫn-cụm" được tạo ở bước thiết lập ban đầu.

366cfd8bc27cd3ed.png.

9d68532c9bc4a89b.png.

Đặt mã dự án hiện tại bằng gcloud cli

Sao chép mã dự án của phòng thí nghiệm này từ trang qwiklabs.

fcff2d10007ec5bc.png

Chạy lệnh gcloud cli để đặt mã dự án. Hãy 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:

f1c03d01b7ac112c.png

Gỡ lỗi trên Kubernetes

  1. Trong ngăn bên trái ở dưới cùng, chọn Cloud Code.

60b8e4e95868b561.pngS

  1. Trong bảng điều khiển xuất hiện ở phần PHIÊN PHÁT TRIỂN, hãy chọn Gỡ lỗi trên Kubernetes.

Di chuyển xuống nếu lựa chọn này không xuất hiện.

7d30833d96632ca0.pngs

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

a024a69b64de7e9e.png

  1. Chọn " trích dẫn-cụm" được tạo trong quá trình thiết lập ban đầu.

faebabf372e3caf0.png

  1. Chọn Kho lưu trữ vùng chứa.

fabc6dce48bae1b4.png

  1. Chọn thẻ Output (Kết quả) trong ngăn phía dưới để xem tiến trình và thông báo
  2. Chọn "Kubernetes: Chạy/Gỡ lỗi – Chi tiết" trong trình đơn thả xuống ở bên phải của kênh để 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

86b44c59db58f8f3.pngS

Chờ ứng dụng được triển khai.

9f37706a752829fe.pngS

  1. Xem xét ứng dụng đã triển khai trên GKE trong Cloud Console.

6ad220e5d1980756.pngS

  1. Quay lại chế độ xem đơn giản bằng cách chọn "Kubernetes: Chạy/Gỡ lỗi" từ trình đơn thả xuống trên tab OUTPUT.
  2. Khi quá trình tạo và kiểm thử hoàn tất, thẻ Đầu ra sẽ cho biết: Resource deployment/demo-app status completed successfully, và một URL có trong danh sách: "Được chuyển tiếp URL từ ứng dụng minh hoạ dịch vụ: http://localhost:8080"
  3. Trong cửa sổ dòng lệnh Mã đám mây, hãy di chuột qua URL trong dữ liệu đầu ra (http://localhost:8080), sau đó chọn Theo dõi đường liên kết của công cụ hiện ra.

28c5539880194a8e.png.

Thẻ mới sẽ mở ra và bạn sẽ thấy kết quả bên dưới:

d67253ca16238f49.png

Khai thác cá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 đ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 để ghi chú điểm ngắt đã được đặt

5027dc6da2618a39.pngS

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

71acfb426623cec2.pngS

  1. Nhấp vào mục biến cho đến khi bạn thấy mục "Mục tiêu" biến.
  2. Quan sát giá trị hiện tại dưới dạng "cục bộ"

a1160d2ed2bb5c82.png

  1. Nhấp đúp vào tên biến "mục tiêu" và trong cửa sổ bật lên,

thay đổi giá trị thành "Cloud Workstations"

e597a556a5c53f32.png

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

ec17086191770d0d.png

  1. Xem lại phản hồi trong trình duyệt để hiện giá trị được cập nhật mà bạn vừa nhập.

6698a9db9e729925.pngS

  1. 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. Thao tác này sẽ ngăn mã của bạn ngừng thực thi tại dòng này trong quá trình bạn tiếp tục học trong phòng thí nghiệm này.

Tải lại nhanh

  1. Thay đổi câu lệnh để trả về một giá trị khác, chẳng hạn như "Hello from %s Code" (Xin chào từ %s Code)
  2. Tệp được tự động lưu và đồng bộ hoá vào các vùng chứa từ xa trong GKE
  3. Làm mới trình duyệt của bạn để xem kết quả được cập 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

a541f928ec8f430e.png c2752bb28d82af86.png

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

984eb2fa34867d70.pngs

5. Phát triển một dịch vụ nghỉ ngơi CRUD đơn giản

Tại thời điểm này, ứng dụng của bạn đã được định cấu hình đầy đủ để phát triển theo 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 nội dung đã học bằng cách thêm điểm cuối của dịch vụ Kiến trúc chuyển trạng thái đại diện (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 phần phụ thuộc

Mã xử lý ứng dụng dùng cơ sở dữ liệu để lưu 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 đoạn mã sau vào tệp pom.xl

  1. Mở tệp pom.xml rồi thêm nội dung sau vào mục 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 vào đoạn mã bên dưới. Thao tác này xác định mô hình Thực thể cho đối tượng Trích dẫn đượ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 vào đoạn mã sau

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 của 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 vào các 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) {
        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 không có) tệp có tên là tệp application.yaml trong src/main/resources và thêm một cấu hình Spring có tham số 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 quy trình di chuyển cơ sở dữ liệu

Tạo thư mục db/migration trong src/main/resources

Tạo tệp SQL: V1__create_quotes_table.sql

Dán các 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 Kerberos

Những nội dung bổ sung sau đây cho tệp deployment.yaml cho phép ứng dụng kết nối với các thực thể CloudSQL.

  • TARGET – định cấu hình biến để cho biết môi trường nơi ứng dụng được thực thi
  • SPbạn_Hồ sơ_HOẠT ĐỘNG – hiển thị 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 tư của 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 – như được đặt trong cấu hình phiên bản CloudSQL, được lưu trữ dưới dạng Khoá bí mật trong GCP

Hãy cập nhật triển khai.yaml của bạn theo 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ơ sở dữ liệu của bạn bằng cách chạy các lệnh bên dưới trong cửa sổ dòng lệnh:

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ở triển khai.yaml và xác minh rằng giá trị DB_HOST đã được cập nhật bằng IP của thực thể.

fd63c0aede14beba.png

Triển khai và xác thực ứng dụng

  1. Trong ngăn ở cuối Cloud Shell Editor, chọn Cloud Code rồi chọn Debug on Kubernetes ở đầu màn hình.

33a5cf41aae91adb.png.

  1. Khi quá trình tạo và kiểm thử hoàn tất, thẻ Đầu ra sẽ cho biết: Resource deployment/demo-app status completed successfully và một URL có trong danh sách: "Được chuyển tiếp URL 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 như 8081. Nếu có, hãy đặt giá trị phù hợp. Đặt giá trị của URL trên thiết bị đầu cuối
export URL=localhost:8080
  1. Xem trích dẫn ngẫu nhiên

Trong dòng lệnh, hãy chạy lệnh dưới đây nhiều lần đối với điểm cuối trích dẫn ngẫu nhiên. Quan sát cuộc gọi lặp lại và trả về các dấu ngoặc kép khác nhau

curl $URL/random-quote | jq
  1. Thêm báo giá

Tạo một bản báo giá 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 lặp lạ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
  1. Xoá bản báo giá

Bây giờ, hãy xoá bản báo giá 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
  1. Lỗi máy chủ

Trải nghiệm trạng thái lỗi bằng cách chạy lại yêu cầu gần đây nhất 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ề HTTP:500 Internal Server Error.

Gỡ lỗi ứng dụng

Ở phần trước, bạn đã phát hiện 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ấn đề. Đã xảy ra lỗi trong thao tác DELETE, 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 đường thẳng này: Optional<Quote> quote = quoteRepository.findById(id);
  4. Đặ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 $URL/quotes/6
  1. Chuyển về chế độ xem gỡ lỗi bằng cách nhấp vào biểu tượng ở cột bên trái
  2. Quan sát dòng gỡ lỗi bị 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
  4. Lưu ý rằng mã sẽ trả về lỗi HTTP 500 lỗi máy chủ nội bộ cho máy khách. Đây là lỗi 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 đoạn mã

Mã này không chính xác và bạn cần tái cấu trúc khối else để 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 "tiếp tục" trong bảng điều khiển gỡ lỗi.
  2. Tiếp theo, hãy thay đổi khối else thành mã:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

Phương thức 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);
        }
    }
  1. Chạy lại lệnh xoá
curl -v -X DELETE $URL/quotes/6
  1. Thực hiện các bước trong trình gỡ lỗi và quan sát lỗi HTTP 404 Not 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
  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

12bc3c82f63dcd8a.png.

6f19c0f855832407.pngS

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 này hoạt động hiệu quả với vùng chứa. Sau đó, bạn đã triển khai và gỡ lỗi ứng dụng cho một cụm GKE từ xa theo cùng một quy trình dành cho nhà phát triển 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 InsideLoop với Cloud Workstation
  • Tạo ứng dụng khởi động Java mới
  • Tìm hiểu về quá trình phát triển
  • Phát triển một Dịch vụ CRUD REST đơ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