项目背景
随着微服务架构在企业级应用中的广泛普及,Spring Boot 凭借其 "开箱即用、简化配置" 的特性,成为微服务开发的主流框架;同时,Kubernetes(简称 K8s)作为容器编排领域的标准平台,能高效解决容器化应用的部署、扩缩容、服务发现、故障自愈等问题,二者结合已成为微服务落地的核心技术方案。
然而,传统的 Spring Boot 应用部署模式(如手动打包 Jar 包、上传至服务器、手动配置容器、手动部署到 K8s 集群)存在显著痛点:
- 手动操作繁琐且易出错:从代码提交到最终部署需经历 "代码编译→单元测试→打包镜像→推送镜像→K8s 资源配置应用" 等多步操作,手动执行不仅效率低,还易因人为疏忽(如配置写错、镜像版本混淆)导致部署失败;
- 环境一致性难以保障:开发、测试、生产环境的依赖版本、配置参数若手动维护,易出现 "开发环境能运行,生产环境报错" 的 "环境不一致" 问题;
- 迭代效率低,无法适配快速业务需求:当业务迭代频繁(如每日多次版本更新)时,手动部署周期长(通常需数十分钟甚至数小时),难以满足 "快速验证、快速上线" 的业务需求;
- 缺乏流程管控与可追溯性:手动部署无统一的流程记录,若出现部署故障,难以快速定位是 "代码问题""构建问题" 还是 "部署配置问题",排查成本高。
为解决上述痛点,需构建一套自动化的 CI/CD(持续集成 / 持续部署)流程,而 Jenkins 作为开源领域最成熟的 CI/CD 工具,具备丰富的插件生态(如 Git 插件、Maven 插件、Docker 插件、Kubernetes 插件),可无缝串联 "代码管理→持续集成→镜像构建→持续部署至 K8s" 的全链路,因此本项目核心目标为:基于 Jenkins 搭建 CI/CD 流水线,实现 Spring Boot 应用从 "代码提交至 Git 仓库" 到 "自动部署至 K8s 集群运行" 的全流程自动化,消除手动操作,保障环境一致性,提升部署效率与稳定性,支撑业务快速迭代。
项目核心技术栈及角色:
- 代码管理:Git(存储 Spring Boot 应用代码及 K8s 资源配置文件);
- CI/CD 工具:Jenkins(编排流水线,触发代码拉取、Maven 构建、Docker 镜像打包 / 推送、K8s 资源部署);
- 应用框架:Spring Boot(微服务应用开发框架,提供业务功能);
- 容器化工具:Docker(将 Spring Boot 应用打包为标准化镜像,保障环境一致性);
- 容器编排平台:Kubernetes(管理 Docker 镜像的运行,实现应用的高可用、扩缩容、故障自愈)。
一、环境准备(Host1、Host2 与基础服务)
1. Host2:安装 Kubernetes(使用轻量版 k3s
)
K3s 是轻量级 Kubernetes 发行版,适合测试环境快速部署。
bash
[root@host2 ~]# curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \
INSTALL_K3S_MIRROR=cn sh -s - \
--system-default-registry "registry.cn-hangzhou.aliyuncs.com"
[INFO] Finding release for channel stable
[INFO] Using v1.33.4+k3s1 as release
[INFO] Downloading hash rancher-mirror.rancher.cn/k3s/v1.33.4-k3s1/sha256sum-amd64.txt
[INFO] Downloading binary rancher-mirror.rancher.cn/k3s/v1.33.4-k3s1/k3s
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Finding available k3s-selinux versions
[WARN] Failed to get available versions of k3s-selinux..defaulting to k3s-selinux-1.2-2.el9.noarch.rpm
Rancher K3s Common (stable) 582 B/s | 1.5 kB 00:02
上次元数据过期检查:0:00:01 前,执行于 2025年09月30日 星期二 20时38分05秒。
依赖关系解决。
=====================================================================================================
软件包 架构 版本 仓库 大小
=====================================================================================================
安装:
k3s-selinux noarch 1.6-1.el9 rancher-k3s-common-stable 22 k
事务概要
=====================================================================================================
安装 1 软件包
总下载:22 k
安装大小:96 k
下载软件包:
k3s-selinux-1.6-1.el9.noarch.rpm 24 kB/s | 22 kB 00:00
-----------------------------------------------------------------------------------------------------
总计 24 kB/s | 22 kB 00:00
Rancher K3s Common (stable) 2.6 kB/s | 2.4 kB 00:00
导入 GPG 公钥 0xE257814A:
Userid: "Rancher (CI) <ci@rancher.com>"
指纹: C8CF F216 4551 26E9 B9C9 18BE 925E A29A E257 814A
来自: https://rpm.rancher.io/public.key
导入公钥成功
运行事务检查
事务检查成功。
运行事务测试
事务测试成功。
运行事务
准备中 : 1/1
运行脚本: k3s-selinux-1.6-1.el9.noarch 1/1
安装 : k3s-selinux-1.6-1.el9.noarch 1/1
运行脚本: k3s-selinux-1.6-1.el9.noarch 1/1
验证 : k3s-selinux-1.6-1.el9.noarch 1/1
已安装:
k3s-selinux-1.6-1.el9.noarch
完毕!
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Creating /usr/local/bin/crictl symlink to k3s
[INFO] Creating /usr/local/bin/ctr symlink to k3s
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO] env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO] systemd: Creating service file /etc/systemd/system/k3s.service
[INFO] systemd: Enabling k3s unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.
[INFO] systemd: Starting k3s
[root@host2 ~]# systemctl status k3s -l
● k3s.service - Lightweight Kubernetes
Loaded: loaded (/etc/systemd/system/k3s.service; enabled; preset: disabled)
Active: active (running) since Tue 2025-09-30 20:41:31 CST; 1min 33s ago
Docs: https://k3s.io
Process: 1997940 ExecStartPre=/sbin/modprobe br_netfilter (code=exited, status=0/SUCCESS)
Process: 1997948 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
Main PID: 1997949 (k3s-server)
Tasks: 142
Memory: 829.9M (peak: 830.7M)
CPU: 1min 5.303s
CGroup: /system.slice/k3s.service
├─1997949 "/usr/local/bin/k3s server"
├─1999656 "containerd "
├─2006198 /var/lib/rancher/k3s/data/9ba85800b7128afafe1105efe9e7a1dac1fbb1c762c61fb2a99>
├─2006205 /var/lib/rancher/k3s/data/9ba85800b7128afafe1105efe9e7a1dac1fbb1c762c61fb2a99>
├─2006212 /var/lib/rancher/k3s/data/9ba85800b7128afafe1105efe9e7a1dac1fbb1c762c61fb2a99>
├─2006217 /var/lib/rancher/k3s/data/9ba85800b7128afafe1105efe9e7a1dac1fbb1c762c61fb2a99>
└─2006261 /var/lib/rancher/k3s/data/9ba85800b7128afafe1105efe9e7a1dac1fbb1c762c61fb2a99>
9月 30 20:42:37 host2 k3s[1997949]: I0930 20:42:37.913532 1997949 garbagecollector.go:787] "failed t>
9月 30 20:42:39 host2 k3s[1997949]: W0930 20:42:39.698023 1997949 handler_proxy.go:99] no RequestInf>
9月 30 20:42:39 host2 k3s[1997949]: E0930 20:42:39.698264 1997949 controller.go:113] "Unhandled Erro>
9月 30 20:42:39 host2 k3s[1997949]: I0930 20:42:39.699592 1997949 controller.go:126] OpenAPI Aggrega>
9月 30 20:42:39 host2 k3s[1997949]: W0930 20:42:39.699663 1997949 handler_proxy.go:99] no RequestInf>
9月 30 20:42:39 host2 k3s[1997949]: E0930 20:42:39.699709 1997949 controller.go:102] "Unhandled Erro>
9月 30 20:42:39 host2 k3s[1997949]: loading OpenAPI spec for "v1beta1.metrics.k8s.io" failed>
9月 30 20:42:39 host2 k3s[1997949]: , Header: map[Content-Type:[text/plain; charset=utf-8] X>
9月 30 20:42:39 host2 k3s[1997949]: >
9月 30 20:42:39 host2 k3s[1997949]: I0930 20:42:39.701348 1997949 controller.go:109] OpenAPI Aggrega>
[1]+ 已停止 systemctl status k3s -l
[root@host2 ~]# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-c8fbf7479-q9hsc 0/1 ContainerCreating 0 98s
kube-system helm-install-traefik-27njx 0/1 ContainerCreating 0 99s
kube-system helm-install-traefik-crd-d2k7r 0/1 ContainerCreating 0 99s
kube-system local-path-provisioner-65c47647b6-kl2z4 0/1 ContainerCreating 0 98s
kube-system metrics-server-64f5cd9f57-72pfx 0/1 ContainerCreating 0 98s
[root@host2 ~]# cat > /etc/rancher/k3s/registries.yaml <<EOF
mirrors:
docker.io:
endpoint:
- "http://hub-mirror.c.163.com"
- "https://docker.mirrors.ustc.edu.cn"
- "https://registry.docker-cn.com"
EOF
[root@host2 ~]# systemctl restart k3s
bash
[root@host2 ~]# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-c8fbf7479-q9hsc 1/1 Running 0 26m
kube-system helm-install-traefik-27njx 0/1 Completed 2 26m
kube-system helm-install-traefik-crd-d2k7r 0/1 Completed 0 26m
kube-system local-path-provisioner-65c47647b6-kl2z4 1/1 Running 0 26m
kube-system metrics-server-64f5cd9f57-72pfx 0/1 Running 0 26m
kube-system svclb-traefik-8819915c-9znxb 2/2 Running 0 19m
kube-system traefik-59465f6f5d-4jpsl 0/1 ImagePullBackOff 0 2m3s
kube-system traefik-856869ccc9-f72c7 1/1 Running 0 2m3s
[root@host2 ~]# sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
[root@host2 ~]# sudo chmod 644 ~/.kube/config
2. Host1:配置 hosts
与重启 Jenkins
bash
[root@host1 ~]# sudo vi /etc/hosts
[root@host1 ~]# sudo cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.197.91 k8s.abc.com
[root@host1 ~]# sudo docker restart jenkins
Error response from daemon: No such container: jenkins
[root@host1 ~]# docker ps -a | grep jenkins
[root@host1 ~]# docker images | grep jenkins
[root@host1 ~]# mkdir -p /root/jenkins_data
[root@host1 ~]# chmod 777 /root/jenkins_data
[root@host1 ~]# docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v /root/jenkins_data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
jenkins/jenkins:lts-jdk11
Unable to find image 'jenkins/jenkins:lts-jdk11' locally
lts-jdk11: Pulling from jenkins/jenkins
cdd62bf39133: Pull complete
21f106ffc421: Pull complete
39df2c5808cf: Pull complete
d9d5ad5daae2: Pull complete
21d9152ebad0: Pull complete
ddc06df74615: Pull complete
bf388b3d4868: Pull complete
1f6fc1ff002b: Pull complete
6201e887d163: Pull complete
a3d60a50862f: Pull complete
282c783b8e01: Pull complete
ddf03cc24103: Pull complete
Digest: sha256:6aa6c6bd7da914bf5333305c8102cb26965ea4b227e37f4269315725a2b0cd81
Status: Downloaded newer image for jenkins/jenkins:lts-jdk11
5ef61c95f7c6352d28edd517fad6f5d15af66de7a6c3bb730b8005b0228103ad
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint jenkins (3d819d291479573b7ac27597faef4e78a66782a1b52fd11d1bf6af0d684e3d44): Bind for 0.0.0.0:8080 failed: port is already allocated
Run 'docker run --help' for more information
[root@host1 ~]# docker ps | grep jenkins
[root@host1 ~]# docker ps | grep jenkins
[root@host1 ~]# ss -ltnp | grep 8080
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=3363,fd=7))
LISTEN 0 4096 [::]:8080 [::]:* users:(("docker-proxy",pid=3370,fd=7))
[root@host1 ~]# docker rm jenkins 2>/dev/null || true
jenkins
[root@host1 ~]# docker run -d \
--name jenkins \
-p 8081:8080 \
-p 50000:50000 \
-v /root/jenkins_data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
jenkins/jenkins:lts-jdk11
bd2ece777c615e3b37ea0ba09bca445d1f4439ac2ea6feda4882ad0ee85ae474
[root@host1 ~]# docker ps | grep jenkins
bd2ece777c61 jenkins/jenkins:lts-jdk11 "/usr/bin/tini -- /u..." 18 seconds ago Up 17 seconds 0.0.0.0:50000->50000/tcp, [::]:50000->50000/tcp, 0.0.0.0:8081->8080/tcp, [::]:8081->8080/tcp
jenkins
[root@host1 ~]# docker logs jenkins 2>&1 | grep "Initial password"
[root@host1 ~]# /var/jenkins_home/secrets/initialAdminPassword
-bash: /var/jenkins_home/secrets/initialAdminPassword: 没有那个文件或目录
[root@host1 ~]# cat /root/jenkins_data/secrets/initialAdminPassword
c6353040c7a9461e854c3c77aa0cb70d
[root@host1 ~]#
3. 搭建私有 Docker 镜像仓库(可选,若需本地存储镜像)
二、Jenkins 配置(在 Host1 的 Jenkins 页面操作)


1. 安装必要插件
- 进入 Jenkins → Manage Jenkins → Manage Plugins → Available。
- 搜索并安装:
Maven Integration
(Maven 项目支持)、Publish Over SSH
(SSH 远程部署)。
2. 配置 Maven
- 进入 Jenkins → Manage Jenkins → Global Tool Configuration。
- 找到 Maven 区域,点击 Add Maven ,命名为
Maven
,选择 "Install automatically" 或指定本地 Maven 路径。
3. 配置 Publish Over SSH(远程部署到 Host2)
- 进入 Jenkins → Manage Jenkins → Configure System。
- 找到 Publish over SSH 区域,点击 Add SSH Server ,配置如下:
- Name :
K8SHost
(与 Jenkinsfile 中configName
对应)。 - Hostname :
192.168.197.91
(Host2 的 IP)。 - Username :
root
(Host2 的登录用户)。 - 点击 Advanced ,勾选 Use password authentication, or use a different key ,在 Passphrase/Password 中输入 Host2 的
root
密码。 - 点击 Test Configuration ,显示
Success
则配置正确。
- Name :



(自行操作,之前博客配置过)
三、准备 Spring Boot 项目与 CI/CD 配置
1. 初始化 Git 仓库(以本地 Git 为例,也可使用 GitLab/GitHub)
bash
[root@host1 ~]# mkdir ~/k8s-demo && cd ~/k8s-demo
[root@host1 k8s-demo]# git init
bash: git: 未找到命令...
安装软件包"git-core"以提供命令"git"? [N/y] y
* 正在队列中等待...
* 正在载入软件包列表。...
下列软件包必须安装:
git-core-2.47.3-1.el9.x86_64Core package of git with minimal functionality
继续更改? [N/y] y
* 正在队列中等待...
* 正在等待认证...
* 正在队列中等待...
* 正在下载软件包...
* 正在请求数据...
* 正在测试更改...
* 正在安装软件包...
提示: 使用 'master' 作为初始分支的名称。这个默认分支名称可能会更改。要在新仓库中
提示: 配置使用初始分支名,并消除这条警告,请执行:
提示:
提示: git config --global init.defaultBranch <名称>
提示:
提示: 除了 'master' 之外,通常选定的名字有 'main'、'trunk' 和 'development'。
提示: 可以通过以下命令重命名刚创建的分支:
提示:
提示: git branch -m <name>
已初始化空的 Git 仓库于 /root/k8s-demo/.git/
[root@host1 k8s-demo]#
2. 添加项目文件
在 ~/k8s-demo
目录下,创建以下文件:
(1)Dockerfile
(构建 Spring Boot 镜像)
bash
[root@host1 k8s-demo]# vi Dockerfile
[root@host1 k8s-demo]# cat Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/spring-boot-hello-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
(2)pom.xml
(Maven 项目配置)
bash
[root@host1 k8s-demo]# vi pom.xml
[root@host1 k8s-demo]# cat pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.abc</groupId>
<artifactId>spring-boot-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3)src/main/java/com/abc/hello/HelloController.java
(Spring Boot 源码)
bash
[root@host1 k8s-demo]# vi src/main/java/com/abc/hello/HelloController.java
[1]+ 已停止 vi src/main/java/com/abc/hello/HelloController.java
[root@host1 k8s-demo]# mkdir -p src/main/java/com/abc/hello/
[root@host1 k8s-demo]# chmod -R u+w src/
[root@host1 k8s-demo]# vi src/main/java/com/abc/hello/HelloController.java
[root@host1 k8s-demo]# cat src/main/java/com/abc/hello/HelloController.java
package com.abc.hello;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Hello! Please test K8S CI/CD!\n";
}
}
[ro
(4)kube.yaml
(Kubernetes 资源配置:Deployment + Service)
bash
[root@host1 k8s-demo]# vi kube.yaml
[root@host1 k8s-demo]# cat kube.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sbdemo-deploy
spec:
replicas: 1
selector:
matchLabels:
app: spring-boot-hello
template:
metadata:
labels:
app: spring-boot-hello
spec:
containers:
- name: spring-boot-hello
image: registry.abc.com:5000/spring-boot-hello
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: sbdemo-svc
spec:
type: NodePort
selector:
app: spring-boot-hello
ports:
- port: 8080
targetPort: 8080
nodePort: 30008
(5)Jenkinsfile
(流水线配置:构建 + 推送镜像 + 远程部署)
bash
[root@host1 k8s-demo]# vi Jenkinsfile
[root@host1 k8s-demo]# cat Jenkinsfile
pipeline {
agent any
tools {
maven 'Maven' // 对应 Jenkins 中配置的 Maven 名称
}
stages {
stage('Build') {
steps {
// 1. Maven 打包
sh 'mvn -B -DskipTests clean package'
// 2. 构建 Docker 镜像
sh 'docker build -t registry.abc.com:5000/spring-boot-hello .'
// 3. 推送镜像到私有仓库
sh 'docker push registry.abc.com:5000/spring-boot-hello'
}
}
stage('Deploy') {
steps {
// 通过 SSH 向 Host2 传输 kube.yaml 并执行部署命令
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'K8SHost', // 对应 Publish Over SSH 中配置的名称
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: 'cd /root/spring-boot-hello && kubectl delete -f kube.yaml || true && kubectl apply -f kube.yaml',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: '/root/spring-boot-hello',
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: '**/kube.yaml'
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: false
)
])
}
}
}
}
3. 提交代码到 Git 仓库
bash
[root@host1 k8s-demo]# git add .
[root@host1 k8s-demo]# git commit -m "Init K8S CI/CD demo"
作者身份未知
*** 请告诉我您是谁。
运行
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
来设置您账号的缺省身份标识。
如果仅在本仓库设置身份标识,则省略 --global 参数。
致命错误:无法自动探测邮件地址(得到 'root@host1.(none)')
[root@host1 k8s-demo]# git config --global user.name "root"
[root@host1 k8s-demo]# git config --global user.email "2243678135@qq.com"
[root@host1 k8s-demo]# git commit -m "Init K8S CI/CD demo"
[master(根提交) 1390477] Init K8S CI/CD demo
5 files changed, 122 insertions(+)
create mode 100644 Dockerfile
create mode 100644 Jenkinsfile
create mode 100644 kube.yaml
create mode 100644 pom.xml
create mode 100644 src/main/java/com/abc/hello/HelloController.java
四、Jenkins 流水线项目创建与执行
- 进入 Jenkins → 新建任务 → 选择 流水线 → 命名为
k8s-demo
→ 确定。 - 在流水线 配置区域,选择 从 SCM ,SCM 选择
Git
,填写仓库 URL (即上一步的 Git 仓库地址),分支指定 为main
。 - 点击 保存 ,然后点击 立即构建,触发流水线。


五、验证部署结果(在 Host2 上执行)
1. 查看 Kubernetes 资源
bash
# 查看 Pod(需显示 Running)
[root@host2 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-app-pod-7b5d9f8d46-xxxx 1/1 Running 0 2m
# 查看 Deployment(需显示 AVAILABLE 1/1)
[root@host2 ~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
my-app-deploy 1/1 1 1 3m
# 查看 Service(需显示 NodePort 为 30008)
[root@host2 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app-svc NodePort 10.96.xxx.xxx <none> 8080:30008/TCP 4m
2. 访问 Spring Boot 应用
bash
[root@host2 ~]# curl http://localhost:30008
Hello! Please test K8S CI/CD!
关键注意事项
- 私有仓库访问 :确保 Host2 的 Docker 已配置
insecure-registries
,能拉取私有仓库镜像。- SSH 权限 :Host1 通过 Jenkins 的 Publish Over SSH 能免密(或密码)登录 Host2 的
root
用户。- K8S 网络:NodePort 服务需确保端口(如 30008)未被占用,且防火墙允许访问。
按上述步骤执行后,即可完成 "Spring Boot 应用通过 Jenkins 自动部署到 Kubernetes 集群" 的全流程。