$ ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=spring-k8s/hello-spring-k8s
Kubernetes で Spring Boot 実行
構築するもの
Spring 環境の Kubernetes は成熟しつつあります。2024 Spring 調査状況 (英語) によると、回答者の 65% が Spring 環境で Kubernetes を使用しています。
Kubernetes 上で Spring Boot アプリケーションを実行する前に、まずコンテナーイメージを生成する必要があります。Spring Boot は、Cloud Native Buildpacks を使用して、Maven または Gradle プラグインから Docker イメージを簡単に生成することをサポートしています。
このガイドの目的は、Kubernetes で Spring Boot アプリケーションを実行し、クラウドネイティブアプリケーションを構築できるいくつかのプラットフォーム機能を利用する方法を示すことです。
このガイドでは、2 つの Spring Boot Web アプリケーションを構築します。Cloud Native Buildpacks を使用して各 Web アプリケーションを Docker イメージにパッケージ化し、そのイメージに基づいて Kubernetes デプロイを作成し、デプロイにアクセスするためのサービスを作成します。
必要なもの
Eclipse STS や IntelliJ IDEA のような任意の IDE または VSCode のようなテキストエディター
Java 17 以降
Docker 環境
Kubernetes 環境
Docker デスクトップ (英語) は、このガイドに従うために必要な Docker 環境と Kubernetes 環境の両方を提供します。 |
このガイドを完了する方法
このガイドでは、Kubernetes 上で Spring Boot アプリを実行するために必要な成果物の作成に重点を置いています。そのため、このリポジトリ [GitHub] (英語) で提供されているコードを使用するのが最善の方法です。
このリポジトリは、使用する 2 つのサービスを提供します。
hello-spring-k8s
は、Hello World メッセージをエコーする基本的な Spring Boot REST アプリケーションです。hello-caller
は、Spring Boot REST アプリケーションhello-spring-k8s
を呼び出します。hello-caller
サービスは、Kubernetes 環境でサービス検出がどのように機能するかを示すためのものです。
これら 2 つのアプリケーションはどちらも Spring Boot REST アプリケーションであり、このガイドを使用して最初から作成できます。このガイドに固有のコードは、レッスンの進行に合わせて以下で紹介されます。
このガイドは個別のセクションに分かれています。
ソリューションリポジトリには、Kubernetes アーティファクトがすでに作成されています。このガイドでは、これらのオブジェクトの作成方法を段階的に説明しますが、実際の例についてはいつでもソリューションを参照できます。
Docker イメージを生成する
まず、Cloud Native Buildpacks を使用して hello-spring-k8s
プロジェクトの Docker イメージを生成します。hello-spring-k8s
ディレクトリで、次のコマンドを実行します。
これにより、spring-k8s/hello-spring-k8s
という名前の Docker イメージが生成されます。ビルドが完了すると、アプリケーション用の Docker イメージが作成され、次のコマンドで確認できます。
$ docker images spring-k8s/hello-spring-k8s
REPOSITORY TAG IMAGE ID CREATED SIZE
spring-k8s/hello-spring-k8s latest <ID> 44 years ago 325MB
これで、コンテナーイメージを開始し、それが機能することを確認できます。
$ docker run -p 8080:8080 --name hello-spring-k8s -t spring-k8s/hello-spring-k8s
アクチュエーター / ヘルスエンドポイントに HTTP リクエストを送信することで、すべてが機能していることをテストできます。
$ curl http://localhost:8080/actuator/health
{"status":"UP"}
先に進む前に、実行中のコンテナーを必ず停止してください。
$ docker stop hello-spring-k8s
Kubernetes の要件
アプリケーションのコンテナーイメージ(start.spring.io にアクセスするだけです! )を使用して、アプリケーションを Kubernetes で実行する準備が整いました。これを行うには、2 つのことが必要です。
Kubernetes CLI (kubectl)
アプリケーションをデプロイする Kubernetes クラスター
次の手順 (英語) に従って、KubernetesCLI をインストールします。
どの Kubernetes クラスターでも動作しますが、この記事では、できるだけ簡単にするために、ローカルでクラスターを起動します。Kubernetes クラスターをローカルで実行する最も簡単な方法は、Docker デスクトップ (英語) を使用することです。
チュートリアル全体で使用される一般的な Kubernetes フラグには、注目すべきものがあります。--dry-run=client
フラグは、送信するオブジェクトを送信せずに出力のみするように Kubernetes に指示します。-o yaml
フラグは、コマンドの出力が yaml であることを指定します。これら 2 つのフラグは、出力リダイレクト >
と組み合わせて使用され、Kubernetes コマンドをファイルにキャプチャーできます。これは、オブジェクトを作成する前に編集したり、繰り返し可能なプロセスを作成したりする場合に役立ちます。
Kubernetes へのデプロイ
このセクションのソリューションは k8s-artifacts/basic/*
で定義されています。
hello-spring-k8s
アプリケーションを Kubernetes にデプロイするには、Kubernetes がアプリケーションのデプロイ、実行、管理に使用できる YAML を生成するとともに、そのアプリケーションをクラスターの残りの部分に公開する必要があります。
提供されたソリューションを実行する代わりに、自分で yaml をビルドすることを選択した場合は、まず YAML 用のディレクトリを作成します。生成される yaml ファイルはパスに依存しないため、このフォルダーがどこにあるかは問題ではありません。
$ mkdir k8s
$ cd k8s
これで、kubectl を使用して、必要な基本的な YAML を生成できます。
$ kubectl create deployment gs-spring-boot-k8s --image spring-k8s/spring-k8s/hello-spring-k8s:latest -o yaml --dry-run=client > deployment.yaml
使用しているイメージはローカルなので、デプロイ内のコンテナーの imagePullPolicy
(英語) を変更する必要があります。yaml の containers:
仕様は次のようになります。
spec:
containers:
- image: spring-k8s/hello-spring-k8s
imagePullPolicy: Never
name: hello-spring-k8s
resources: {}
imagePullPolicy
を変更せずに デプロイを実行しようとすると、pod のステータスは ErrImagePull
になります。
deployment.yaml
ファイルは Kubernetes にアプリケーションのデプロイと管理の方法を指示しますが、アプリケーションを他のアプリケーションへのネットワークサービスにすることはできません。そのためには、サービスリソースが必要です。Kubectl は、サービスリソースの YAML を生成できます。
$ kubectl create service clusterip gs-spring-boot-k8s --tcp 80:8080 -o yaml --dry-run=client > service.yaml
これで、YAML ファイルを Kubernetes に適用する準備が整いました。
$ kubectl apply -f deployment.yaml
$ kubectl apply -f service.yaml
次に、実行できます。
$ kubectl get all
新しく作成されたデプロイ、サービス、pod が実行されていることがわかります。
NAME READY STATUS RESTARTS AGE
pod/gs-spring-boot-k8s-779d4fcb4d-xlt9g 1/1 Running 0 3m40s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/gs-spring-boot-k8s ClusterIP 10.96.142.74 <none> 80/TCP 3m40s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h55m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/gs-spring-boot-k8s 1/1 1 1 3m40s
NAME DESIRED CURRENT READY AGE
replicaset.apps/gs-spring-boot-k8s-779d4fcb4d 1 1 1 3m40s
残念ながら、Kubernetes のサービスはクラスターネットワークの外部に公開されていないため、HTTP リクエストを直接行うことはできません。kubectl の助けを借りて、ローカルマシンからクラスターで実行されているサービスに HTTP トラフィックを転送できます。
$ kubectl port-forward svc/gs-spring-boot-k8s 9090:80
port-forward コマンドを実行すると、localhost:9090 に HTTP リクエストを送信できるようになり、Kubernetes で実行されているサービスに転送されます。
$ curl http://localhost:9090/helloWorld
Hello World!!
先に進む前に、必ず上記の port-forward
コマンドを停止してください。
ベストプラクティス
このセクションのソリューションは k8s-artifacts/best_practice/*
で定義されています。
アプリケーションは Kubernetes 上で実行されますが、アプリケーションを最適に実行するには、ベストプラクティスを実装することをお勧めします。
テキストエディターで deployment.yaml
を開き、準備プロパティと有効プロパティをファイルに追加します。
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: gs-spring-boot-k8s
name: gs-spring-boot-k8s
spec:
replicas: 1
selector:
matchLabels:
app: gs-spring-boot-k8s
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: gs-spring-boot-k8s
spec:
containers:
- image: spring-k8s/hello-spring-k8s
imagePullPolicy: Never
name: hello-spring-k8s
resources: {}
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
status: {}
これは最初のベストプラクティスに対応します。さらに、アプリケーション設定にプロパティを追加する必要があります。アプリケーションは Kubernetes 上で実行されるため、優れたクラウド開発者が行うように、Kubernetes ConfigMaps (英語) を利用してこのプロパティを外部化できます。次に、その方法を見ていきます。
ConfigMaps を使用した設定の外部化
このセクションのソリューションは k8s-artifacts/config_map/*
で定義されています。
Spring Boot アプリケーションで正常なシャットダウンを有効にするには、application.properties
に server.shutdown=graceful
を設定します。この行をコードに直接追加するのではなく、ConfigMap (英語) を使用します。アクチュエーターエンドポイントを使用して、アプリケーションが ConfigMap のプロパティファイルを PropertySources のリストに追加していることを確認できます。
グレースフルシャットダウンを可能にし、すべてのアクチュエーターエンドポイントを公開するプロパティファイルを作成できます。アプリケーションがプロパティファイルを ConfigMap から PropertySources のリストに追加していることを確認する方法として、アクチュエーターエンドポイントを使用できます。
yaml ファイルを保存する場所に、application.properties
という新しいファイルを作成します。そのファイルに次のプロパティを追加します。
server.shutdown=graceful
management.endpoints.web.exposure.include=*
または、次のコマンドを実行して、コマンドラインから 1 つの簡単な手順でこれを実行できます。
$ cat <<EOF >./application.properties
server.shutdown=graceful
management.endpoints.web.exposure.include=*
EOF
プロパティファイルを作成したら、kubectl を使用して ConfigMap を作成できます (英語) 。
$ kubectl create configmap gs-spring-boot-k8s --from-file=./application.properties
ConfigMap を作成すると、次のようになります。
$ kubectl get configmap gs-spring-boot-k8s -o yaml
apiVersion: v1
data:
application.properties: |
server.shutdown=graceful
management.endpoints.web.exposure.include=*
kind: ConfigMap
metadata:
creationTimestamp: "2020-09-10T21:09:34Z"
name: gs-spring-boot-k8s
namespace: default
resourceVersion: "178779"
selfLink: /api/v1/namespaces/default/configmaps/gs-spring-boot-k8s
uid: 9be36768-5fbd-460d-93d3-4ad8bc6d4dd9
最後のステップは、この ConfigMap をボリュームとしてコンテナーにマウントすることです (英語) 。
これを行うには、デプロイ YAML を変更して、最初にボリュームを作成してから、そのボリュームをコンテナーにマウントする必要があります。
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: gs-spring-boot-k8s
name: gs-spring-boot-k8s
spec:
replicas: 1
selector:
matchLabels:
app: gs-spring-boot-k8s
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: gs-spring-boot-k8s
spec:
containers:
- image: spring-k8s/hello-spring-k8s
imagePullPolicy: Never
name: hello-spring-k8s
resources: {}
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
volumeMounts:
- name: config-volume
mountPath: /workspace/config
volumes:
- name: config-volume
configMap:
name: gs-spring-boot-k8s
status: {}
すべてのベストプラクティスを実装したら、新しいデプロイを Kubernetes に適用できます。これにより、別の Pod がデプロイされ、古い Pod が停止します(新しい Pod が正常に起動する限り)。
$ kubectl apply -f deployment.yaml
活性プローブと準備プローブが正しく構成されている場合、Pod は正常に起動し、準備完了状態に移行します。Pod が準備完了状態に到達しない場合は、戻って準備プローブの構成を確認してください。Pod が準備完了状態に達しても、Kubernetes が常に Pod を再起動する場合は、活性プローブが正しく構成されていません。pod が起動して起動したままの場合、すべてが正常に機能しています。
/actuator/env
エンドポイントを押すことにより、ConfigMap ボリュームがマウントされていること、およびアプリケーションがプロパティファイルを使用していることを確認できます。
$ kubectl port-forward svc/gs-spring-boot-k8s 9090:80
http://localhost:9090/actuator/env にアクセスすると、マウントされたボリュームから提供されたプロパティソースが表示されます。
curl http://localhost:9090/actuator/env | jq
{
"name":"applicationConfig: [file:./config/application.properties]",
"properties":{
"server.shutdown":{
"value":"graceful",
"origin":"URL [file:./config/application.properties]:1:17"
},
"management.endpoints.web.exposure.include":{
"value":"*",
"origin":"URL [file:./config/application.properties]:2:43"
}
}
}
続行する前に、必ず port-forward
コマンドを停止してください。
サービス検出と負荷分散
ガイドのこのパートでは、hello-caller
アプリケーションを追加します。このセクションのソリューションは、k8s-artifacts/service_discovery/*
で定義されています。
負荷分散を実証するために、まず既存の hello-spring-k8s
サービスを 3 つのレプリカに拡張してみましょう。これは、デプロイに replicas
構成を追加することで実行できます。
...
metadata:
creationTimestamp: null
labels:
app: gs-spring-boot-k8s
name: gs-spring-boot-k8s
spec:
replicas: 3
selector:
...
次のコマンドを実行して デプロイを更新します。
kubectl apply -f deployment.yaml
これで、3 つの pods が動作しているのがわかるはずです。
$ kubectl get pod --selector=app=gs-spring-boot-k8s
NAME READY STATUS RESTARTS AGE
gs-spring-boot-k8s-76477c6c99-2psl4 1/1 Running 0 15m
gs-spring-boot-k8s-76477c6c99-ss6jt 1/1 Running 0 3m28s
gs-spring-boot-k8s-76477c6c99-wjbhr 1/1 Running 0 3m28s
このセクションでは 2 番目のサービスを実行する必要があるため、hello-caller
に注目しましょう。このアプリケーションには、hello-spring-k8s
を呼び出す単一のエンドポイントがあります。URL は kubernetes のサービス名と同じであることに注意してください。
@GetMapping
public Mono<String> index() {
return webClient.get().uri("http://gs-spring-boot-k8s/name")
.retrieve()
.toEntity(String.class)
.map(entity -> {
String host = entity.getHeaders().get("k8s-host").get(0);
return "Hello " + entity.getBody() + " from " + host;
});
}
Kubernetes は DNS エントリを設定して、hello-spring-k8s
のサービス ID を使用して、pods の IP アドレスを知らなくてもサービスに HTTP リクエストを送信できるようにします。Kubernetes サービスは、これらのリクエストをすべての pods 間で負荷分散します。
ここで、hello-caller
アプリケーションを Docker イメージとしてパッケージ化し、Kubernetes リソースとして実行する必要があります。Docker イメージを生成するには、もう一度 Cloud Native Buildpacks を使用します。hello-caller
フォルダーで、次のコマンドを実行します。
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=spring-k8s/hello-caller
Docker イメージが作成されると、すでに見たものと同様の新しい デプロイを作成できます。完成した構成は caller_deployment.yaml
ファイルに用意されています。このファイルを実行します。
kubectl apply -f caller_deployment.yaml
次のコマンドでアプリが実行されていることを確認できます。
$ kubectl get pod --selector=app=gs-hello-caller
NAME READY STATUS RESTARTS AGE
gs-hello-caller-774469758b-qdtsx 1/1 Running 0 2m34s
提供されているファイル caller_service.yaml
で定義されているサービスも作成する必要があります。このファイルは次のコマンドで実行できます。
kubectl apply -f caller_service.yaml
これで、2 つの デプロイと 2 つのサービスが実行されたため、アプリケーションをテストする準備が整いました。
$ kubectl port-forward svc/gs-hello-caller 9090:80
$ curl http://localhost:9090 -i; echo
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 4
Date: Mon, 14 Sep 2020 15:37:51 GMT
Hello Paul from gs-spring-boot-k8s-76477c6c99-5xdq8
複数のリクエストを行うと、異なる名前が返されます。リクエストには pod の名前もリストされます。複数のリクエストを送信すると、この値も変わります。Kubernetes ロードバランサーが別の pod を選択するのを待っている間に、最新のリクエストを返した pod を削除することで、プロセスを高速化できます。
$ kubectl delete pod gs-spring-boot-k8s-76477c6c99-5xdq8
要約
Spring Boot アプリケーションを Kubernetes で実行するには、start.spring.io にアクセスするだけで済みます。Spring Boot のゴールは常に、Java アプリケーションの構築と実行を可能な限り簡単にすることであり、アプリケーションの実行方法に関係なく、そのゴールを実現できるように努めています。Kubernetes を使用してクラウドネイティブアプリケーションを構築するには、Spring Boot の組み込みイメージビルダーを使用するイメージを作成し、Kubernetes プラットフォームの機能を活用するだけです。