1. 概要
このラボでは、コンテナ化された環境で Java アプリケーションを開発するソフトウェア エンジニア向けに、開発ワークフローを効率化するための特長と機能を紹介します。一般的なコンテナ開発では、ユーザーがコンテナの詳細とコンテナのビルドプロセスを理解する必要があります。さらに、デベロッパーは通常、作業の中断を余儀なくされ、IDE から離れてリモート環境でアプリケーションのテストやデバッグを行う必要もあります。このチュートリアルで説明するツールとテクノロジーを使用すると、デベロッパーは IDE を離れることなく、コンテナ化されたアプリケーションを効果的に操作できます。
学習内容
このラボでは、GCP でコンテナを使用して開発するための次のような方法について学びます。
- Cloud Workstations を使用した InnerLoop 開発
- 新しい Java スターター アプリケーションを作成する
- 開発プロセスの説明
- シンプルな CRUD REST サービスを開発する
- GKE クラスタ上のアプリケーションをデバッグする
- アプリケーションを Cloud SQL データベースに接続する
2. 設定と要件
セルフペース型の環境設定
- Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。
- プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列です。この値はいつでも更新できます。
- プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud コンソールでは一意の文字列が自動生成されます。通常、それが何であるかは関係ありません。ほとんどの Codelab では、プロジェクト ID を参照する必要があります(通常は
PROJECT_ID
として識別されます)。生成された ID が気に入らない場合は、別のランダムな ID を生成できます。または、ご自身でお試しになることもできます。このステップを終えた後は変更できず、プロジェクト期間中は維持されます。 - なお、3 つ目の値は、一部の API で使用される [プロジェクト番号] です。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
- 次に、Cloud のリソースや API を使用するために、Cloud コンソールで課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルの終了後に課金が発生しないようにリソースをシャットダウンするには、作成したリソースを削除するか、プロジェクト全体を削除します。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。
Cloudshell エディタを起動する
このラボは、Google Cloud Shell エディタで使用するように設計、テストされています。エディタにアクセスするには、
- https://console.cloud.google.com から Google プロジェクトにアクセスします。
- 右上にある Cloud Shell エディタのアイコンをクリックします。
- ウィンドウの下部に新しいペインが開きます
- [エディタを開く] ボタンをクリックします。
- エディタが開き、右側にエクスプローラ、中央部分にエディタが表示されます
- 画面の下部にターミナル ペインも表示されます。
- ターミナルが開いていない場合は、`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
ステータスになるまで待ちます。
ワークステーション構成の作成
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 分かかります。
コンソールで Cloud Workstations を開き、新しいインスタンスを作成します。
名前を my-workstation
に変更し、既存の構成(codeoss-java
)を選択します。
[ワークステーション] セクションで結果を確認します。
ワークステーションを起動
ワークステーションを起動します。
アドレスバーのアイコンをクリックして、サードパーティ Cookie を許可します。
[サイトが動作していない場合] をクリックします。
[Cookie を許可] をクリックします。
ワークステーションを起動すると、Code OSS IDE が表示されます。[完了マークを付ける] をクリックしますワークステーションの IDE では
3. 新しい Java スターター アプリケーションを作成する
このセクションでは、spring.io が提供するサンプル アプリケーションを利用して、新しい Java Spring Boot アプリケーションをゼロから作成します。新しいターミナルを開きます。
サンプル アプリケーションのクローンを作成する
- スターター アプリケーションを作成する
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] ボタンをクリックし、ワークステーションにコピーして貼り付けられるようにします。
- アプリケーションを解凍する
unzip sample-app.zip -d sample-app
- 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>
」という説明文の後に次のコードを貼り付けます。
- 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>
- 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 のマニフェストを作成します。
ターミナルで以下のコマンドを実行してプロセスを開始します。
- ターミナルで次のコマンドを実行します。
skaffold init --generate-manifests
- プロンプトが表示されたら、次の操作を行います。
- 矢印を使用して
Jib Maven Plugin
にカーソルを移動します。 - Space キーを押してオプションを選択します。
- Enter キーを押して続行
- ポートに「8080」と入力します。
- 「y」と入力して構成を保存します。
ワークスペースの skaffold.yaml
と deployment.yaml
に 2 つのファイルが追加されます。
Skaffold の出力:
アプリ名を更新
現在、構成に含まれるデフォルト値は、アプリケーションの名前と一致していません。デフォルト値ではなく、アプリケーション名を参照するようにファイルを更新します。
- Skaffold 構成のエントリを変更する
skaffold.yaml
を開きます。- 現在
pom-xml-image
として設定されているイメージ名を選択します - 右クリックして [すべてのオカレンスを変更] を選択します。
- 新しい名前として「
demo-app
」と入力します。
- Kubernetes 構成のエントリを変更する
deployment.yaml
ファイルを開く- 現在
pom-xml-image
として設定されているイメージ名を選択します - 右クリックして [すべてのオカレンスを変更] を選択します。
- 新しい名前として「
demo-app
」と入力します。
自動同期モードを有効にする
ホットリロードを最適化するには、Jib が提供する同期機能を使用します。このステップでは、ビルドプロセスでその機能を使用するように Skaffold を構成します。
「同期」はSkaffold 構成で構成しているプロファイルは、Spring の「同期」を利用します。前のステップで構成し、spring-dev-tools のサポートを有効にしたプロファイル。
- 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
というファイルを作成します。
次の内容をファイルに貼り付けて、デフォルトの 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] を選択します。
[ログインに進む] をクリックします。
ターミナルで出力を確認し、リンクを開きます。
Qwiklabs の受講者用認証情報でログインします。
[許可] を選択:
確認コードをコピーして、[ワークステーション] タブに戻ります。
確認コードを貼り付けて、Enter キーを押します。
Kubernetes クラスタを追加
- クラスタを追加する
- Google Kubernetes Engine を選択します。
- プロジェクトを選択します。
- [qwiklabs-cluster] を選択します。初期セットアップで作成された すべてのジョブに適用されます
gcloud CLI を使用して現在のプロジェクト ID を設定する
Qwiklabs ページからこのラボのプロジェクト ID をコピーします。
gcloud cli コマンドを実行してプロジェクト ID を設定します。コマンドを実行する前に、サンプル プロジェクト ID を置き換えます。
gcloud config set project qwiklabs-gcp-project-id
出力例:
Kubernetes でのデバッグ
- 下部の左側のペインで [Cloud Code] を選択します。
- [開発セッション] の下に表示されるパネルで、[Kubernetes でのデバッグ] を選択します。
オプションが表示されていない場合は下にスクロールします。
- [はい] を選択します。現在のコンテキストを使用します。
- [qwiklabs-cluster] を選択します。初期セットアップ中に作成されたファイルです。
- [Container Repository] を選択します。
- 下部のペインで [Output] タブを選択すると、進行状況と通知が表示されます。
- [Kubernetes: Run/Debug - Detailed] を選択します。右側のチャネル プルダウンから、追加の詳細情報や、コンテナからライブ ストリーミングされるログを確認できます。
アプリケーションがデプロイされるまで待ちます。
- Cloud コンソールで、GKE にデプロイされたアプリケーションを確認します。
- [Kubernetes: Run/Debug] を選択すると、簡素化されたビューに戻ります。[出力]タブのプルダウンから [出力]を選択します
- ビルドとテストが完了すると、[Output] タブに
Resource deployment/demo-app status completed successfully
と表示され、URL として「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。 - Cloud Code ターミナルで、出力の URL(http://localhost:8080)にカーソルを合わせ、表示されたツールチップで [Follow link] を選択します。
新しいタブが開き、以下の出力が表示されます。
ブレークポイントの活用
/src/main/java/com/example/springboot/HelloController.java
にあるHelloController.java
アプリを開きます。return String.format("Hello from your %s environment!", target);
というルートパスの return ステートメントを見つけます。- 行番号の左側にある空白スペースをクリックして、その行にブレークポイントを追加します。ブレークポイントが設定されたことを示す赤いインジケーターが表示される
- ブラウザを再読み込みすると、デバッガがブレークポイントでプロセスを停止し、GKE でリモートで実行されているアプリケーションの変数と状態を調査できるようになります。
- [変数] 欄をクリックして、[ターゲット] を探します。変数です。
- 現在の値が「local」であることを確認します。
- 変数名「target」をダブルクリックします。ポップアップで
「Cloud Workstations」に変更してください。
- デバッグ用コントロール パネルで [続行] ボタンをクリックします。
- ブラウザでレスポンスを確認します。入力した値が更新されています。
- 行番号の左側にある赤いインジケーターをクリックして、ブレークポイントを削除します。こうすることで、このラボを進めたときに、この行でコードの実行が停止するのを防ぐことができます。
ホットリロード
- 「Hello from %s Code」など、別の値を返すようにステートメントを変更します。
- ファイルが自動的に保存され、GKE のリモート コンテナに同期されます。
- ブラウザを更新して、更新された結果を確認してください。
- デバッグ ツールバーの赤い正方形をクリックしてデバッグ セッションを停止します
[Yes clean up after each run] を選択します。
5. シンプルな CRUD REST サービスを開発する
これで、アプリケーションはコンテナ化された開発用に完全に構成され、Cloud Code での基本的な開発ワークフローはひととおり確認できました。以降のセクションでは、Google Cloud のマネージド データベースに接続する REST サービス エンドポイントを追加して、学習した内容を実践します。
依存関係を構成する
アプリケーション コードは、データベースを使用して残りのサービスデータを保持します。pom.xl に以下を追加して、依存関係が利用可能であることを確認します。
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/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。この 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 で更新されたことを確認します。
アプリケーションのデプロイと検証
- Cloud Shell エディタの下部にあるペインで、[Cloud Code] を選択し、画面上部の [Debug on Kubernetes] を選択します。
- ビルドとテストが完了すると、[Output] タブに
Resource deployment/demo-app status completed successfully
と表示され、URL として「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。ポートが 8081 など異なる場合があります。適切な値を設定します。ターミナルで URL の値を設定する
export URL=localhost:8080
- ランダムな引用を表示
ターミナルから、ランダム引用符のエンドポイントに対して以下のコマンドを複数回実行します。異なる引用符を返す繰り返し呼び出しを確認する
curl $URL/random-quote | jq
- 見積もりを追加
以下のコマンドを使用して 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
- 見積もりを削除する
次に、先ほど追加した引用符を delete メソッドで削除し、HTTP/1.1 204
レスポンス コードを確認します。
curl -v -X DELETE $URL/quotes/6
- サーバーエラー
エントリが削除されてから最後のリクエストを再度実行すると、エラー状態になる
curl -v -X DELETE $URL/quotes/6
レスポンスが HTTP:500 Internal Server Error
を返すことに注意してください。
アプリケーションをデバッグする
前のセクションでは、データベースにないエントリを削除しようとしたときに、アプリケーションでエラー状態が見つかりました。このセクションでは、問題を特定するためのブレークポイントを設定します。DELETE オペレーションでエラーが発生したため、QuoteController クラスで作業します。
src/main/java/com/example/springboot/QuoteController.java
を開きます。deleteQuote()
メソッドを見つけます。- 「
Optional<Quote> quote = quoteRepository.findById(id);
」という行を探します。 - 行番号の左側にある空白スペースをクリックして、その行にブレークポイントを設定します。
- ブレークポイントが設定されたことを示す赤いインジケーターが表示される
delete
コマンドを再実行する
curl -v -X DELETE $URL/quotes/6
- 左側の列のアイコンをクリックしてデバッグビューに戻る
- QuoteController クラスでデバッグ行が停止したことを確認します。
- デバッガで、
step over
アイコンをクリックします。
- コードが内部サーバーエラー 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
ブロックをリファクタリングする必要があります。
エラーを修正します。
- デバッグ セッションが実行中のままで、[続行] をクリックしてリクエストを完了します。[デバッグ コントロール パネル] ボタンを使用します。
- 次に、
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); } }
- 削除コマンドを再実行する
curl -v -X DELETE $URL/quotes/6
- デバッガを順に実行して、呼び出し元に 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
- デバッグ ツールバーの赤い正方形をクリックしてデバッグ セッションを停止します
6. 完了
これで、このラボでは、新しい Java アプリケーションをゼロから作成し、コンテナと効果的に連携するように構成しました。その後、従来のアプリケーション スタックで見られるのと同じデベロッパー フローに従って、アプリケーションをリモート GKE クラスタにデプロイし、デバッグしました。
学習した内容
- Cloud Workstations を使用した InnerLoop 開発
- 新しい Java スターター アプリケーションを作成する
- 開発プロセスの説明
- シンプルな CRUD REST サービスを開発する
- GKE クラスタ上のアプリケーションをデバッグする
- アプリケーションを Cloud SQL データベースに接続する