java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Boot  K8S 打包、部署与运维发布

Spring Boot 项目在 K8S 中的打包、部署与运维发布实践指南

作者:@吴涵

文章介绍了运维工程师掌握SpringBoot+Docker+K8S+JVM的基础知识和发布流程,包括Java项目的交付物、SpringBoot构建流程、Jenkins流水线发布、JVM配置和优化等,帮助运维理解从源码到上线的完整交付视角,提升发布成功率和故障排查能力,感兴趣的朋友一起看看吧

面向运维工程师,从基础概念到流水线发布、再到上线保障与故障排查,逐步建立对 Spring Boot + Docker + K8S + JVM 的完整认知。

一、为什么运维要掌握 Spring Boot 项目的发布链路

1. 本文目标

2. 适合谁看

3. 运维最容易踩的坑

 什么是 Java 项目的“交付物”

jar 包的典型架构:

myapp.jar
├── META-INF/
│   └── MANIFEST.MF       # 包含Main-Class和Class-Path配置
├── BOOT-INF/
│   ├── classes/           # 业务代码.class文件
│   └── lib/               # 所有依赖Jar包
└── org/springframework/boot/loader/  # Spring Boot启动器

war 包典型架构:

myapp.war
├── WEB-INF/
│   ├── web.xml            # Web应用配置文件(传统项目必需)
│   ├── classes/           # 业务代码.class文件
│   └── lib/               # Web应用依赖Jar包
├── META-INF/
│   └── MANIFEST.MF
├── index.jsp              # JSP页面
├── static/                # 静态资源(CSS、JS、图片)
└── templates/             # 模板文件(如Thymeleaf、FreeMarker)

什么是Spring Boot

Spring Boot 通过自动配置机制完成 Tomcat 的初始化和启动。

4. 什么是Maven

maven 是 Java 项目的自动化构建和依赖管理工具。
你可以把它理解成 Java 项目的管家:自动帮你下载所有 jar 包,自动帮你编译、打包、运行、测试,统一管理项目结构、版本、依赖。
一句话:不用手动找 jar、手动导包、手动配置,Maven 全部自动化搞定。
pom.xml 是 Maven 项目的核心配置文件,使用哪些依赖、如何打包、使用什么插件,都在这里定义。

二、Spring Boot 项目的打包发布

springboot 项目在前面已经介绍过了,通过 Java 就可以启动构建好的 jar 包。
通常会先通过 Docker 镜像进行打包,然后再到集群中部署。
下面详细介绍一下 Spring Boot 项目的打包构建流程。

三、Jenkins 流水线发布流程

现在使用的流水线打包配置,基本都通过 Jenkins 来实现,而 Jenkins 中的打包过程主要有以下几步:

在当前的流水线平台架构上,通过定义好的 Jenkinsfile 来指导 Jenkins 的构建流程。这里就构建、打包以及发布的过程进行梳理:

  1. Jenkins 拉取配置仓,将提前定义好的部署文件、Dockerfile 等文件放到工作目录。
  2. Jenkins 根据填写的仓库地址拉取项目源码。
  3. Jenkins 中进行编译检查,使用 mvn complie
  4. 触发 Docker 构建。Docker 构建一般可以分为两种:先构建打包镜像产物,再构建运行镜像;或者全部放到 Docker 构建中做多阶段构建。

两种方式各有优劣。为了更好地管理流水线、减少磁盘空间占用,线上采用第二种方式,将打包和构建都放到一个 Dockerfile 中执行。

示例 Dockerfile

FROM maven:3.3.9 as BUILD
#构建构建镜像且命名为build
COPY . /usr/app/
RUN cd /usr/app; mvn clean package -Dmaven.test.skip=true
## 运行打包命令
FROM your-jdk-runtime:8
#导入运行镜像
COPY --from=BUILD /usr/app/your-app/target/your-app.jar /usr/local/apps/
## 关键,将从前一个名为build的构建阶段容器的文件系统里面,把文件复制到当前这个运行阶段的镜像里面。,这就是将构建和运行分开。
ENV  APP_BASE /usr/local/apps/
WORKDIR /usr/local/apps/
RUN ping -c 4 gitlab.example.com && \
    yum install -y git && mkdir -p /tmp/gitfile && \
    cd /tmp/gitfile && git init && \
    git remote add origin -f gitlab.example.com/example-group/shell.git  && \
    echo springboot/start.sh >> .git/info/sparse-checkout && \
    git config core.sparsecheckout true && \
    git pull origin master && chmod +x /tmp/gitfile/springboot/start.sh && \
    mv /tmp/gitfile/springboot/start.sh /usr/local/apps/
## 这里是做一些见检查,然后拉取一个启动脚本,启动脚本中定义的一些环境变量的相关信息。
EXPOSE 8080
## springboot的主业务端口
EXPOSE 10090
## 管理端口
CMD ["./start.sh", "your-app.jar"]
#通过脚本去启动jar包

从上面的 Dockerfile 中可以看到,Jenkins 没有在宿主机上直接执行 mvn 打包命令。Jenkins 负责触发 docker build,而实际的打包构建是在 docker build 阶段执行的 mvn clean package,也就是 jar 包是在容器构建中生成的。

可以看出,在打包构建阶段引用的是 maven:3.3.9,然后执行的打包命令是 mvn clean package -Dmaven.test.skip=true
打包完成之后,再把 jar 拷贝到运行镜像目录中,构建出最终运行镜像。

  1. 在运行镜像构建完成之后,Jenkins 会生成一个带时间戳、commit id、随机串的 tag,然后推送到 Harbor,再进入部署阶段。
  2. 部署阶段,Jenkins 通过选择指定的 K8S 环境,调用 deployment.yaml,替换 YAML 中的镜像名为构建完成的运行镜像,然后执行发布操作。

四、流水线环节中容易失败的地方

五、运维如何提升发布成功率

六、运维必须掌握的 JVM 基础与参数设置

1. 为什么运维要懂JVM

java 服务的所有代码都运行在 JVM 上,JVM 的行为直接决定了服务的性能和稳定性。所以 JVM 的配置很重要,关联业务代码是否能够稳定运行。

运维侧必须掌握的 JVM 技能:

  1. 能够看懂 GC 日志,识别 GC 频繁、GC 停顿过长。
  2. 会用 jstack 抓取线程栈,定位死锁、CPU 高的线程。
  3. 会用 jmap 抓取堆 dump,分析内存泄漏。
  4. 会配置基本的 JVM 参数(堆大小、GC 收集器)。

容器内存限制与 JVM 堆配置强相关。
因为容器的规格限制与 JVM 的限制相关,JVM 配置不能大于容器规格限制。JVM 默认运行在容器里面,而集群部署对于容器规格是有限制的。
例如在集群中通过 resources.limits.memory=2G 限制了容器占用内存的大小,如果 JVM 占用大于 2G,那么容器会被识别为超出限制并直接重启。这时候会触发 OOM,Pod 会被强制重启。
而在 Pod 日志中,仅能看到退出原因为 OOMKilled
在 Java 11+ 版本中,自带容器感知能力,默认使用容器内存的 25% 作为堆大小。
所以在 JVM 示例中,通常需要显式指定 JVM 大小。

例如:

env:
- name: JAVA_OPTS
  value: "-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=75.0"
resources:
  limits:
    memory: "2Gi"

常见问题是服务部署发布完成后,运行一段时间 Pod 又会被重启,服务偶尔出现异常中断。
JVM 的问题大多是累积形式:1. 内存泄漏;2. GC 问题;3. 线程问题;4. 大对象问题等。
所以在发布后,不能只关注 Pod 是否 Running,还需要关注 Pod 的内存使用情况、GC 次数等指标。

介绍一下 GC:
GC 是垃圾回收机制,是 JVM 自带的自动内存管理机制。

你可以把 JVM 堆内存想象成一个仓库:

为什么 GC 会搞崩你的服务:
因为 GC 在工作时,会暂停所有业务线程。这个暂停就是 STW,是很多 Java 服务卡顿的根源。

GC 的类型有 Young GCFull GC。其中 Young GC 频繁问题不大,只要不耗时太长;Full GC 是更危险的信号,只要 Full GC 超过 1 次 / 分钟,或者单次超过 1 秒,服务通常就会出问题。

而在容器 Pod 中,查看 GC 日志和 GC 指标,才能定位问题。

方式 1:进入 Pod 直接查看 GC 日志(前提是 GC 日志开启了打印和收集)。

常见的位置:

# 最常见:和 app.jar 同目录
ls -l /app/gc*.log
# 有些项目会放在 logs 目录
ls -l /app/logs/gc*.log
# 找不到就全局搜
find / -name "gc*.log" 2>/dev/null

示例:

# Full GC 日志(重点看这行)
2024-05-20T10:30:00.123+08:00: [Full GC (System.gc()) 1500M->800M(2048M), 2.5s]

从这条记录中可以看到,发生了 Full GC,发生前使用了 1500M,GC 后使用了 800M。
这次 GC 的总耗时时间是 2.5s。

抓取堆栈 dump 和线程栈:
当发现 Full GC 频繁、内存一直上涨时,就要怀疑是内存泄漏,此时要抓取堆 dump(内存快照)来进行分析。

抓取方式:

# 进入 Pod
kubectl exec -it <pod-name> -- /bin/bash
# 找到 Java 进程 ID(一般是 1,因为容器里只有一个进程)
jps
# 抓取堆 dump(会生成一个 hprof 文件)
jmap -dump:format=b,file=/app/heapdump.hprof 1
# 从 Pod 复制到本地
kubectl cp <pod-name>:/app/heapdump.hprof ./heapdump.hprof

使用工具分析 dump 文件:

Pod 内存使用率高不等于一定有问题。

举个例子:

这完全正常,因为 JVM 会把内存用满,然后触发 GC 回收。只要 GC 能回收,内存使用率就会降下来。

真正有问题的是:

运维排查 GC 问题标准流程:

  1. 发现问题:Grafana 告警 Full GC 频繁、接口超时或 Pod 内存使用率高。
  2. 初步判断:kubectl logs <pod-name> | grep "Full GC",看 Full GC 次数和耗时。
  3. 深入分析:进入 Pod 看完整 GC 日志,看 GC 前后内存变化。
  4. 抓取证据:如果怀疑内存泄漏,抓取堆 dump。
  5. 临时解决:重启 Pod(能暂时缓解,但不能根治)。
  6. 根治问题:分析 dump 文件,找到泄漏点,让开发修复。

到此这篇关于Spring Boot 项目在 K8S 中的打包、部署与运维发布实践指南的文章就介绍到这了,更多相关Spring Boot K8S 打包、部署与运维发布内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文