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

1. 概要

このラボでは、コンテナ化された環境で Java アプリケーションを開発するソフトウェア エンジニア向けに、開発ワークフローを効率化するための特長と機能を紹介します。一般的なコンテナ開発では、ユーザーがコンテナの詳細とコンテナのビルドプロセスを理解する必要があります。さらに、デベロッパーは通常、作業の中断を余儀なくされ、IDE から離れてリモート環境でアプリケーションのテストやデバッグを行う必要もあります。このチュートリアルで説明するツールとテクノロジーを使用すると、デベロッパーは IDE を離れることなく、コンテナ化されたアプリケーションを効果的に操作できます。

学習内容

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

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

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 を生成できます。または、ご自身でお試しになることもできます。このステップを終えた後は変更できず、プロジェクト期間中は維持されます。
  • なお、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 にコードをデプロイし、Cloud SQL データベースに保存されているデータにアクセスします。以下の設定スクリプトは、このインフラストラクチャを準備します。プロビジョニング プロセスには 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

[構成] セクションで結果を確認します。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

このメッセージが表示された場合は [Allow] ボタンをクリックし、ワークステーションにコピーして貼り付けられるようにします。

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 を有効にするには、エディタでエクスプローラから 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 ファイルを下にスクロールし、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 は、コンテナ開発を簡素化するための統合ツールを提供します。このステップでは、ベース Kubernetes YAML ファイルを自動的に作成する Skaffold を初期化します。このプロセスでは、Dockerfile などのコンテナ イメージ定義を含むディレクトリを特定し、それぞれに対して Deployment と Service のマニフェストを作成します。

ターミナルで以下のコマンドを実行してプロセスを開始します。

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. [qwiklabs-cluster] を選択します。初期セットアップで作成された すべてのジョブに適用されます

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. [qwiklabs-cluster] を選択します。初期セットアップ中に作成されたファイルです。

faebabf372e3caf0.png

  1. [Container Repository] を選択します。

fabc6dce48bae1b4.png

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

86b44c59db58f8f3.png

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

9f37706a752829fe.png

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

6ad220e5d1980756.png

  1. [Kubernetes: Run/Debug] を選択すると、簡素化されたビューに戻ります。[出力]タブのプルダウンから [出力]を選択します
  2. ビルドとテストが完了すると、[Output] タブに Resource deployment/demo-app status completed successfully と表示され、URL として「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。
  3. Cloud Code ターミナルで、出力の URL(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); というルートパスの return ステートメントを見つけます。
  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

[Yes clean up after each run] を選択します。

984eb2fa34867d70.png

5. シンプルな CRUD REST サービスを開発する

これで、アプリケーションはコンテナ化された開発用に完全に構成され、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 オブジェクトのエンティティ モデルを定義します。

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/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。この IP は、データベース インスタンスの作成時に、または Google Cloud コンソールのナビゲーション メニューで SQL をクリックして確認できます。値を変更してください。
  • DB_USER と DB_PASS - Cloud SQL インスタンス構成で設定され、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 と表示され、URL として「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。ポートが 8081 など異なる場合があります。適切な値を設定します。ターミナルで URL の値を設定する
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. 見積もりを削除する

次に、先ほど追加した引用符を delete メソッドで削除し、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

コードを更新する

コードが誤っているため、HTTP 404「見つかりません」ステータス コードを返すように else ブロックをリファクタリングする必要があります。

エラーを修正します。

  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 クラスタ上のアプリケーションをデバッグする
  • アプリケーションを Cloud SQL データベースに接続する