# 将您的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) 。
![图像](https://github.com/spring-guides/top-spring-on-kubernetes/raw/master/extracted-media/media/image1.png)
[将您的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服务详解