Spring?Boot?項目在?K8S?中的打包、部署與運維發(fā)布實踐指南
面向運維工程師,從基礎(chǔ)概念到流水線發(fā)布、再到上線保障與故障排查,逐步建立對
Spring Boot + Docker + K8S + JVM的完整認(rèn)知。
一、為什么運維要掌握 Spring Boot 項目的發(fā)布鏈路
1. 本文目標(biāo)
- 說明運維為什么不能只會
kubectl apply - 建立從源碼到上線的完整交付視角
- 幫助運維理解
Spring Boot項目為什么能以jar形式運行 - 幫助運維提升
K8S流水線發(fā)布成功率
2. 適合誰看
- 正在接觸 Java 項目交付的運維工程師
- 想搞懂
jar、war、Tomcat、Maven關(guān)系的人 - 想系統(tǒng)學(xué)習(xí)
Spring Boot在K8S中部署流程的人
3. 運維最容易踩的坑
- 只會改鏡像 tag,不理解交付物類型
- 不清楚
jar和war的運行環(huán)境差異 - 不理解
JVM與容器內(nèi)存限制的關(guān)系 - 不會判斷到底是構(gòu)建失敗、鏡像失敗,還是
K8S啟動失敗
什么是 Java 項目的“交付物”
- 什么是源碼:
- 源碼就是源代碼。
- 什么是編譯產(chǎn)物:
- 編譯產(chǎn)物就是源碼經(jīng)過編譯處理之后的交付結(jié)果,比如 Java 的源碼經(jīng)過 Maven 處理后,會生成
.class文件、jar包、war包。
- 什么是
jarjar是Java Archive,是 Java 的一種打包格式,本質(zhì)上是基于 zip 格式的壓縮包,專門用于打包 Java 類文件、資源文件和元數(shù)據(jù)。- 里面會放置編譯后的一些文件:編譯后的
.class文件、依賴包、配置文件、資源文件。 jar包主要分為普通jar包和可執(zhí)行jar包。- 普通
jar包僅包含編譯后的.class文件和資源,無外部依賴,不能直接運行;而可運行jar中通常會把依賴和內(nèi)嵌的 Web 容器一起打包進去,不需要外置 Tomcat,可以通過java -jar直接執(zhí)行。Spring Boot 應(yīng)用就屬于這種一鍵部署方式。
- 什么是
warwar包是Web Application Archive,是 Java Web 應(yīng)用的打包格式,同樣基于 zip 壓縮,專門用于打包 Web 應(yīng)用資源(servlet、jsp、靜態(tài)資源等)。與jar相比,war僅適用于 Java Web 應(yīng)用,結(jié)構(gòu)固定(需包含WEB-INF/、WEB-INF/classes/、WEB-INF/lib/等),必須部署到 Servlet 容器(如 Tomcat)中,由容器負(fù)責(zé)加載、初始化和調(diào)度。- 傳統(tǒng)的 Java Web 應(yīng)用(如 Spring MVC)常使用這種方式。
jar 包的典型架構(gòu):
myapp.jar ├── META-INF/ │ └── MANIFEST.MF # 包含Main-Class和Class-Path配置 ├── BOOT-INF/ │ ├── classes/ # 業(yè)務(wù)代碼.class文件 │ └── lib/ # 所有依賴Jar包 └── org/springframework/boot/loader/ # Spring Boot啟動器
war 包典型架構(gòu):
myapp.war ├── WEB-INF/ │ ├── web.xml # Web應(yīng)用配置文件(傳統(tǒng)項目必需) │ ├── classes/ # 業(yè)務(wù)代碼.class文件 │ └── lib/ # Web應(yīng)用依賴Jar包 ├── META-INF/ │ └── MANIFEST.MF ├── index.jsp # JSP頁面 ├── static/ # 靜態(tài)資源(CSS、JS、圖片) └── templates/ # 模板文件(如Thymeleaf、FreeMarker)
什么是Spring Boot
Spring Boot解決了什么問題- springboot 的核心目標(biāo)主要是為了簡化 spring 應(yīng)用的初始化搭建和開發(fā)過程。主要解決傳統(tǒng) spring 開發(fā)配置繁瑣、依賴管理復(fù)雜、部署麻煩、監(jiān)控缺失等問題,讓開發(fā)者能夠更快上手,專注于業(yè)務(wù)邏輯開發(fā)。
- 為什么它常見產(chǎn)物是可執(zhí)行
jar - 主要是因為 Spring Boot 構(gòu)建出的
jar包能夠獨立運行、簡化部署,符合微服務(wù)架構(gòu)需求,保證服務(wù)獨立部署運行,不需要外部環(huán)境依賴。適合通過 Docker 等容器化方式進行啟動。運維側(cè)也更加方便,更新回滾時僅替換文件即可。 - 內(nèi)嵌
Tomcat的基本原理 - springboot 內(nèi)嵌 tomcat 的核心是將 tomcat 作為普通的 Java 對象運行在應(yīng)用中,而非獨立的外部進程。
- 當(dāng)引入
spring-boot-starter-web時,會自動傳遞引入 Tomcat 內(nèi)嵌依賴: tomcat-embed-core是 Tomcat 核心引擎,tomcat-embed-el是表達式語言支持,tomcat-embed-websocket是 WebSocket 支持(可選)。
Spring Boot 通過自動配置機制完成 Tomcat 的初始化和啟動。
4. 什么是Maven
maven 是 Java 項目的自動化構(gòu)建和依賴管理工具。
你可以把它理解成 Java 項目的管家:自動幫你下載所有 jar 包,自動幫你編譯、打包、運行、測試,統(tǒng)一管理項目結(jié)構(gòu)、版本、依賴。
一句話:不用手動找 jar、手動導(dǎo)包、手動配置,Maven 全部自動化搞定。pom.xml 是 Maven 項目的核心配置文件,使用哪些依賴、如何打包、使用什么插件,都在這里定義。
二、Spring Boot 項目的打包發(fā)布
springboot 項目在前面已經(jīng)介紹過了,通過 Java 就可以啟動構(gòu)建好的 jar 包。
通常會先通過 Docker 鏡像進行打包,然后再到集群中部署。
下面詳細(xì)介紹一下 Spring Boot 項目的打包構(gòu)建流程。
三、Jenkins 流水線發(fā)布流程
現(xiàn)在使用的流水線打包配置,基本都通過 Jenkins 來實現(xiàn),而 Jenkins 中的打包過程主要有以下幾步:
在當(dāng)前的流水線平臺架構(gòu)上,通過定義好的 Jenkinsfile 來指導(dǎo) Jenkins 的構(gòu)建流程。這里就構(gòu)建、打包以及發(fā)布的過程進行梳理:
- Jenkins 拉取配置倉,將提前定義好的部署文件、
Dockerfile等文件放到工作目錄。 - Jenkins 根據(jù)填寫的倉庫地址拉取項目源碼。
- Jenkins 中進行編譯檢查,使用
mvn complie。 - 觸發(fā) Docker 構(gòu)建。Docker 構(gòu)建一般可以分為兩種:先構(gòu)建打包鏡像產(chǎn)物,再構(gòu)建運行鏡像;或者全部放到 Docker 構(gòu)建中做多階段構(gòu)建。
兩種方式各有優(yōu)劣。為了更好地管理流水線、減少磁盤空間占用,線上采用第二種方式,將打包和構(gòu)建都放到一個 Dockerfile 中執(zhí)行。
示例 Dockerfile:
FROM maven:3.3.9 as BUILD
#構(gòu)建構(gòu)建鏡像且命名為build
COPY . /usr/app/
RUN cd /usr/app; mvn clean package -Dmaven.test.skip=true
## 運行打包命令
FROM your-jdk-runtime:8
#導(dǎo)入運行鏡像
COPY --from=BUILD /usr/app/your-app/target/your-app.jar /usr/local/apps/
## 關(guān)鍵,將從前一個名為build的構(gòu)建階段容器的文件系統(tǒng)里面,把文件復(fù)制到當(dāng)前這個運行階段的鏡像里面。,這就是將構(gòu)建和運行分開。
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/
## 這里是做一些見檢查,然后拉取一個啟動腳本,啟動腳本中定義的一些環(huán)境變量的相關(guān)信息。
EXPOSE 8080
## springboot的主業(yè)務(wù)端口
EXPOSE 10090
## 管理端口
CMD ["./start.sh", "your-app.jar"]
#通過腳本去啟動jar包從上面的 Dockerfile 中可以看到,Jenkins 沒有在宿主機上直接執(zhí)行 mvn 打包命令。Jenkins 負(fù)責(zé)觸發(fā) docker build,而實際的打包構(gòu)建是在 docker build 階段執(zhí)行的 mvn clean package,也就是 jar 包是在容器構(gòu)建中生成的。
可以看出,在打包構(gòu)建階段引用的是 maven:3.3.9,然后執(zhí)行的打包命令是 mvn clean package -Dmaven.test.skip=true。
打包完成之后,再把 jar 拷貝到運行鏡像目錄中,構(gòu)建出最終運行鏡像。
- 在運行鏡像構(gòu)建完成之后,Jenkins 會生成一個帶時間戳、
commit id、隨機串的 tag,然后推送到 Harbor,再進入部署階段。 - 部署階段,Jenkins 通過選擇指定的 K8S 環(huán)境,調(diào)用
deployment.yaml,替換 YAML 中的鏡像名為構(gòu)建完成的運行鏡像,然后執(zhí)行發(fā)布操作。
四、流水線環(huán)節(jié)中容易失敗的地方
Maven依賴下載失?。ㄋ椒?Nexus/Artifactory不可用、認(rèn)證過期、倉庫地址變更、網(wǎng)絡(luò)超時)- 源碼拉取失?。?code>Git 憑證失效、分支/tag 不存在、子模塊或大倉超時)
Maven編譯/測試失?。ùa沖突、本地與流水線pom不一致、跳測參數(shù)與質(zhì)量門禁不匹配)- 構(gòu)建環(huán)境
JDK/Maven版本與本地或Dockerfile基鏡像不一致 Dockerfile多階段構(gòu)建失?。窂綄戝e、COPY --from階段名錯誤、構(gòu)建階段內(nèi)存不足)- 鏡像構(gòu)建失?。ɑA(chǔ)鏡像拉取失敗、
RUN命令非 0 退出、磁盤空間滿) - 鏡像推送失敗(
Harbor登錄過期、項目配額滿、網(wǎng)絡(luò)或 TLS 問題) K8S拉鏡像失?。?code>imagePullSecrets 缺失、tag 未推上去、鏡像名與部署 YAML 不一致)Pod啟動失?。?code>CrashLoopBackOff、配置/密鑰缺失、JVM堆大于容器內(nèi)存限制)- 探針失?。?code>readiness/liveness 路徑或端口與真實監(jiān)聽不一致、初始延遲過短、依賴未就緒)
- 發(fā)布階段失?。?code>YAML 語法錯誤、資源配額不足、
RBAC無權(quán)限、Deployment與HPA/PDB沖突)
五、運維如何提升發(fā)布成功率
- 固化構(gòu)建基線:流水線與
Dockerfile使用固定版本的JDK、Maven、基礎(chǔ)鏡像;重大升級走單獨變更,避免「同一套流水線突然換版本」。 - 依賴與制品可復(fù)現(xiàn):私服高可用、憑證輪換有流程;必要時對關(guān)鍵依賴做緩存層或構(gòu)建節(jié)點本地
.m2緩存策略,減少外網(wǎng)抖動影響。 - 鏡像與部署聯(lián)動:
tag規(guī)則統(tǒng)一(時間戳 +commit id);部署前校驗鏡像在倉庫中可拉取;imagePullPolicy與回滾策略和團隊約定一致。 - 資源與 JVM 對齊:為容器設(shè)置合理
requests/limits,JVM-Xmx等明顯小于容器內(nèi)存上限,避免OOMKilled;大構(gòu)建任務(wù)單獨調(diào)高構(gòu)建 Pod/節(jié)點的內(nèi)存與超時。 - 探針與啟動順序:與研發(fā)確認(rèn)健康檢查 URL、端口、依賴就緒時間;適當(dāng)調(diào)大
initialDelaySeconds,區(qū)分liveness與readiness語義,避免誤殺仍在啟動的進程。 - 配置與密鑰:
ConfigMap/Secret變更納入發(fā)布 checklist;避免「只改鏡像不改配置」導(dǎo)致啟動即失敗;密鑰輪換后同步更新K8S與流水線憑據(jù)。 - 可觀測與快速回滾:發(fā)布前后看構(gòu)建日志、事件
kubectl describe、Pod日志;保留上一版可用鏡像 tag,出問題優(yōu)先rollout undo或改回舊 tag。 - 分環(huán)境與灰度:測試/預(yù)發(fā)與生產(chǎn)隔離;生產(chǎn)盡量金絲雀或分批發(fā)布,降低單次失敗影響面。
六、運維必須掌握的 JVM 基礎(chǔ)與參數(shù)設(shè)置
1. 為什么運維要懂JVM
- Java 服務(wù)性能和穩(wěn)定性直接受
JVM影響 - 容器內(nèi)存限制與
JVM堆配置強相關(guān) - 發(fā)布成功不代表運行穩(wěn)定
java 服務(wù)的所有代碼都運行在 JVM 上,JVM 的行為直接決定了服務(wù)的性能和穩(wěn)定性。所以 JVM 的配置很重要,關(guān)聯(lián)業(yè)務(wù)代碼是否能夠穩(wěn)定運行。
運維側(cè)必須掌握的 JVM 技能:
- 能夠看懂 GC 日志,識別 GC 頻繁、GC 停頓過長。
- 會用
jstack抓取線程棧,定位死鎖、CPU 高的線程。 - 會用
jmap抓取堆 dump,分析內(nèi)存泄漏。 - 會配置基本的 JVM 參數(shù)(堆大小、GC 收集器)。
容器內(nèi)存限制與 JVM 堆配置強相關(guān)。
因為容器的規(guī)格限制與 JVM 的限制相關(guān),JVM 配置不能大于容器規(guī)格限制。JVM 默認(rèn)運行在容器里面,而集群部署對于容器規(guī)格是有限制的。
例如在集群中通過 resources.limits.memory=2G 限制了容器占用內(nèi)存的大小,如果 JVM 占用大于 2G,那么容器會被識別為超出限制并直接重啟。這時候會觸發(fā) OOM,Pod 會被強制重啟。
而在 Pod 日志中,僅能看到退出原因為 OOMKilled。
在 Java 11+ 版本中,自帶容器感知能力,默認(rèn)使用容器內(nèi)存的 25% 作為堆大小。
所以在 JVM 示例中,通常需要顯式指定 JVM 大小。
例如:
env:
- name: JAVA_OPTS
value: "-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=75.0"
resources:
limits:
memory: "2Gi"
常見問題是服務(wù)部署發(fā)布完成后,運行一段時間 Pod 又會被重啟,服務(wù)偶爾出現(xiàn)異常中斷。
JVM 的問題大多是累積形式:1. 內(nèi)存泄漏;2. GC 問題;3. 線程問題;4. 大對象問題等。
所以在發(fā)布后,不能只關(guān)注 Pod 是否 Running,還需要關(guān)注 Pod 的內(nèi)存使用情況、GC 次數(shù)等指標(biāo)。
介紹一下 GC:
GC 是垃圾回收機制,是 JVM 自帶的自動內(nèi)存管理機制。
你可以把 JVM 堆內(nèi)存想象成一個倉庫:
- 你的 Java 代碼運行時,會不斷創(chuàng)建新對象(往倉庫里放東西)
- 有些對象用完就沒用了(變成垃圾)
- GC 就是倉庫的清潔工,自動把沒用的垃圾清走,騰出空間
為什么 GC 會搞崩你的服務(wù):
因為 GC 在工作時,會暫停所有業(yè)務(wù)線程。這個暫停就是 STW,是很多 Java 服務(wù)卡頓的根源。
GC 的類型有 Young GC 和 Full GC。其中 Young GC 頻繁問題不大,只要不耗時太長;Full GC 是更危險的信號,只要 Full GC 超過 1 次 / 分鐘,或者單次超過 1 秒,服務(wù)通常就會出問題。
而在容器 Pod 中,查看 GC 日志和 GC 指標(biāo),才能定位問題。
方式 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]
從這條記錄中可以看到,發(fā)生了 Full GC,發(fā)生前使用了 1500M,GC 后使用了 800M。
這次 GC 的總耗時時間是 2.5s。
抓取堆棧 dump 和線程棧:
當(dāng)發(fā)現(xiàn) Full GC 頻繁、內(nèi)存一直上漲時,就要懷疑是內(nèi)存泄漏,此時要抓取堆 dump(內(nèi)存快照)來進行分析。
抓取方式:
# 進入 Pod kubectl exec -it <pod-name> -- /bin/bash # 找到 Java 進程 ID(一般是 1,因為容器里只有一個進程) jps # 抓取堆 dump(會生成一個 hprof 文件) jmap -dump:format=b,file=/app/heapdump.hprof 1 # 從 Pod 復(fù)制到本地 kubectl cp <pod-name>:/app/heapdump.hprof ./heapdump.hprof
使用工具分析 dump 文件:
Eclipse MAT:最常用的內(nèi)存分析工具JProfiler:功能更強大的商業(yè)工具
Pod 內(nèi)存使用率高不等于一定有問題。
舉個例子:
- 你給 Pod 限制了 2G 內(nèi)存
- JVM 堆配置了 1.5G
- 運行一段時間后,Pod 內(nèi)存使用率到了 80%(1.6G)
這完全正常,因為 JVM 會把內(nèi)存用滿,然后觸發(fā) GC 回收。只要 GC 能回收,內(nèi)存使用率就會降下來。
真正有問題的是:
- GC 后內(nèi)存使用率還是 80% 以上
- Full GC 越來越頻繁
- 內(nèi)存使用率一直漲,直到 OOM
運維排查 GC 問題標(biāo)準(zhǔn)流程:
- 發(fā)現(xiàn)問題:Grafana 告警
Full GC頻繁、接口超時或 Pod 內(nèi)存使用率高。 - 初步判斷:
kubectl logs <pod-name> | grep "Full GC",看 Full GC 次數(shù)和耗時。 - 深入分析:進入 Pod 看完整 GC 日志,看 GC 前后內(nèi)存變化。
- 抓取證據(jù):如果懷疑內(nèi)存泄漏,抓取堆 dump。
- 臨時解決:重啟 Pod(能暫時緩解,但不能根治)。
- 根治問題:分析 dump 文件,找到泄漏點,讓開發(fā)修復(fù)。
到此這篇關(guān)于Spring Boot 項目在 K8S 中的打包、部署與運維發(fā)布實踐指南的文章就介紹到這了,更多相關(guān)Spring Boot K8S 打包、部署與運維發(fā)布內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+Docker+K8s云原生部署全流程(從零到發(fā)布)
- K8S(Docker)如何優(yōu)雅的關(guān)閉SpringBoot微服務(wù)
- k8s部署springboot實現(xiàn)前后端分離項目
- k8s+springboot+CronJob定時任務(wù)部署實現(xiàn)
- 手把手教你k8s部署springboot服務(wù)
- springboot項目部署到k8s上的方法步驟
- 阿里云k8s服務(wù)springboot項目應(yīng)用升級時出現(xiàn)502錯誤
- 使用Stargate訪問K8ssandra的過程之Springboot整合Cassandra
- SpringBoot應(yīng)用快速部署到K8S的詳細(xì)教程
相關(guān)文章
快速解決SpringMVC @RequestBody 用map接收請求參數(shù)的問題
今天小編就為大家分享快速解決SpringMVC @RequestBody 用map接收請求參數(shù)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
springboot加載命令行參數(shù)ApplicationArguments的實現(xiàn)
本文主要介紹了springboot加載命令行參數(shù)ApplicationArguments的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
SpringBoot搭建Dubbo項目實現(xiàn)斐波那契第n項詳解
這篇文章主要講解了“SpringBoot+Dubbo怎么實現(xiàn)斐波那契第N項”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)吧2022-06-06
Java中的CopyOnWriteArrayList深入解讀
這篇文章主要介紹了Java中的CopyOnWriteArrayList深入解讀,在 ArrayList 的類注釋上,JDK 就提醒了我們,如果要把 ArrayList 作為共享變量的話,是線程不安全的,需要的朋友可以參考下2023-12-12
Mybatis 實現(xiàn)動態(tài)組裝查詢條件,仿SQL模式
這篇文章主要介紹了Mybatis 實現(xiàn)動態(tài)組裝查詢條件,仿SQL模式的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06

