# 將您的Spring Boot應用程序部署到Kubernetes
在構建在云中運行的Java應用程序時, 無疑是 [Spring和Spring Boot 最愛](https://www.jrebel.com/blog/2020-java-technology-report) 。 越來越明顯的是,諸如Docker和Kubernetes之類的技術 [在Spring社區中扮演著重要的角色](https://tanzu.vmware.com/content/ebooks/state-of-spring-2020) 。

[將您的Spring Boot應用程序打包到Docker容器中](https://spring.io/guides/gs/spring-boot-docker/) 并將該應用程序部署到Kubernetes已經有一段時間了,并且花費很少的精力。 由于“使罐子不打架”的座右銘,將Spring Boot應用程序容器化所需的全部就是帶有JRE的容器來運行該罐子。 有了Docker容器后,在Kubernetes中運行容器化的Spring Boot應用程序僅是運行容器的問題。
也就是說,隨著越來越多的人將Spring Boot應用程序部署到Kubernetes,我們顯然可以做得更好。 為此,我們 [在Spring Boot 2.3 了一些增強,](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#cloud-deployment-kubernetes) 中 并 [進行 在即將發布的Spring Boot 2.4版本 更多改進,](https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4) 中進行了 以使在Kubernetes上運行Spring Boot的體驗更好。
本指南的目的是向您展示如何在Kubernetes上運行Spring Boot應用程序,以及如何利用一些平臺功能來構建云原生應用程序。
## 入門:start.spring.io
那么,您需要如何開始在Kubernetes上運行Spring Boot應用程序?
只是快速訪問“每個人在互聯網上最喜歡的地方: [start.spring.io](https://start.spring.io) ”。
為您的應用程序創建目錄。 然后運行以下cURL命令從start.spring.io生成一個應用程序:
~~~
$ curl https://start.spring.io/starter.tgz -d dependencies=webflux,actuator | tar -xzvf -
~~~
或者, [單擊此處](https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.3.4.RELEASE&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=webflux,actuator) 以正確的配置打開start.spring.io,然后單擊“生成”以下載項目。
使用基本的Spring Boot Web應用程序,我們現在需要創建一個Docker容器。 使用Spring Boot 2.3,我們可以使用Spring Boot Maven或Gradle插件為我們完成此操作,而無需修改應用程序。 為了使構建映像插件正常工作,您需要在 [本地安裝Docker](https://docs.docker.com/get-docker/) 。
~~~
$ ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=spring-k8s/gs-spring-boot-k8s
~~~
構建完成后,我們現在應該為我們的應用程序創建一個Docker映像,我們可以使用以下命令進行檢查:
~~~
$ docker images spring-k8s/gs-spring-boot-k8s
REPOSITORY TAG IMAGE ID CREATED SIZE
spring-k8s/gs-spring-boot-k8s latest 21f21558c005 40 years ago 257MB
~~~
現在,我們可以啟動容器映像并確保其有效:
~~~
$ docker run -p 8080:8080 --name gs-spring-boot-k8s -t spring-k8s/gs-spring-boot-k8s
~~~
我們可以通過向執行器/運行狀況端點發出HTTP請求來測試一切是否正常:
~~~
$ curl http://localhost:8080/actuator/health; echo
{"status":"UP"}
~~~
在繼續前進之前,請務必停止正在運行的容器。
~~~
$ docker stop gs-spring-boot-k8s
~~~
## 到Kubernetes
有了我們應用程序的容器映像(只需要訪問start.spring.io!),我們就可以在Kubernetes上運行我們的應用程序了。 為此,我們需要做兩件事:
1. Kubernetes CLI(kubectl)
2. 將我們的應用程序部署到的Kubernetes集群
請按照以下 [說明](https://kubernetes.io/docs/tasks/tools/install-kubectl/) 安裝Kubernetes CLI。
任何Kubernetes集群都可以工作,但是,出于本文的目的,我們在本地啟動了一個集群,以使其盡可能簡單。 在本地運行Kubernetes集群的最簡單方法是使用名為 的工具 [Kind](https://kind.sigs.k8s.io/) 。 請按照以下 [說明](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) 安裝Kind。 安裝Kind之后,我們現在可以創建集群。
~~~
$ kind create cluster
~~~
Kind創建集群后,它將自動配置Kubernetes CLI指向該集群。 為確保所有設置均正確,請運行:
~~~
$ kubectl cluster-info
~~~
如果沒有看到任何錯誤,則可以將應用程序部署到Kubernetes。
### 部署到州長
要將我們的應用程序部署到Kubernetes,我們需要生成一些YAML,以便Kubernetes可以使用它來部署,運行和管理我們的應用程序,并將該應用程序暴露給集群的其余部分。
首先為我們的YAML創建目錄:
~~~
$ mkdir k8s
~~~
現在我們可以使用kubectl生成所需的基本YAML:
~~~
$ kubectl create deployment gs-spring-boot-k8s --image spring-k8s/gs-spring-boot-k8s:snapshot -o yaml --dry-run=client > k8s/deployment.yaml
~~~
這 `deployment.yaml`該文件告訴Kubernetes如何部署和管理我們的應用程序,但是它不允許我們的應用程序成為其他應用程序的網絡服務。 為此,我們需要一種服務資源。 Kubectl可以幫助我們為服務資源生成YAML:
~~~
$ kubectl create service clusterip gs-spring-boot-k8s --tcp 80:8080 -o yaml --dry-run=client > k8s/service.yaml
~~~
在將這些YAML文件應用于Kubernetes集群之前,我們需要將Docker映像加載到Kind集群中。 如果我們不這樣做,Kubernetes會嘗試在Docker Hub中為我們的映像找到容器,而該容器當然是不存在的。
~~~
$ docker tag spring-k8s/gs-spring-boot-k8s spring-k8s/gs-spring-boot-k8s:snapshot
$ kind load docker-image spring-k8s/gs-spring-boot-k8s:snapshot
~~~
我們為圖像創建一個新標簽,因為使用最新標簽的圖像的默認Kubernetes拉策略是 Always。 由于該映像在Docker存儲庫中不存在于外部,因此我們想使用以下映像拉取策略 Never 或者 IfNotPresent。 當使用最新標簽以外的標簽時,默認的Kubernetes拉策略為 IfNotPresent.
現在我們準備將YAML文件應用于Kubernetes:
~~~
$ kubectl apply -f ./k8s
~~~
然后,您可以運行:
~~~
$ 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/actuator; echo
~~~
~~~
{
"_links":{
"self":{
"href":"http://localhost:9090/actuator",
"templated":false
},
"health-path":{
"href":"http://localhost:9090/actuator/health/{*path}",
"templated":true
},
"health":{
"href":"http://localhost:9090/actuator/health",
"templated":false
},
"info":{
"href":"http://localhost:9090/actuator/info",
"templated":false
}
}
}
~~~
在繼續前進之前,請務必停止 `port-forward` 上面的命令。
### 最佳實踐
我們的應用程序在Kubernetes上運行,但是,為了使我們的應用程序最佳運行,我們建議實現以下幾種最佳實踐:
1. [添加準備和活躍度探針](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes)
2. [等待容器生命周期過程完成](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#cloud-deployment-kubernetes-container-lifecycle)
3. [啟用正常關機](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-graceful-shutdown)
打開 `k8s/deployment.yaml` 在文本編輯器中,將就緒,活躍度和生命周期屬性添加到您的文件中:
k8s / 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/gs-spring-boot-k8s:snapshot
name: gs-spring-boot-k8s
resources: {}
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
status: {}
~~~
這照顧到最佳實踐1和2。
為了解決第三個最佳實踐,我們需要在我們的應用程序配置中添加一個屬性。 由于我們在Kubernetes上運行我們的應用程序,因此我們可以利用 [Kubernetes ConfigMaps](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) 來外部化此屬性,就像一個優秀的云開發人員應該的那樣。 現在,我們來看看如何做到這一點。
### 使用ConfigMap外部化配置
要在Spring Boot應用程序中啟用正常關機,我們需要設置 `server.shutdown=graceful`.
我們可以創建一個屬性文件,以啟用正常關閉并公開所有的Actuator端點。 我們可以使用Actuator端點作為一種驗證應用程序是否將ConfigMap中的屬性文件添加到PropertySources列表的方法。
創建一個新文件,名為 `application.properties` 在里面 `k8s`目錄。 在該文件中添加以下屬性。
application.properties
~~~
server.shutdown=graceful
management.endpoints.web.exposure.include=*
~~~
另外,您可以通過運行以下命令,從命令行通過一個簡單的步驟來執行此操作。
~~~
$ cat <<EOF >./k8s/application.properties
server.shutdown=graceful
management.endpoints.web.exposure.include=*
EOF
~~~
創建屬性文件后,我們現在可以 [創建ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-files) 使用kubectl 。
~~~
$ kubectl create configmap gs-spring-boot-k8s --from-file=./k8s/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作為卷安裝](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-volume) 在容器中。
為此,我們需要修改我們的部署YAML,以首先創建該卷,然后將該卷安裝在容器中:
k8s / 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/gs-spring-boot-k8s:snapshot
name: gs-spring-boot-k8s
resources: {}
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
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 ./k8s
~~~
如果正確配置了活動性和就緒性探針,則Pod將成功啟動并轉換為就緒狀態。 如果Pod從未達到就緒狀態,請返回并檢查您的就緒探針配置。 如果您的Pod達到就緒狀態,但是Kubernetes不斷重啟Pod,則您的活動探針配置不正確。 如果吊艙啟動并保持靜止,則一切正常。
您可以通過單擊來驗證是否已安裝ConfigMap卷以及應用程序正在使用屬性文件。 `/actuator/env` 端點。
~~~
$ kubectl port-forward svc/gs-spring-boot-k8s 9090:80
~~~
現在,如果您訪問 [http:// localhost:9090 / actuator / env,](http://localhost:9090/actuator/env) 您將看到屬性源是由我們的已裝載卷貢獻的。
~~~
{
"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` 命令。
### 服務發現和負載平衡
對于本指南的這一部分,您應該安裝 [Kustomize](https://kustomize.io/) 。 當使用Kubernetes并針對不同的環境(開發,測試,登臺,生產)時,Kustomize是一個有用的工具。 我們使用它來生成YAML,以將另一個應用程序部署到Kubernetes,然后我們將能夠使用服務發現來調用它。
運行以下命令以部署 的實例 [name-service](https://github.com/ryanjbaxter/k8s-spring-workshop/tree/master/name-service) :
~~~
$ kustomize build "github.com/ryanjbaxter/k8s-spring-workshop/name-service/kustomize/multi-replica/" | kubectl apply -f -
~~~
這應該部署 `name-service`到您的Kubernetes集群。 部署應為以下對象創建兩個副本 `name-service`:
~~~
$ kubectl get pods --selector app=k8s-workshop-name-service
NAME READY STATUS RESTARTS AGE
k8s-workshop-name-service-56b986b664-6qt59 1/1 Running 0 7m26s
k8s-workshop-name-service-56b986b664-wjcr9 1/1 Running 0 7m26s
~~~
為了演示該服務的作用,我們可以對其進行請求:
~~~
$ kubectl port-forward svc/k8s-workshop-name-service 9090:80
$ curl http://localhost:9090 -i; echo
HTTP/1.1 200
k8s-host: k8s-workshop-name-service-56b986b664-6qt59
Content-Type: text/plain;charset=UTF-8
Content-Length: 4
Date: Mon, 14 Sep 2020 15:37:51 GMT
Paul
~~~
如果發出多個請求,則應該看到返回的不同名稱。 另請注意標題: `k8s-host`。 這應該與為請求提供服務的廣告連播的ID保持一致。
使用port-forwarding命令時,它僅向單個pod發出一個請求,因此您只會在響應中看到一個主機。
確保停止 `port-forward` 在繼續之前先執行命令。
隨著我們的服務運行,我們可以修改我們的應用程序以向 `name-service`.
Kubernetes設置DNS條目,以便我們可以將服務ID用于 `name-service`在不知道Pod的IP地址的情況下向服務發出HTTP請求。 Kubernetes服務還在所有Pod之間平衡這些請求的負載。
在您的應用程序中,打開 `DemoApplication.java` 在 `src/main/java/com/example/demo`。 修改代碼,如下所示:
~~~
package com.example.demo;
import reactor.core.publisher.Mono;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
@SpringBootApplication
@RestController
public class DemoApplication {
private WebClient webClient = WebClient.create();
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping
public Mono<String> index() {
return webClient.get().uri("http://k8s-workshop-name-service")
.retrieve()
.toEntity(String.class)
.map(entity -> {
String host = entity.getHeaders().get("k8s-host").get(0);
return "Hello " + entity.getBody() + " from " + host;
});
}
}
~~~
請注意網址中的網址 `WebClient` 請求是 `k8s-workshop-name-service`。 這是我們在Kubernetes中服務的ID。
由于我們更新了應用程序代碼,因此我們需要構建一個新映像并將其部署到Kubernetes:
~~~
$ ./mvnw clean spring-boot:build-image -Dspring-boot.build-image.imageName=spring-k8s/gs-spring-boot-k8s
$ docker tag spring-k8s/gs-spring-boot-k8s:latest spring-k8s/gs-spring-boot-k8s:snapshot
$ kind load docker-image spring-k8s/gs-spring-boot-k8s:snapshot
~~~
部署新映像的一種簡單方法是刪除應用程序容器。 Kubernetes會使用我們剛剛加載到集群中的新映像自動創建另一個Pod。
~~~
$ kubectl delete pod --selector app=gs-spring-boot-k8s
~~~
新的Pod啟動并運行后,您可以將轉發請求移植到服務:
~~~
$ kubectl port-forward svc/gs-spring-boot-k8s 9090:80
~~~
現在,如果您對服務進行請求,則應該看到將請求發送到名稱服務的哪一個窗格:
~~~
$ curl http://localhost:9090; echo
Hello Paul from k8s-workshop-name-service-56b986b664-wjcr9
~~~
驗證負載平衡可能會更具挑戰性。 您可以不斷地發出相同的cURL請求,并觀察Pod ID是否更改。 諸如watch之類的工具可能對此非常有用:
~~~
$ watch -n 1 curl http://localhost:9090
~~~
watch命令每秒發出一次cURL請求。 缺點是您必須注意終端并等待。 最終,盡管如此,您應該注意到吊艙ID發生了變化。
查看事物切換的一種更快的方法是運行watch命令,然后刪除當前正在為請求提供服務的pod:
~~~
$ kubectl delete pod k8s-workshop-name-service-56b986b664-wjcr9
~~~
執行此操作時,您應該立即注意到watch命令中的pod ID發生了變化。
讓Kubernetes上運行的Spring Boot應用程序只需要訪問 [start.spring.io即可](https://start.spring.io) 。 Spring Boot的目標一直是盡可能簡化構建和運行Java應用程序的過程,無論您選擇如何運行應用程序,我們都會嘗試實現這一點。 用Kubernetes構建云原生應用程序只需要創建一個使用Spring Boot內置映像生成器的映像并利用Kubernetes平臺的功能即可。 在我們關于Spring Boot和Kubernetes的第二部分中,我們將探討Spring Cloud如何適合這個故事。
- springboot概述
- springboot構建restful服務
- spring構建一個RESTful Web服務
- spring定時任務
- 消費RESTful Web服務
- gradle構建項目
- maven構建項目
- springboot使用jdbc
- springboot應用上傳文件
- 使用LDNA驗證用戶
- 使用 spring data redis
- 使用 spring RabbitTemplate消息隊列
- 用no4j訪問nosql數據庫
- springboot驗證web表單
- Spring Boot Actuator構j建服務
- 使用jms傳遞消息
- springboot創建批處理服務
- spring security保護web 安全
- 在Pivotal GemFire中訪問數據
- 使用Spring Integration
- 使用springboot jpa進行數據庫操作
- 數據庫事務操作
- 操作mongodb
- springmvc+tymleaf創建web應用
- 將Spring Boot JAR應用程序轉換為WAR
- 創建異步服務
- spring提交表單
- 使用WebSocket構建交互式Web應用程序
- 使用REST訪問Neo4j數據
- jquery消費restful
- springboot跨域請求
- 消費SOAP Web服務
- springboot使用緩存
- 使用Vaadin創建CRUD UI
- 使用REST訪問JPA數據
- 使用REST訪問Pivotal GemFire中的數據
- 構建soap服務
- 使用rest訪問mongodb數據
- 構建springboot應用docker鏡像
- 從STS部署到Cloud Foundry
- springboot測試web應用
- springboot訪問mysql
- springboot編寫自定義模塊并使用
- 使用Google Cloud Pub / Sub進行消息傳遞
- 構建反應式RESTful Web服務
- 使用Redis主動訪問數據
- Spring Boot 部署到Kubernetes
- 使用反應式協議R2DBC訪問數據
- Spring Security架構
- spring構建Docker鏡像詳解
- Spring Boot和OAuth2
- springboot應用部署到k8s
- spring構建rest服務詳解