Cloud Workstations と Cloud Code を使用した開発

1. 概要

このラボでは、コンテナ化された環境で Java アプリケーションの開発を担当するソフトウェア エンジニアの開発ワークフローを効率化するために設計された機能について説明します。一般的なコンテナ開発では、コンテナの詳細とコンテナ ビルドプロセスを理解する必要があります。また、デベロッパーは通常、フローを中断して IDE から移動し、リモート環境でアプリケーションをテストしてデバッグする必要があります。このチュートリアルで説明したツールとテクノロジーを使用すると、開発者は IDE を離れることなく、コンテナ化されたアプリケーションを効率的に操作できます。

学習内容

このラボでは、GCP でコンテナを使用して開発する方法について学びます。

  • Cloud Workstations を使用した InnerLoop 開発
  • 新しい Java スターター アプリケーションの作成
  • 開発プロセスを理解する
  • シンプルな CRUD Rest サービスの開発
  • GKE クラスタでのアプリケーションのデバッグ
  • アプリケーションを CloudSQL データベースに接続する

58a4cdd3ed7a123a.png

2. 設定と要件

セルフペース型の環境設定

  1. Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列です。この設定はいつでも変更できます。
  • プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud コンソールでは一意の文字列が自動生成されます。通常は、この内容を意識する必要はありません。ほとんどの Codelab では、プロジェクト ID(通常は PROJECT_ID と識別されます)を参照する必要があります。生成された ID が好みではない場合は、ランダムに別の ID を生成できます。または、ご自身で試して、利用可能かどうかを確認することもできます。このステップ以降は変更できず、プロジェクトを通して同じ ID になります。
  • なお、3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
  1. 次に、Cloud のリソースや API を使用するために、Cloud コンソールで課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルの終了後に請求が発生しないようにリソースをシャットダウンするには、作成したリソースを削除するか、プロジェクト全体を削除します。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Cloudshell エディタを起動する

このラボは、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_ID 変数と REGION 変数として保存します。

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 Console で 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

[構成] セクションで結果を確認します。READY ステータスに移行するまでに 2 分かかります。

7a6af5aa2807a5f2.png

コンソールで Cloud Workstations を開き、新しいインスタンスを作成します。

a53adeeac81a78c8.png

名前を my-workstation に変更し、既存の構成 codeoss-java を選択します。

f21c216997746097.png

[ワークステーション] セクションで結果を確認します。

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 と Jib を追加する

Spring Boot DevTools を有効にするには、エディタのエクスプローラで 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 は、アプリケーションをコンテナ イメージにパッケージ化するすべてのステップを処理する、高速でシンプルなコンテナ イメージ ビルダーです。Dockerfile の作成や Docker のインストールは不要で、Maven と Gradle に直接統合されています。

pom.xml ファイルを下にスクロールし、Jib プラグインを含むように Build セクションを更新します。ビルド セクションは、完了すると次のようになります。

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 に移動します。
  • Space キーを押してオプションを選択します。
  • 続行するには Enter キーを押してください
  1. ポートに 8080 と入力します。
  2. y」と入力して構成を保存します

ワークスペース skaffold.yamldeployment.yaml に 2 つのファイルが追加されます。

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 の「同期」プロファイルを利用します。このステップでは、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 に push し、アプリケーションを GKE にデプロイします。これはバックグラウンドで実行され、デベロッパー フローから詳細が抽象化されます。Cloud Code は、コンテナベースの開発に従来のデバッグ機能とホットシンク機能を提供することで、開発プロセスを強化します。

Google Cloud にログインする

Cloud Code アイコンをクリックし、[Sign in to 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. 初期設定で作成された「quote-cluster」を選択します。

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

gcloud CLI を使用して現在のプロジェクト ID を設定する

このラボのプロジェクト ID を Qwiklabs ページからコピーします。

fcff2d10007ec5bc.png

gcloud CLI コマンドを実行して、プロジェクト ID を設定します。コマンドを実行する前に、サンプル プロジェクト ID を置き換えます。

gcloud config set project qwiklabs-gcp-project-id

出力例:

f1c03d01b7ac112c.png

Kubernetes でのデバッグ

  1. 左側のペインの下部にある [Cloud Code] を選択します。

60b8e4e95868b561.png

  1. [DEVELOPMENT SESSIONS] の下に表示されるパネルで、[Kubernetes 上でデバッグする] を選択します。

オプションが表示されない場合は、下にスクロールします。

7d30833d96632ca0.png

  1. [はい] を選択して現在のコンテキストを使用します。

a024a69b64de7e9e.png

  1. 初期設定時に作成された「quote-cluster」を選択します。

faebabf372e3caf0.png

  1. [コンテナ リポジトリ] を選択します。

fabc6dce48bae1b4.png

  1. 下部ペインの [出力] タブを選択して、進行状況と通知を表示する
  2. 右側のチャンネル プルダウンで [Kubernetes: Run/Debug - Detailed] を選択すると、追加の詳細とコンテナからライブ ストリーミングされるログが表示されます。

86b44c59db58f8f3.png

アプリケーションがデプロイされるまで待ちます。

9f37706a752829fe.png

  1. Cloud コンソールで GKE にデプロイされたアプリケーションを確認します。

6ad220e5d1980756.png

  1. [OUTPUT] タブのプルダウンから [Kubernetes: Run/Debug] を選択して、簡略ビューに戻ります。
  2. ビルドとテストが完了すると、[出力] タブに Resource deployment/demo-app status completed successfully と URL「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。
  3. Cloud Code ターミナルで、出力の URL(http://localhost:8080)にカーソルを合わせ、表示されたツールチップで [リンクをたどる] を選択します。

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); と書かれたルートパスの return 文を見つけます。
  3. 行番号の左側にある余白をクリックして、その行にブレークポイントを追加します。ブレークポイントが設定されたことを示す赤いインジケーターが表示されます。

5027dc6da2618a39.png

  1. ブラウザを再読み込みします。デバッガがブレークポイントでプロセスを停止し、GKE でリモートで実行されているアプリケーションの変数と状態を調べることができます。

71acfb426623cec2.png

  1. [Target] 変数が見つかるまで、[変数] セクションをクリックして下に移動します。
  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 Rest サービスの開発

これで、コンテナ化された開発用にアプリケーションが完全に構成され、Cloud Code を使用した基本的な開発ワークフローを完了しました。以降のセクションでは、Google Cloud のマネージド データベースに接続する REST サービス エンドポイントを追加して、学習した内容を実践します。

依存関係を構成する

アプリケーション コードは、データベースを使用して REST サービスデータを永続化します。pom.xl に以下を追加して、依存関係が使用可能であることを確認します。

  1. pom.xml ファイルを開き、構成の dependencies セクションに以下を追加します。

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 オブジェクトのエンティティ モデルを定義します。

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/springbootQuoteRepository.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/springbootQuoteController.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/resourcesdb/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 Config

deployment.yaml ファイルに次の追加を行うと、アプリケーションが CloudSQL インスタンスに接続できるようになります。

  • TARGET - アプリが実行される環境を示すように変数を構成します
  • SPRING_PROFILES_ACTIVE - アクティブな Spring プロファイルを表示します。これは cloud-dev に構成されます。
  • DB_HOST - データベースのプライベート IP。データベース インスタンスの作成時にメモしたか、Google Cloud コンソールのナビゲーション メニューで SQL をクリックして確認します。値を変更してください。
  • DB_USER と DB_PASS - CloudSQL インスタンスの構成で設定され、GCP の Secret として保存されます。

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. ビルドとテストが完了すると、[出力] タブに Resource deployment/demo-app status completed successfully と URL「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。ポートが 8081 のように異なる場合もあります。その場合は、適切な値を設定します。ターミナルで URL の値を設定する
export URL=localhost:8080
  1. ランダムな引用文を表示する

ターミナルから、次のコマンドを random-quote エンドポイントに対して複数回実行します。繰り返し呼び出しで異なる引用符が返されることを確認する

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. コードがクライアントに Internal Server Error 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 Not Found ステータス コードを返す必要があります。

エラーを修正します。

  1. デバッグ セッションがまだ実行されている状態で、デバッグ コントロール パネルの [続行] ボタンを押してリクエストを完了します。
  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 Not Found を確認します。
   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 データベースに接続する