使用 Cloud Workstations 和 Cloud Code 進行開發

1. 總覽

本研究室介紹了專為在容器化環境中開發 Java 應用程式的軟體工程師所設計的功能和功能,協助他們簡化開發工作流程。一般的容器開發作業會要求使用者瞭解容器和容器建構程序的詳細資料。此外,開發人員通常必須中斷流程,從 IDE 中移出 IDE,才能在遠端環境中對應用程式進行測試及偵錯。有了本教學課程中提及的工具和技術,開發人員不必離開 IDE,就能有效地使用容器化應用程式。

學習目標

在本研究室中,您將瞭解在 GCP 中使用容器進行開發的方法,包括:

  • 使用 Cloud Workstations 進行 InnerLoop 開發作業
  • 建立新的 Java 範例應用程式
  • 逐步完成開發程序
  • 開發簡易的 CRUD 靜態服務
  • GKE 叢集中的偵錯應用程式
  • 將應用程式連線至 CloudSQL 資料庫

58a4cdd3ed7a123a.png

2. 設定和需求

自修環境設定

  1. 登入 Google Cloud 控制台,建立新專案或重複使用現有專案。如果您還沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • 「專案名稱」是這項專案參與者的顯示名稱。這是 Google API 未使用的字元字串。您可以隨時更新。
  • 所有 Google Cloud 專案的專案 ID 均不得重複,而且設定後即無法變更。Cloud 控制台會自動產生一個不重複的字串。但通常是在乎它何在在大部分的程式碼研究室中,您必須參照專案 ID (通常為 PROJECT_ID)。如果您對產生的 ID 不滿意,可以隨機產生一個 ID。此外,您也可以自行嘗試,看看系統是否提供該付款方式。在完成這個步驟後就無法變更,而且在專案期間仍會保持有效。
  • 資訊中的第三個值是專案編號,部分 API 會使用這個編號。如要進一步瞭解這三個值,請參閱說明文件
  1. 接下來,您需要在 Cloud 控制台中啟用計費功能,才能使用 Cloud 資源/API。執行這個程式碼研究室並不會產生任何費用,如果有的話。如要關閉資源,以免系統產生本教學課程結束後產生的費用,您可以刪除自己建立的資源,或刪除整個專案。Google Cloud 的新使用者符合 $300 美元免費試用計畫的資格。

啟動 Cloud Shell 編輯器

本研究室專為與 Google Cloud Shell 編輯器搭配使用而設計和測試。如要存取編輯器

  1. 前往 https://console.cloud.google.com 存取您的 Google 專案。
  2. 按一下右上角的「Cloud Shell 編輯器」圖示

8560cc8d45e8c112.png

  1. 視窗底部隨即會開啟新的窗格
  2. 點選「開啟編輯器」按鈕

9e504cb98a6a8005.png

  1. 編輯器隨即開啟,右側會顯示多層檢視,中央區則顯示編輯者
  2. 畫面底部應會顯示終端機窗格
  3. 如果終端機「未」開啟,使用 `ctrl+` 的按鍵組合開啟新的終端機視窗

設定 gcloud

在 Cloud Shell 中設定專案 ID 和您要部署應用程式的區域。將其儲存為 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)')

複製原始碼

本研究室的原始碼位於 GitHub 的 GoogleCloudPlatform 中的 container-developer-workshop。請使用下列指令複製資料夾,然後變更至目錄中。

git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot

佈建本研究室中使用的基礎架構

在本研究室中,您會將程式碼部署至 GKE,並存取 CloudSQL 資料庫中儲存的資料。下列設定指令碼可協助您完成這個基礎架構的設定。佈建程序需要 25 分鐘才能完成。請等待指令碼完成,再前往下一節。

./setup_with_cw.sh &

Cloud Workstations 叢集

在 Cloud 控制台中開啟 Cloud Workstations。等待叢集處於 READY 狀態。

305e1a3d63ac7ff6.png

建立工作站設定

如果 Cloud Shell 工作階段已中斷連線,請按一下 [重新連線]然後執行 gcloud cli 指令設定專案 ID。在執行指令前,請先將下方的範例專案 ID 替換為 qwiklabs 專案 ID。

gcloud config set project qwiklabs-gcp-project-id

在終端機中執行以下指令碼,以便建立 Cloud Workstations 設定。

cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh

在「設定」部分下方確認結果。系統需要 2 分鐘的時間才能轉換為「就緒」狀態。

7a6af5aa2807a5f2.png

開啟控制台中的 Cloud Workstations 並建立新的執行個體。

a53adeeac81a78c8.png

將名稱變更為 my-workstation,並選取現有設定:codeoss-java

f21c216997746097.png

在「Workstations」區段下方驗證結果。

66a9fc8b20543e32.png

啟動工作站

啟動及啟動工作站。

c91bb69b61ec8635.png

按一下網址列中的圖示,允許第三方 Cookie。1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

按一下「網站無法正常運作嗎?」。

36a84c0e2e3b85b.png

按一下 [允許 Cookie]。

2259694328628fba.png

工作站啟動後,您會看到 Code OSS IDE。按一下「標示為完成」選用於「入門指南」 其中一個工作站 IDE

94874fba9b74cc22.png

3. 建立新的 Java 範例應用程式

在本節中,您將使用 Spring.io 提供的範例應用程式,從頭開始建立新的 Java Spring Boot 應用程式。開啟新的終端機。

c31d48f2e4938c38.png

複製範例應用程式

  1. 建立範例應用程式
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

若您看到這則訊息,請按一下「允許」按鈕,以便複製內容到工作站。

58149777e5cc350a.png

  1. 將應用程式解壓縮
unzip sample-app.zip -d sample-app
  1. 開啟「sample-app」資料夾
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

新增 Spring-boot-devtools 和吉布

如要啟用 Spring Boot DevTools,請在編輯器中的 Explorer 找出並開啟 pom.xml。接著,將下列程式碼貼在 <description>Demo project for Spring Boot</description> 說明行的後方

  1. 在 pom.xml 中新增 Spring-boot-devtools

開啟專案根目錄中的 pom.xml。在 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. 在 pom.xml 中啟用 jib-maven-plugin

Jib 是 Google 提供的開放原始碼 Java 容器化工具,可讓 Java 開發人員使用熟悉的 Java 工具建構容器。Jib 是一款快速簡單的容器映像檔建構工具,可處理將應用程式封裝至容器映像檔的所有步驟。這項服務已直接整合至 Maven 和 Gradle,因此您無須編寫 Dockerfile 或安裝 docker。

pom.xml 檔案中向下捲動,並更新 Build 區段,加入 Jib 外掛程式。完成之後,「建構」部分應如下所示。

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>

產生資訊清單

Skaffold 提供整合式工具可簡化容器開發程序。在這個步驟中,您將初始化 Skaffold,並自動建立基礎 Kubernetes YAML 檔案。這項程序會嘗試找出含有容器映像檔定義的目錄 (例如 Dockerfile),然後為每個目錄建立部署和服務資訊清單。

在終端機中執行下列指令來開始這項程序。

d869e0cd38e983d7.png

  1. 在終端機中執行下列指令
skaffold init --generate-manifests
  1. 出現提示時:
  • 使用箭頭將遊標移到「Jib Maven Plugin
  • 按下空白鍵可選取選項。
  • 按下 Enter 鍵即可繼續操作
  1. 輸入通訊埠 8080
  2. 輸入 y 來儲存設定

工作區「skaffold.yaml」和「deployment.yaml」已新增兩個檔案

Skaffold 輸出:

b33cc1e0c2077ab8.png

更新應用程式名稱

設定中的預設值與您的應用程式名稱不符。更新檔案以參照您的應用程式名稱,而非使用預設值。

  1. 變更 Skaffold 設定中的項目
  • 開啟「skaffold.yaml
  • 選取目前設為「pom-xml-image」的映像檔名稱
  • 按一下滑鼠右鍵,選擇「變更所有出現項目」
  • 輸入新名稱,格式為「demo-app
  1. 變更 Kubernetes 設定的項目
  • 開啟 deployment.yaml 檔案
  • 選取目前設為「pom-xml-image」的映像檔名稱
  • 按一下滑鼠右鍵,選擇「變更所有出現項目」
  • 輸入新名稱,格式為「demo-app

啟用自動同步處理模式

如要執行最佳的熱重新載入體驗,請使用 Jib 提供的同步處理功能。在這個步驟中,您將設定 Skaffold 以在建構程序中使用該功能。

請注意,「同步」您在 Skaffold 設定中設定的設定檔會利用 Spring「sync」您在上一個步驟設定的設定檔,已啟用 Spring-dev-tools 的支援功能。

  1. 更新 Skaffold 設定

skaffold.yaml 檔案中,使用以下規格取代檔案的完整建構部分。請勿更改檔案的其他區段。

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

新增預設路徑

/src/main/java/com/example/springboot/ 資料夾中建立名為 HelloController.java 的檔案。

a624f5dd0c477c09.png

將下列內容貼入檔案,建立預設的 http 路徑。

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. 逐步完成開發程序

本節將逐步引導您使用 Cloud Code 外掛程式完成幾個步驟,藉此瞭解基本程序,並驗證範例應用程式的設定和設定。

Cloud Code 與 Skaffold 整合,簡化開發程序。按照下列步驟部署至 GKE 時,Cloud Code 和 Skaffold 會自動建構容器映像檔並推送至 Container Registry,然後將應用程式部署至 GKE。這會在背景執行,將詳細資料從開發人員流程中抽離出來。此外,Cloud Code 對容器型開發提供傳統的偵錯和熱同步功能,可改善您的開發程序。

登入 Google Cloud

按一下「Cloud Code」圖示,然後選取「登入 Google Cloud」:

1769afd39be372ff.png

按一下「繼續登入」。

923bb1c8f63160f9.png

查看終端機的輸出內容並開啟連結:

517fdd579c34aa21.png

使用 Qwiklabs 學生憑證登入。

db99b345f7a8e72c.png

選取「允許」:

a5376553c430ac84.png

複製驗證碼並返回「工作站」分頁。

6719421277b92eac.png

貼上驗證碼,然後按 Enter 鍵。

e9847cfe3fa8a2ce.png

新增 Kubernetes 叢集

  1. 新增叢集

62a3b97bdbb427e5.png

  1. 選取 Google Kubernetes Engine:

9577de423568bbaa.png

  1. 選取專案。

c5202fcbeebcd41c.png

  1. 選取「引用叢集」在初始設定中建立的 ID

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

使用 gcloud cli 設定目前的專案 ID

從 Qwiklabs 頁面複製這個研究室的專案 ID。

fcff2d10007ec5bc.png

執行 gcloud cli 指令即可設定專案 ID。在執行指令前替換範例專案 ID。

gcloud config set project qwiklabs-gcp-project-id

輸出內容範例:

f1c03d01b7ac112c.png

在 Kubernetes 上偵錯

  1. 在左側窗格中選取底部窗格中的「Cloud Code」。

60b8e4e95868b561.png

  1. 在「開發工作階段」下方的面板中,選取 [在 Kubernetes 上偵錯]。

如果沒有顯示該選項,請向下捲動。

7d30833d96632ca0.png

  1. 選取「是」來使用目前內容

a024a69b64de7e9e.png

  1. 選取「引用叢集」在初始設定期間建立的 Pod

faebabf372e3caf0.png

  1. 選取「Container Repository」。

fabc6dce48bae1b4.png

  1. 選取下方窗格中的「Output」分頁標籤,即可查看進度和通知
  2. 選取「Kubernetes: Run/Debug - 詳細」按一下管道下拉式選單中的右側,即可查看其他詳細資料和從容器串流的即時記錄檔

86b44c59db58f8f3.png

等待應用程式部署完成。

9f37706a752829fe.png

  1. 前往 Cloud 控制台查看已部署在 GKE 上的應用程式。

6ad220e5d1980756.png

  1. 選取「Kubernetes:執行/偵錯」即可返回簡化的檢視畫面。
  2. 建構和測試完成後,「Output」(輸出) 分頁標籤會顯示 Resource deployment/demo-app status completed successfully,以及一個網址:「Forwarded URL from service demo-app: http://localhost:8080」
  3. 在 Cloud Code 終端機中,將滑鼠遊標懸停在輸出 (http://localhost:8080) 的網址上,然後在顯示的工具提示中,選取「Follow link」。

28c5539880194a8e.png

系統會開啟新分頁,輸出內容如下:

d67253ca16238f49.png

善用中斷點

  1. 開啟位於 /src/main/java/com/example/springboot/HelloController.java 的「HelloController.java」應用程式
  2. 找出讀取 return String.format("Hello from your %s environment!", target); 的根路徑傳回陳述式
  3. 按一下行號左側的空白處,為該行新增中斷點。系統會顯示紅色指標,說明已設定中斷點

5027dc6da2618a39.png

  1. 重新載入瀏覽器並留意偵錯工具,會在中斷點停止程序,然後針對在 GKE 中從遠端執行的應用程式調查變數和狀態

71acfb426623cec2.png

  1. 按一下「變數」部分,直到找到「目標」為止變數。
  2. 觀察目前的值為「local」

a1160d2ed2bb5c82.png

  1. 按兩下變數名稱「target」在彈出式視窗中

將值改為「Cloud Workstations」

e597a556a5c53f32.png

  1. 按一下偵錯控制台中的 [繼續] 按鈕

ec17086191770d0d.png

  1. 請在瀏覽器中查看回應,以便顯示剛才輸入的更新值。

6698a9db9e729925.png

  1. 按一下行號左側的紅色指標,移除中斷點。這樣做可避免程式碼在後續研究室中停止執行。

熱重新載入

  1. 變更陳述式以傳回不同的值,例如「Hello from %s Code」
  2. 檔案會自動儲存並同步到 GKE 中的遠端容器
  3. 重新整理瀏覽器即可查看更新後的結果。
  4. 按一下偵錯工具列中的紅色方塊,即可停止偵錯工作階段

a541f928ec8f430e.png c2752bb28d82af86.png

選取「每次執行後都要清除資料」。

984eb2fa34867d70.png

5. 開發簡易的 CRUD 靜態服務

此時您的應用程式已全面完成容器化開發作業,您也完成了 Cloud Code 的基本開發工作流程。在接下來的各節中,您將學到以下內容:新增連結至 Google Cloud 代管資料庫的 REST 服務端點。

設定依附元件

應用程式程式碼會使用資料庫來保存其餘服務資料。在 pom.xl 中新增下列指令,確保依附元件可供使用

  1. 開啟 pom.xml 檔案,並將以下內容新增至設定的依附元件部分

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>

程式碼 REST 服務

Quote.java

/src/main/java/com/example/springboot/ 中建立名為 Quote.java 的檔案,然後複製下方的程式碼。這定義了應用程式中使用 quote 物件的 Entity 模型。

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

src/main/java/com/example/springboot 建立名為 QuoteRepository.java 的檔案,然後複製下列程式碼

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();
}

此程式碼使用 JPA 來保留資料。這個類別會擴充 Spring JPARepository 介面,並允許建立自訂程式碼。您已在程式碼中新增 findRandomQuote 自訂方法,

QuoteController.java

如要公開服務的端點,QuoteController 類別會提供這項功能。

src/main/java/com/example/springboot 建立名為 QuoteController.java 的檔案,並在下列內容中複製

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);
        }
    }

}

新增資料庫設定

application.yaml

為服務存取的後端資料庫新增設定。編輯 (或建立) src/main/resources 底下的 application.yaml 檔案,然後為後端新增參數化 Spring 設定。

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

新增資料庫遷移作業

建立「src/main/resources」下的資料夾「db/migration

建立 SQL 檔案:V1__create_quotes_table.sql

將下列內容貼到檔案中

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');

Kubernetes 設定

加入 deployment.yaml 檔案後,應用程式就能連線至 Cloud SQL 執行個體。

  • TARGET - 設定變數以指出執行應用程式的環境
  • SPRING_PROFILES_ACTIVE:顯示使用中的 Spring 設定檔 (設為 cloud-dev)
  • DB_HOST - 資料庫的私人 IP (在建立資料庫執行個體時都已記下,或在 Google Cloud 控制台的導覽選單中按一下 SQL)。請變更值!
  • DB_USER 和 DB_PASS - 在 CloudSQL 執行個體設定中,會以密鑰的形式儲存在 GCP 中

使用以下內容更新 deployment.yaml。

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

在終端機中執行下列指令,以您的資料庫位址取代 DB_HOST 值:

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

開啟 deployment.yaml,並驗證 DB_HOST 值是否已更新為執行個體 IP。

fd63c0aede14beba.png

部署及驗證應用程式

  1. 在 Cloud Shell 編輯器底部的窗格中,依序選取「Cloud Code」和畫面頂端的「Debug on Kubernetes」。

33a5cf41aae91adb.png

  1. 建構和測試完成後,「Output」(輸出) 分頁會顯示 Resource deployment/demo-app status completed successfully,以及一個網址:「Forwarded URL from service demo-app: http://localhost:8080」。請注意,有時通訊埠可能與 8081 不同。如果有的話,請設定適合的值。在終端機中設定網址的值
export URL=localhost:8080
  1. 檢視隨機引述

在終端機中,針對隨機報價端點多次執行下列指令。觀察重覆呼叫並傳回不同的報價

curl $URL/random-quote | jq
  1. 新增報價

使用下方列出的指令以 id=6 建立新報價,然後觀察系統傳回的要求

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. 刪除報價

現在,刪除您剛才使用刪除方法新增的引用,然後觀察 HTTP/1.1 204 回應代碼。

curl -v -X DELETE $URL/quotes/6
  1. 伺服器錯誤

項目刪除後,再次執行上一個要求,藉此顯示錯誤狀態

curl -v -X DELETE $URL/quotes/6

請注意,回應會傳回 HTTP:500 Internal Server Error

對應用程式進行偵錯

在上一節中,當您嘗試刪除資料庫中沒有的項目時,在應用程式中發現錯誤狀態。在本節中,您將設定中斷點以找出問題。DELETE 作業發生錯誤,因此您必須使用 quoteController 類別。

  1. 開啟「src/main/java/com/example/springboot/QuoteController.java
  2. 找出 deleteQuote() 方法
  3. 找出以下行:Optional<Quote> quote = quoteRepository.findById(id);
  4. 按一下行號左側的空白空間,設定該行的中斷點。
  5. 出現紅色指標,表示已設定中斷點
  6. 再次執行 delete 指令
curl -v -X DELETE $URL/quotes/6
  1. 按一下左欄中的圖示,切換回偵錯檢視畫面
  2. 觀察 QuoteController 類別中的偵錯行。
  3. 在偵錯工具中,按一下 step over 圖示 b814d39b2e5f3d9e.png
  4. 您會發現程式碼將內部伺服器錯誤 HTTP 500 傳回至不理想的用戶端。
   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

更新程式碼

程式碼不正確,必須重構 else 區塊,以便傳回 HTTP 404 找不到狀態碼。

修正錯誤。

  1. 偵錯工作階段仍在執行時,按下「continue」以完成要求按鈕,就能前往偵錯控制台
  2. 接著將 else 區塊變更為程式碼:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

方法應如下所示

@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. 重新執行刪除指令
curl -v -X DELETE $URL/quotes/6
  1. 逐步執行偵錯工具,觀察傳回呼叫端的 HTTP 404 找不到錯誤。
   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. 按一下偵錯工具列中的紅色方塊,即可停止偵錯工作階段

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. 恭喜

恭喜!在本研究室中,您已從頭開始建立新的 Java 應用程式,並設定為有效率地與容器搭配使用。接著,您按照傳統應用程式堆疊中的相同開發人員流程,將應用程式部署至遠端 GKE 叢集,並進行偵錯。

目前所學內容

  • 使用 Cloud Workstations 進行 InnerLoop 開發作業
  • 建立新的 Java 範例應用程式
  • 逐步完成開發程序
  • 開發簡易 CRUD REST 服務
  • GKE 叢集中的偵錯應用程式
  • 將應用程式連線至 CloudSQL 資料庫