基于 CI/CD(Jenkins)将 Spring Boot 应用自动部署到 Kubernetes 集群 - 实践
项目背景
随着微服务架构在企业级应用中的广泛普及,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 发行版,适合测试环境快速部署。
[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) "指纹: 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 KubernetesLoaded: loaded (/etc/systemd/system/k3s.service; enabled; preset: disabled)Active: active (running) since Tue 2025-09-30 20:41:31 CST; 1min 33s agoDocs: https://k3s.ioProcess: 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: 142Memory: 829.9M (peak: 830.7M)CPU: 1min 5.303sCGroup: /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 <
[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
[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/tcpjenkins
[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
则配置正确。



(自行操作,之前博客配置过)
三、准备 Spring Boot 项目与 CI/CD 配置
1. 初始化 Git 仓库(以本地 Git 为例,也可使用 GitLab/GitHub)
[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
已初始化空的 Git 仓库于 /root/k8s-demo/.git/
[root@host1 k8s-demo]#
2. 添加项目文件
在 ~/k8s-demo
目录下,创建以下文件:
(1)Dockerfile
(构建 Spring Boot 镜像)
[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 项目配置)
[root@host1 k8s-demo]# vi pom.xml
[root@host1 k8s-demo]# cat pom.xml
4.0.0 com.abc spring-boot-hello 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.7.0 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin
(3)src/main/java/com/abc/hello/HelloController.java
(Spring Boot 源码)
[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)
[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: 1selector:matchLabels:app: spring-boot-hellotemplate:metadata:labels:app: spring-boot-hellospec:containers:- name: spring-boot-helloimage: registry.abc.com:5000/spring-boot-helloports:- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:name: sbdemo-svc
spec:type: NodePortselector:app: spring-boot-helloports:- port: 8080targetPort: 8080nodePort: 30008
(5)Jenkinsfile
(流水线配置:构建 + 推送镜像 + 远程部署)
[root@host1 k8s-demo]# vi Jenkinsfile
[root@host1 k8s-demo]# cat Jenkinsfile
pipeline {agent anytools {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 仓库
[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 demo5 files changed, 122 insertions(+)create mode 100644 Dockerfilecreate mode 100644 Jenkinsfilecreate mode 100644 kube.yamlcreate mode 100644 pom.xmlcreate mode 100644 src/main/java/com/abc/hello/HelloController.java
四、Jenkins 流水线项目创建与执行
- 进入 Jenkins → 新建任务 → 选择 流水线 → 命名为
k8s-demo
→ 确定。 - 在流水线配置区域,选择 从 SCM,SCM 选择
Git
,填写仓库 URL(即上一步的 Git 仓库地址),分支指定为 main
。 - 点击 保存,然后点击 立即构建,触发流水线。


五、验证部署结果(在 Host2 上执行)
1. 查看 Kubernetes 资源
# 查看 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 8080:30008/TCP 4m
2. 访问 Spring Boot 应用
[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 集群” 的全流程。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/925660.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!