news 2026/10/13 5:51:42

SpringBoot+Docker+Jenkins自动化部署实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Docker+Jenkins自动化部署实战指南

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节点被攻破,所有凭证泄露。正确做法是:

  1. 在Harbor UI创建一个Robot Account,命名为jenkins-prod-pusher,权限设为pull/push;
  2. 复制生成的Token(一长串base64字符串);
  3. 在Jenkins“Credentials”里新增“Secret text”类型凭据,ID设为harbor-robot-token;
  4. 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);
  • 脚本逻辑是:
    1. 从Harbor API查询harbor.internal/library/myapp:${TARGET_TAG}是否存在;
    2. 存在则触发K8s滚动更新:kubectl set image deployment/myapp myapp=harbor.internal/library/myapp:${TARGET_TAG};
    3. 同时更新ConfigMap,将SPRING_PROFILES_ACTIVE设为prod(确保环境变量一致)。

这个设计的精妙在于:回滚不是删除新版本,而是让K8s控制器自动将Pod副本集从新镜像切回旧镜像。整个过程平滑,无请求丢失,且K8s Event里会清晰记录ScalingReplicaSet事件,审计可追溯。实测从点击“Build Now”到所有Pod Ready,耗时22秒,比手动操作快5倍。

4. 实操全流程详解:一次真实发布的完整命令流与日志解读

4.1 前置准备:Jenkins Agent环境的5项硬性检查清单

在正式跑通流水线前,必须确保每台Jenkins Agent满足以下5项条件,缺一不可。这是我给新入职运维同事的checklist,贴在工位显示器边框上:

  1. 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。

  2. 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。

  3. 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"。

  4. 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推送失败。

  5. 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 daemonDocker daemon未启动或磁盘满sudo systemctl status docker;df -h /var/lib/docker启动docker服务;清理/var/lib/docker/overlay2旧层
mvn clean package报错Could not resolve dependencies for projectMaven私服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 requiredHarbor 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 authorityAgent节点未信任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不断重启PodDataSource配置错误或数据库连接超时kubectl logs <pod-name> | grep "HikariPool" | tail -10检查application-prod.yml中spring.datasource.url是否指向正确的RDS endpoint
Jenkins构建成功,但K8s里Pod始终不Ready,kubectl get pods显示0/1Readiness 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 foundDockerfile中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
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 5:51:35

C/C++面试三件套:语言、操作系统与网络如何串成一条线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:51:04

CMake 3.24.0 手动编译与跨平台 C++ 构建实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:50:25

Linux网卡重命名原理与udev永久固化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:50:21

用Rust实现GB28181设备注册:协议流程、鉴权与踩坑实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 5:50:07

Java自定义View与Compose状态联动:跨UI体系同步方案实践

去年我把一个老项目往 Jetpack Compose 迁移&#xff0c;最头疼的不是 Compose 本身&#xff0c;而是那堆用 Java 写了好几年的自定义控件。页面框架换成 Compose 后&#xff0c;自定义控件的状态怎么同步过去、控件交互怎么把变化传回来&#xff0c;就成了绕不开的坎。这个问题…

作者头像 李华