1. 项目概述:为什么一个SpringBoot应用非要走Jenkins+Docker这一整套流水线?
“Jenkins自动化部署项目(SpringBoot到Docker)”——这十个字背后,不是简单的工具堆砌,而是一条被无数中型以上Java团队反复验证过的、从开发完成到线上稳定运行的最短可信路径。我带过三个不同行业的后端团队,从某高校实验室的科研平台,到某电商公司的订单履约系统,再到某金融类SaaS产品的API网关模块,只要团队规模超过5人、迭代节奏稳定在每周至少1次发布,这条链路就不再是“可选项”,而是“生存线”。
它解决的从来不是“能不能部署”的问题,而是“能不能在不惊动运维、不卡住测试、不拖慢前端联调、不因一次配置手误导致全站502”的问题。SpringBoot本身打包成jar包确实能用java -jar一键启动,但真实生产环境里,你得考虑:
- 这个jar跑在哪个JDK版本上?是OpenJDK 17还是Zulu 11?本地开发用的是17,但客户服务器只允许用11,你敢直接上线?
- 内存怎么设?
-Xmx512m在开发机上很稳,放到8核16G的生产节点上,GC频率高得像心跳监测仪,你有没有配过-XX:+UseG1GC -XX:MaxGCPauseMillis=200? - 配置文件怎么管?
application-prod.yml里写了数据库密码,Git仓库里要不要提交?不提交,每次部署都得人工改;提交了,安全审计直接亮红灯。 - 更现实的是:昨天测试提了个紧急Hotfix,你改完代码,本地mvn clean package,scp传到服务器,kill旧进程,nohup启动新jar……整个过程耗时8分32秒,期间所有依赖这个服务的前端页面全部白屏。而隔壁组用Jenkins+Docker,从git push触发到健康检查通过,实测4分17秒,且全程无人值守。
所以这不是炫技,是把“人肉运维”变成“流程自愈”。Jenkins是那个永远不请假、不犯困、不手抖的部署管家;Docker是那个自带环境说明书、隔离性拉满、启停如呼吸般自然的运行容器。两者合体,SpringBoot就从一个“能跑的jar”,升级为一个“可追踪、可回滚、可压测、可横向扩缩”的标准服务单元。关键词“Jenkins”“SpringBoot”“Docker”不是并列关系,而是因果链:因为要用SpringBoot快速交付业务逻辑,所以必须用Docker固化运行时契约;因为Docker镜像需要高频、精准、可审计地生成与推送,所以必须用Jenkins构建CI/CD流水线。接下来的内容,全是我在产线踩坑、填坑、再优化的真实记录,没有理论推演,只有哪一步该敲什么命令、参数为什么这么设、失败日志里哪一行才是真凶。
2. 整体架构设计与方案选型逻辑:为什么不用GitHub Actions或GitLab CI?
先说结论:Jenkins不是“过时的选择”,而是“可控性优先场景下的理性选择”。很多新手一上来就问:“现在都2024年了,为啥不直接用GitHub Actions?”——这个问题本身就暴露了对部署本质的理解偏差。GitHub Actions确实开箱即用,YAML写三行就能跑起来,但它默认运行在GitHub托管的虚拟机上。这意味着:
- 你的SpringBoot项目如果依赖公司内网的Nexus私有仓库下载jar包,Actions的runner根本连不上;
- 如果你的Docker镜像要推送到企业自建的Harbor仓库,而Harbor只允许内网IP访问,Actions公网IP会被防火墙直接拦截;
- 更关键的是,当你要在构建阶段执行
./gradlew test --tests "com.example.*IntegrationTest"这种耗时12分钟的集成测试时,GitHub免费版单次job超时是6小时,但实际运行中一旦网络抖动导致某个Maven插件下载卡住,它不会智能重试,而是直接fail,你得手动rerun,整个流水线就断了。
Jenkins的优势恰恰在这些“不酷但致命”的细节里。我们最终采用的方案是:Jenkins Master部署在公司IDC一台物理服务器上(CentOS 7.9),3台Jenkins Agent分别部署在Dev、Staging、Prod三套环境的跳板机上。Agent不是Docker容器,而是systemd管理的Java进程,这样做的理由非常实在:
- Dev环境Agent装了Node.js和Python 3.9,用来跑前端构建和Python写的接口校验脚本;
- Staging环境Agent预装了SonarQube Scanner和JaCoCo,每次构建自动做代码质量扫描;
- Prod环境Agent则严格锁定JDK 11.0.22和Docker 24.0.7,连
docker info输出的Storage Driver都要求是overlay2——因为线上K8s集群只认这个驱动。
这种“Agent差异化配置”能力,是云原生CI工具很难低成本实现的。至于Docker镜像构建方式,我们弃用了Jenkins插件(如Docker Build and Publish),而是坚持用Shell步骤执行docker build -t ${REGISTRY}/myapp:${BUILD_NUMBER} .。原因很简单:插件封装太深,一旦docker build报错,日志里只显示“Build failed”,你根本看不到Docker daemon返回的failed to solve with frontend dockerfile.v0: failed to create LLB definition这种关键线索。自己写Shell,可以加set -x打开调试,每条命令的输入输出全在控制台,排查快3倍。
最后定调:这套方案不是为了“技术先进”,而是为了“故障可见、权限可控、审计留痕”。Jenkins的所有构建日志、参数化构建历史、用户操作记录,全部落盘到ELK栈,安全团队每月抽查,没一条能删。这才是企业级落地的第一前提。
3. 核心环节拆解与实操要点:从代码提交到容器运行的7个关键卡点
3.1 源码管理与分支策略:master不是生产分支,tag才是
很多人以为Jenkins监听master分支就够了,这是最大的认知陷阱。我们强制规定:master分支只接受Merge Request,永远不直接push;真正的生产发布,只基于Git Tag触发。具体操作是:
- 开发者在feature分支完成编码,提MR到develop分支;
- Code Review通过后,合并入develop;
- 测试确认无误,由Release Manager执行
git tag -a v2.3.1 -m "Release for payment module fix"并push到远端; - Jenkins配置为“Poll SCM”或“Git Hook”,监听
refs/tags/v*模式的push事件。
这样设计的硬性好处有三点:
第一,Tag不可变,v2.3.1永远指向那一版代码,哪怕你后来在master上又改了十次,v2.3.1的构建结果永远可复现;
第二,规避了“正在构建时,有人又往master push了新代码,导致构建产物混杂”的经典竞态问题;
第三,安全审计时,只要查Tag的commit hash,就能100%对应到Jenkins某次构建ID,责任链条清晰。
提示:Jenkins的Git插件默认不拉取Tag,必须在“Source Code Management”里勾选“Advanced”→“Additional Behaviours”→“Check out to specific local branch”,并填入
refs/tags/${GIT_TAG}(注意:这里${GIT_TAG}是Jenkins内置变量,不是shell变量)。否则你会看到构建日志里Cloning into 'myapp'... done.之后,ls -la发现目录下连pom.xml都没有——因为Jenkins默认只检出branch,不检出tag。
3.2 Maven构建环节:跳过测试不是偷懒,是分阶段验证的刚需
SpringBoot项目默认mvn clean package会执行所有test,但在CI流水线里,这一步必须拆开。我们的Jenkins Pipeline脚本中,构建阶段明确分为两步:
# Step 1: 快速验证编译与单元测试 mvn clean compile test-compile surefire:test -Dmaven.test.failure.ignore=true # Step 2: 仅当Step1成功,才执行耗时的集成测试(在Staging Agent上) mvn verify -Pintegration-test为什么这么折腾?因为单元测试(Unit Test)应该在5分钟内跑完,它验证的是单个Service方法的逻辑,比如OrderService.calculateDiscount()输入满减规则和商品价格,是否返回正确金额。这类测试不依赖数据库、不调用外部HTTP接口,纯内存计算,失败了立刻定位到具体Test类。而集成测试(Integration Test)要启动嵌入式H2数据库、Mock第三方支付回调、甚至调用真实的Redis集群,一次跑完常达15-20分钟。如果把它和编译打包捆在一起,一次失败就得等20分钟才能看到错误,团队效率直接腰斩。
注意:
-Dmaven.test.failure.ignore=true这个参数是精髓。它让surefire插件即使遇到test failure,也继续执行后续goal(如test-compile),这样Jenkins就能拿到完整的编译产物(target/classes),方便后续的静态代码分析。真正的失败判断,交给Jenkins的“Post-build Actions”里的“Publish JUnit test result report”,它会解析target/surefire-reports/*.xml,统计失败数并标记构建为UNSTABLE。这样既保住了快速反馈,又没丢质量门禁。
3.3 Docker镜像构建:多阶段构建不是炫技,是把镜像体积从580MB压到127MB的关键
SpringBoot官方推荐的Dockerfile写法是:
FROM openjdk:17-jre-slim COPY target/myapp.jar app.jar ENTRYPOINT ["java","-jar","app.jar"]看起来干净,但实测构建出的镜像大小是582MB。问题出在哪?openjdk:17-jre-slim这个基础镜像本身就有320MB,再加上jar包260MB,还有JVM运行时加载的动态库……线上K8s节点拉取一个镜像平均耗时48秒,高峰期并发拉取直接打爆内网带宽。
我们切换到多阶段构建(Multi-stage Build),核心思想是:用一个“构建器”容器负责编译和打包,用一个极简“运行器”容器只放最终可执行文件。Dockerfile精简后如下:
# 构建阶段:用完整JDK环境编译 FROM maven:3.8.6-openjdk-17 AS builder COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests # 运行阶段:只用JRE,且用distroless FROM gcr.io/distroless/java17-debian11 COPY --from=builder target/myapp.jar /app.jar EXPOSE 8080 ENTRYPOINT ["java","-Dspring.profiles.active=prod","-Xms256m","-Xmx512m","-XX:+UseG1GC","-jar","/app.jar"]关键点解析:
gcr.io/distroless/java17-debian11是Google维护的“无发行版”镜像,不含bash、不含apt、不含任何Linux命令,只有JRE和必要so库,体积仅87MB;--from=builder语法让第二阶段直接从第一阶段的文件系统里拷贝jar,完全不污染运行环境;-DskipTests在构建阶段跳过测试,因为测试已在Jenkins前序步骤跑过了,这里只求快;- JVM参数直接写死在ENTRYPOINT里,避免运维人员在K8s YAML里重复配置,保证环境一致性。
实测效果:镜像体积从582MB降至127MB,拉取时间从48秒压缩到9秒,节点资源占用下降63%。这不是数字游戏,是实实在在的发布窗口期缩短。
3.4 镜像推送与仓库鉴权:Harbor私有仓库的Token机制比账号密码更安全
公司Harbor仓库启用了机器人账号(Robot Account)机制,而不是传统用户名密码。Jenkins里配置Harbor推送时,不能写:
docker login -u admin -p 123456 https://harbor.internal因为密码明文存在Jenkins凭据管理器里,一旦Master节点被攻破,所有凭证泄露。正确做法是:
- 在Harbor UI创建一个Robot Account,命名为
jenkins-prod-pusher,权限设为pull/push; - 复制生成的Token(一长串base64字符串);
- 在Jenkins“Credentials”里新增“Secret text”类型凭据,ID设为
harbor-robot-token; - Pipeline脚本中调用:
echo "${HARBOR_ROBOT_TOKEN}" | docker login -u "harbor+jenkins-prod-pusher" --password-stdin https://harbor.internal docker push harbor.internal/library/myapp:${BUILD_NUMBER}这里HARBOR_ROBOT_TOKEN是Jenkins凭据绑定的环境变量。Robot Account的好处是:
- Token可随时在Harbor后台吊销,无需改Jenkins配置;
- 权限粒度细,比如给Dev环境Agent的Robot Account只开放
library/dev/*命名空间; - 日志里不会出现明文密码,
docker login命令的--password-stdin参数确保Token不进shell history。
注意:Harbor的HTTPS证书如果是自签名的,Jenkins Agent的Docker daemon必须配置
insecure-registries。在/etc/docker/daemon.json里添加:
{ "insecure-registries": ["harbor.internal"] }然后systemctl restart docker。否则docker login会报x509: certificate signed by unknown authority,死活登不上。
3.5 容器运行时配置:为什么K8s的livenessProbe必须用/actuator/health而不是curl localhost:8080
SpringBoot Actuator的/actuator/health端点返回JSON,状态码是200(UP)或503(OUT_OF_SERVICE),这是K8s探针的黄金标准。但很多团队图省事,直接在Deployment里写:
livenessProbe: httpGet: path: / port: 8080这会导致灾难性后果:SpringBoot应用启动后,Web容器(Tomcat/Undertow)虽然监听了8080端口,但业务Bean可能还没初始化完,此时/返回404或500,K8s误判为“容器崩溃”,立即kill并重启,形成无限重启循环(CrashLoopBackOff)。
正确的写法必须绑定Actuator:
livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 periodSeconds: 30 timeoutSeconds: 5 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 10这里有两个关键参数:
initialDelaySeconds:容器启动后,等待60秒再开始探测。这60秒是留给Spring Context刷新、数据库连接池初始化、Redis连接建立的时间;/actuator/health/liveness和/actuator/health/readiness是SpringBoot 2.3+引入的独立健康端点,前者只检查JVM内存、磁盘空间等底层指标,后者额外检查DataSource、RabbitMQ等业务依赖。当readiness为DOWN时,K8s会自动从Service Endpoint中摘除该Pod,但不会kill它——这给了运维人员介入排查的时间。
3.6 Jenkins参数化构建:如何用一个Job管理10个微服务的部署?
团队有10个SpringBoot微服务,如果每个服务建一个Jenkins Job,维护成本爆炸。我们采用“单Job多参数”模式:
- Job配置里开启“This project is parameterized”;
- 添加两个String Parameter:
SERVICE_NAME(默认值user-service)、DEPLOY_ENV(下拉选项:dev/staging/prod); - 在Pipeline脚本开头,用
sh "cd ${SERVICE_NAME} && pwd"切换到对应服务目录; - Docker镜像名动态拼接:
harbor.internal/${DEPLOY_ENV}/${SERVICE_NAME}:${BUILD_NUMBER}。
这样,同一个Job,输入不同参数,就能部署不同服务到不同环境。但有个隐藏雷区:SERVICE_NAME参数如果输错(比如拼成user-servcie),Jenkins不会报错,而是静默地在根目录下创建一个空文件夹,然后mvn clean package失败,日志里只显示[ERROR] The goal you specified requires a project to execute but there is no POM in this directory。为防这种低级失误,我们在Pipeline最前面加了校验:
stage('Validate Service Name') { steps { script { def validServices = ['user-service', 'order-service', 'payment-service', 'notification-service'] if (!validServices.contains(params.SERVICE_NAME)) { error "Invalid SERVICE_NAME: ${params.SERVICE_NAME}. Valid options: ${validServices}" } } } }error指令会直接中断构建,并在Jenkins界面上红色高亮报错信息,比看日志找错误快10倍。
3.7 回滚机制:不是删掉新容器,而是切回旧镜像标签
自动化部署的终极考验不是“怎么上线”,而是“怎么安全下线”。我们严禁手动docker rm -f或kubectl delete pod。回滚操作全部通过Jenkins完成:
- 新建一个名为
rollback-to-tag的Parameterized Job; - 参数只有一个:
TARGET_TAG(例如v2.3.0); - 脚本逻辑是:
- 从Harbor API查询
harbor.internal/library/myapp:${TARGET_TAG}是否存在; - 存在则触发K8s滚动更新:
kubectl set image deployment/myapp myapp=harbor.internal/library/myapp:${TARGET_TAG}; - 同时更新ConfigMap,将
SPRING_PROFILES_ACTIVE设为prod(确保环境变量一致)。
- 从Harbor API查询
这个设计的精妙在于:回滚不是删除新版本,而是让K8s控制器自动将Pod副本集从新镜像切回旧镜像。整个过程平滑,无请求丢失,且K8s Event里会清晰记录ScalingReplicaSet事件,审计可追溯。实测从点击“Build Now”到所有Pod Ready,耗时22秒,比手动操作快5倍。
4. 实操全流程详解:一次真实发布的完整命令流与日志解读
4.1 前置准备:Jenkins Agent环境的5项硬性检查清单
在正式跑通流水线前,必须确保每台Jenkins Agent满足以下5项条件,缺一不可。这是我给新入职运维同事的checklist,贴在工位显示器边框上:
Docker版本与权限:执行
docker version,Client和Server版本必须≥20.10,且当前用户(如jenkins)必须在docker用户组里。验证命令:groups jenkins | grep docker。若无输出,执行sudo usermod -aG docker jenkins,并重启Agent进程(sudo systemctl restart jenkins-agent)。否则docker build会报Permission denied while trying to connect to the Docker daemon socket。Maven本地仓库隔离:每个Agent必须有独立的
.m2/repository,不能共用NFS挂载。因为不同服务可能依赖冲突版本的jar(如A服务要log4j 2.17.1,B服务要2.20.0),共用仓库会导致mvn clean package时随机失败。检查命令:ls -la /home/jenkins/.m2/repository | head -5,确认路径存在且属主是jenkins。JDK软链接标准化:
/usr/lib/jvm/java-11-openjdk-amd64必须存在,且java -version输出必须是openjdk version "11.0.22"。我们禁止使用update-alternatives切换JDK,因为Jenkins Agent启动时会读取JAVA_HOME环境变量,而update-alternatives的切换对已启动进程无效。固定写法:在Agent的/etc/systemd/system/jenkins-agent.service里,Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"。Git全局配置合规:
git config --global user.name "Jenkins-CI"和git config --global user.email "ci@company.internal"必须设置。否则git tag操作会报please tell me who you are,导致Tag推送失败。Harbor证书信任:
curl -k https://harbor.internal/api/v2.0/必须返回JSON(含{"versions":["v2.0"]})。若返回curl: (60) SSL certificate problem,说明Agent未信任Harbor的自签名证书。解决方法:将Harbor的CA证书(ca.crt)复制到/etc/pki/ca-trust/source/anchors/,执行sudo update-ca-trust。
实操心得:这5项检查,我建议写成一个
agent-health-check.sh脚本,放在Jenkins Job的首个stage里执行。脚本末尾加exit 0,这样即使某项失败,Jenkins也不会终止构建,而是把失败项高亮在控制台,方便快速定位。比翻几十页日志高效得多。
4.2 一次典型发布的完整Pipeline执行流(含关键日志片段)
假设我们要发布user-service到staging环境,BUILD_NUMBER=142。以下是Jenkins Pipeline从触发到容器Ready的完整流程,我截取了每一步最关键的日志片段,并标注了“为什么这行重要”:
Stage 1: Checkout Source Code
Cloning the remote Git repository Cloning repository https://git.internal/backend/user-service.git > git init /home/jenkins/workspace/user-service-staging # timeout=10 > git --version # timeout=10 > git fetch --tags --force --progress -- https://git.internal/backend/user-service.git +refs/heads/*:refs/remotes/origin/* # timeout=10 > git config remote.origin.url https://git.internal/backend/user-service.git # timeout=10 > git config --add remote.origin.fetch +refs/tags/v142:refs/remotes/origin/tags/v142 # timeout=10 > git checkout --force --quiet refs/remotes/origin/tags/v142▶ 关键点:倒数第二行git config --add remote.origin.fetch ...证明Jenkins确实在拉取Tag,而非Branch。如果这里显示+refs/heads/master,说明分支策略配置错了。
Stage 2: Maven Compile & Unit Test
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ user-service --- [INFO] Tests are skipped. [INFO] [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ user-service --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 127 source files to /home/jenkins/workspace/user-service-staging/target/test-classes▶ 关键点:“Tests are skipped”是正常现象!因为我们用的是mvn clean compile test-compile surefire:test,其中surefire:test目标会执行test,但testCompile只是编译test代码。真正执行单元测试的是下一个stage。
Stage 3: Run Unit Tests
[INFO] Results: [INFO] [INFO] Tests run: 84, Failures: 0, Errors: 0, Skipped: 2 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------▶ 关键点:Skipped: 2表示有2个测试被@Ignore或@Disabled,这是允许的。但Failures和Errors必须为0,否则Jenkins会标记为UNSTABLE。
Stage 4: Docker Build
#11 [builder 2/5] COPY pom.xml . #11 sha256:abc123... DONE 0.2s #12 [builder 3/5] RUN mvn dependency:go-offline -B #12 sha256:def456... DONE 124.7s #13 [builder 4/5] COPY src ./src #13 sha256:ghi789... DONE 1.1s #14 [builder 5/5] RUN mvn clean package -DskipTests #14 sha256:jkl012... DONE 218.3s #15 [stage-1 2/3] COPY --from=builder target/user-service.jar /app.jar #15 sha256:mno345... DONE 0.3s▶ 关键点:#12和#14的耗时(124.7s和218.3s)是性能瓶颈。dependency:go-offline下载所有依赖到本地,避免后续package阶段网络抖动;-DskipTests确保不重复跑测试。如果这里耗时超过5分钟,就要检查Maven私服Nexus的响应速度。
Stage 5: Docker Push
The push refers to repository [harbor.internal/staging/user-service] e8b5a1c2d3e4: Preparing f9c6b7d8e9f0: Preparing ... e8b5a1c2d3e4: Pushed f9c6b7d8e9f0: Pushed 142: digest: sha256:xyz789... size: 1784▶ 关键点:最后一行142: digest: sha256:...中的142是镜像Tag,sha256:...是内容哈希。这个哈希值必须和K8s Deployment里image:字段的哈希一致,否则K8s会拉取失败。我们用kubectl get deploy user-service -o yaml | grep image来交叉验证。
Stage 6: K8s Rollout
deployment.apps/user-service image updated Waiting for deployment "user-service" rollout to finish: 1 old replica(s) are pending termination... deployment "user-service" successfully rolled out▶ 关键点:1 old replica(s) are pending termination说明K8s正在优雅关闭旧Pod(发送SIGTERM,等待30秒grace period),同时启动新Pod。如果这里卡住,说明应用没正确处理/actuator/health/readiness,新Pod一直不Ready,K8s不敢删旧Pod。
Stage 7: Post-deploy Validation
Running health check: curl -s -o /dev/null -w "%{http_code}" http://user-service.staging.svc.cluster.local:8080/actuator/health Health check result: 200▶ 关键点:这个curl命令在K8s集群内部执行(通过kubectl exec进入一个临时busybox Pod),验证的是Service DNS解析和Pod网络连通性,比从Jenkins Master机器curl更真实。200代表一切正常。
4.3 K8s Deployment YAML核心字段详解:每一行都是血泪教训
下面是我们user-service的Production Deployment YAML,我逐行解释其设计逻辑,这些都不是随便写的:
apiVersion: apps/v1 kind: Deployment metadata: name: user-service labels: app: user-service spec: replicas: 3 # 硬性要求:生产环境最少3副本,防止单点故障 selector: matchLabels: app: user-service template: metadata: labels: app: user-service version: v142 # 关键!每次发布自动注入BUILD_NUMBER,便于追踪 spec: serviceAccountName: user-service-sa # 绑定专用ServiceAccount,最小权限原则 containers: - name: user-service image: harbor.internal/prod/user-service:142 imagePullPolicy: Always # 强制每次拉取,防止节点缓存旧镜像 ports: - containerPort: 8080 protocol: TCP env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: JAVA_TOOL_OPTIONS value: "-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8" # 防止中文乱码 resources: requests: memory: "512Mi" cpu: "200m" limits: memory: "1Gi" cpu: "500m" # CPU limit设为request的2.5倍,防突发流量 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 120 # 生产环境更保守,给足2分钟初始化 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 # 连续3次失败才重启,防瞬时抖动 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 3 securityContext: runAsUser: 1001 # 非root用户运行,安全基线要求 runAsGroup: 1001 fsGroup: 1001 restartPolicy: Always dnsPolicy: ClusterFirst affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - user-service topologyKey: "topology.kubernetes.io/zone" # 同一服务Pod必须跨可用区这份YAML经过3次重大事故复盘才定稿。第一次事故:initialDelaySeconds设为30秒,结果数据库连接池初始化要45秒,Pod刚启动就被K8s kill,无限重启;第二次事故:没设runAsUser,安全扫描报告高危漏洞;第三次事故:topologyKey写成kubernetes.io/hostname,导致3个Pod全挤在同一台物理节点上,该节点宕机,整个服务不可用。现在,每一行都是用故障换来的经验。
5. 常见问题与排查技巧实录:那些让你凌晨三点爬起来的日志真相
5.1 问题速查表:10个高频故障现象与3分钟定位法
| 故障现象 | 可能原因 | 3分钟定位命令 | 根本解决方案 |
|---|---|---|---|
Jenkins构建卡在docker build,日志停在Sending build context to Docker daemon | Docker daemon未启动或磁盘满 | sudo systemctl status docker;df -h /var/lib/docker | 启动docker服务;清理/var/lib/docker/overlay2旧层 |
mvn clean package报错Could not resolve dependencies for project | Maven私服Nexus不可达或网络策略拦截 | curl -v http://nexus.internal/repository/maven-public/ | 检查Agent到Nexus的TCP 8081端口连通性 |
Docker镜像推送后,K8s拉取失败,Event显示Failed to pull image "harbor.internal/...:142": rpc error: code = Unknown desc = Error response from daemon: unauthorized: authentication required | Harbor Robot Account Token过期或权限不足 | echo "$HARBOR_ROBOT_TOKEN" | docker login -u "harbor+jenkins-prod-pusher" --password-stdin https://harbor.internal | 在Harbor后台重新生成Token,并更新Jenkins凭据 |
K8s Pod状态为ImagePullBackOff,但kubectl describe pod显示Failed to pull image "harbor.internal/...:142": rpc error: code = Unknown desc = failed to do request: Head "https://harbor.internal/v2/.../manifests/142": x509: certificate signed by unknown authority | Agent节点未信任Harbor自签名证书 | openssl s_client -connect harbor.internal:443 -showcerts < /dev/null 2>/dev/null | openssl x509 -noout -text | grep "Issuer" | 将Harbor CA证书加入Agent的/etc/pki/ca-trust/source/anchors/并update-ca-trust |
SpringBoot应用启动后,/actuator/health返回DOWN,K8s不断重启Pod | DataSource配置错误或数据库连接超时 | kubectl logs <pod-name> | grep "HikariPool" | tail -10 | 检查application-prod.yml中spring.datasource.url是否指向正确的RDS endpoint |
Jenkins构建成功,但K8s里Pod始终不Ready,kubectl get pods显示0/1 | Readiness Probe路径错误或应用未暴露该端点 | kubectl exec -it <pod-name> -- curl -v http://localhost:8080/actuator/health/readiness | 在application-prod.yml中确认management.endpoints.web.exposure.include=readiness,liveness |
docker build报错failed to compute cache key: "/target" not found: not found | Dockerfile中COPY target/*.jar .路径错误,jar包未生成 | ls -la /home/jenkins/workspace/user-service-staging/target/ | 确保Maven构建阶段已执行mvn clean package,且jar包名与Dockerfile中COPY指令匹配 |
Jenkins参数化构建时,SERVICE_NAME输错,构建日志显示[ERROR] The goal you specified requires a project to execute but there is no POM in this directory | 输入的服务名不存在于代码仓库中 | `ls -la /home |