news 2026/9/24 19:27:37

Spring Boot部署Kubernetes实战:镜像构建、探针配置与滚动发布

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot部署Kubernetes实战:镜像构建、探针配置与滚动发布

最近团队在折腾把Spring Boot服务迁到Kubernetes上的事,前前后后踩了不少坑,也总结出一些能直接抄作业的套路。很多人一上来就找一堆YAML模板往上一贴,结果要么Pod起不来,要么流量一上来就内存爆掉,还有的连探针都没配,服务挂在半死状态自己都不知道。这篇把我从零部署Spring Boot项目的完整思路、文件怎么写、为什么这么写、出现了问题怎么查,原原本本理一遍,顺便把企业项目里最常问的那几个点也交代清楚。

适合谁看?正在从单体部署往容器化迁移的Java后端,刚把应用打成镜像但不太清楚Kubernetes里怎么编排的,以及在测试环境能用docker run跑通、到K8s上却总出幺蛾子的同学。我不打算做概念宣讲,Kubernetes和Spring Boot的基本概念默认你已经有数,直接讲实操。

1. 部署前的准备:镜像构建与容器化细节

1.1 Spring Boot应用镜像的构建思路

很多人把镜像构建当成“写一个Dockerfile把jar丢进去”这么简单,但在Kubernetes里跑和本地docker run有几个本质区别:一是Pod会被调度到不同节点,镜像体积直接决定拉取时间;二是容器进程被K8s直接托管,1号进程是否优雅处理SIGTERM会直接影响滚动发布;三是JVM对容器CPU和内存的感知如果不正确,性能会跑偏。

基础镜像我建议优先用Eclipse Temurin的JRE版本,而不是JDK完整版。一个只有几十MB的JRE基础镜像,配合多阶段构建,最终镜像能控制在200MB以内(传统胖Jar方式可能上500MB)。如果你们的Spring Boot版本已经到3.x,推荐直接上eclipse-temurin:17-jre-alpine21-jre-alpine这类基于Alpine的镜像,Alpine的musl libc已经能正常跑JVM,而且镜像非常小,唯一的坑是如果你们用了一些依赖native库的中间件(比如某些OCR、图像处理库),可能得换成ubuntu-jammy基础镜像,否则运行时会缺动态库报错。

多阶段构建是最推荐的方式,理由不只是“减小体积”。它可以彻底把构建环境和运行环境隔离,保证宿主机上不残留Maven仓库、编译临时文件之类的垃圾,另外在Docker缓存策略上也更友好。.dockerignore文件别忘了加,把.gittarget*.iml、IDE配置都排除掉,不然每改一行代码构建上下文都会把几百MB的代码和缓存推给Docker daemon。

1.2 JVM在容器里的内存和CPU配置

这里是我见过最多人踩坑的地方,也是线上事故高发区。JVM默认的堆大小是物理内存的1/4,但容器里看到的是宿主机内存而不是cgroup限制,这就导致一个极其常见的现象:你在Deployment里写了limits: memory: 512Mi,结果JVM直接把堆开到宿主机几个G,最后容器被OOM Killer杀掉,Pod无限重启。

解决办法很简单:显式指定-XX:MaxRAMPercentage=75.0这类比例参数,或者直接用-Xmx限制堆大小。我个人的习惯是:堆内存不超过容器内存的70%,再留一些给元空间、线程栈、JIT编译等。比如容器限制512Mi,堆就写-Xmx384m,预留大概25%左右的非堆开销。JDK 8u191+和JDK 11+默认开启了UseContainerSupport,但开启了不代表你就能不管,因为默认值依然按宿主机内存算,还是得手动设置。

另一个值得说的点是对容器CPU的限制。默认JVM会检测宿主机CPU核心数来设置GC线程和ForkJoinPool,如果你limit了2核但宿主机是32核,JVM会创建大量的GC线程,造成无谓的上下文切换。加一句-XX:ActiveProcessorCount=2就能解决,也可以搭配容器限核配置。

# 多阶段构建示例 FROM maven:3.9-eclipse-temurin-17 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests FROM eclipse-temurin:17-jre-alpine RUN addgroup -S app && adduser -S app -G app WORKDIR /app COPY --from=build /app/target/*.jar app.jar USER app ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:ActiveProcessorCount=2" ENV TZ=Asia/Shanghai EXPOSE 8080 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

这里的adduser非root用户很重要,别用默认的root跑服务。一方面这是容器安全基线,另一方面一些K8s集群开了Pod Security Admission之后,root用户直接会被拦截。还有把时区在镜像层就设好TZ=Asia/Shanghai,不然应用里数据库连接、日志时间戳会差8个小时,排查问题的时候焦虑加倍。

1.3 优雅停机与Spring Boot的配合

Kubernetes在滚动更新或Pod删除时,会给容器主进程发送SIGTERM信号,然后等待terminationGracePeriodSeconds指定的时间(默认30秒),超时后强制SIGKILL。Spring Boot默认会注册JVM ShutdownHook,但问题在于:Spring Boot的关闭默认是立刻销毁Bean,不等待正在处理的请求结束。

想要优雅停机,需要在application.yml里开启:

server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 20s

shutdown: graceful让Spring Boot在收到SIGTERM后停止接收新请求,并等待存量请求处理完再销毁容器。timeout-per-shutdown-phase控制等待上限,这个值建议比K8s的terminationGracePeriodSeconds小一点,保证你有足够时间让Spring完成收尾。

说到Java 21的虚拟线程,如果你的服务是Spring Boot 3.5搭配虚拟线程,部署上多注意一下:虚拟线程适合IO密集型场景,但容器里线程资源已经由K8s在管理,很多人在Pod里配上几百个平台线程照样没事,虚拟线程的优势主要在应用内并发模型更轻量,跟K8s本身没有直接冲突。不过有一点要注意,如果你在代码里用了ThreadLocalsynchronized或者一些没适配虚拟线程的库,改造成本会很明显,部署阶段的JVM参数反而不需要特殊处理。

2. 核心资源编排:从Deployment到Service

2.1 Deployment设计:副本数、滚动更新与探针

Deployment是Kubernetes里最基础的负载资源,它负责维持Pod副本数在期望状态。Spring Boot应用无状态,天然适合Deployment管理。需要重点说的是滚动更新策略和探针的配合,这是保证发布不中断流量的根本。

一个生产可用的副本数可以设为3,但更重要的是更新策略:

strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 0

maxUnavailable: 0意味着滚动过程中任何时候都不允许可用Pod少于期望副本数,代价是更新时先起一个额外Pod再杀掉旧的;maxSurge: 25%控制最多超过期望副本数的比例。这套组合在Spring Boot启动较慢的场景下非常重要,因为如果maxUnavailable不是0,滚动一开始就会先停掉一个Pod,而新Pod可能还在拉镜像、等Java启动,流量窗口会变窄。很多公司在测试环境不敏感,一到生产发布就报警,大概率就是这个参数没配对。

探针是K8s健康检查的关键。Spring Boot项目直接集成Actuator:

spring: boot: admin: context-path: /hello # 注意这和下面没关系,只是示例 management: endpoints: web: exposure: include: health,info,metrics endpoint: health: probes: enabled: true health: livenessstate: enabled: true readinessstate: enabled: true

开启probes.enabled: true后,/actuator/health会暴露livenessreadiness两个子状态。K8s里配两组探针:

livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 20 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3

这里有几个关键点要解释清楚。liveness探针失败会重启容器,readiness探针失败会从Service Endpoints中摘除流量。区别是:如果你的应用因为某个外部依赖(比如Redis连不上)暂时不可用,readiness失败但liveness成功,K8s不会重启Pod而只是停流量;如果连liveness都触发了,说明进程本身已经进入无法自愈的状态,只能重启。这个设计非常贴合Spring Boot的ReadinessStateHealthIndicatorLivenessStateHealthIndicator语义,千万别只用单个/actuator/health同时配给两个探针,不然数据库抖动的时候会变成整个Pod被反复重启。

initialDelaySeconds要给足。Spring Boot冷启动慢,如果是Java 21 + 微服务架构,可能启动要30秒起步,太短的initialDelay会直接打成CrashLoopBackOff。更保险的做法是用startupProbe,专门负责兜底启动期间的健康检查:

startupProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 0 periodSeconds: 5 failureThreshold: 60

startupProbe成功前,livenessProbe不会启动,避免启动慢导致误杀。

2.2 Service与Ingress:应用如何被访问

Deployment只负责拉起Pod,外部访问还要靠Service。Service类型有三类常用选择:ClusterIP(集群内访问)、NodePort(节点端口访问)、LoadBalancer(云厂商负载均衡)。对Spring Boot企业应用来说,生产环境最常见的是“ClusterIP + Ingress”这套组合,NodePort只适合本地调试,LoadBalancer一般交给云平台自动创建,价格和权限成本都不低。

apiVersion: v1 kind: Service metadata: name: spring-boot-service namespace: production spec: selector: app: spring-boot-demo ports: - name: http port: 80 targetPort: 8080 type: ClusterIP

注意selector要和Deployment里的Pod标签完全一致,这是新手最容易分流失败的原因。port是Service暴露的端口,targetPort是容器内应用监听的端口,Spring Boot默认8080。Ingress一般用nginx-ingress-controller:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: spring-boot-ingress namespace: production spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: spring-boot-service port: number: 80

企业项目里如果服务很多,Ingress的路径转发和域名规划要提前设计好,不然后期几十个服务挤在一个Ingress里,改起来非常痛苦。

2.3 配置管理:ConfigMap与Secret的正确打开方式

Spring Boot的配置天然是application.yml外部化,这给K8s的ConfigMap提供了很好的接入点。把环境相关的配置(数据源地址、Redis地址、日志级别、业务开关)放到ConfigMap里,镜像保持环境无关,这在多环境部署时非常实用。

常见的做法有两种。一种是通过环境变量注入:

env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: DB_HOST valueFrom: configMapKeyRef: name: app-config key: db.host

另一种是把整个application.yml做成ConfigMap,用volume挂载到容器里覆盖默认配置。第二种在配置项特别多的时候更清晰:

apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: production data: application.yml: | server: port: 8080 spring: datasource: url: jdbc:mysql://mysql-service:3306/appdb?useSSL=false username: appuser password: ${DB_PASSWORD}

volume挂载方式:

volumeMounts: - name: app-config mountPath: /app/config volumes: - name: app-config configMap: name: app-config

重点是/app/config这个位置。Spring Boot的配置加载顺序里,./config/目录优先于./,所以只要jar包在/app下,配置放/app/config就能自动被识别,不需要额外指定spring.config.location。我测试过这个方案,实测下来非常稳,尤其适合保存完整的、带缩进的YAML文件。

数据库密码、API密钥这类敏感信息,一定放Secret而不是ConfigMap。Secret本质上只是Base64编码,谈不上绝对安全,但至少不会出现在ConfigMap明文里,配合RBAC控制访问范围能降低泄露风险。从ConfigMap/Secret挂载到容器的文件,在Pod运行期间如果ConfigMap发生更新,文件内容会延迟同步(有kubelet定期同步机制),但进程不会自动重启加载新配置,所以改了配置最好滚动更新一下Deployment,这个很多人不知道。

如果配置项特别多变且需要动态刷新,建议上Nacos或Apollo这类配置中心,而不是在K8s里硬磕ConfigMap。

3. 完整实操:把一个Spring Boot项目跑起来

3.1 本地快速验证

先把一个最小的Spring Boot服务构建成镜像。假设项目是标准的Maven结构,本地已经能跑通mvn spring-boot:run,接下来按顺序操作:

# 1. 构建镜像 docker build -t registry.company.com/demo/spring-boot-app:1.0.0 . # 2. 本地验证容器能否正常启动 docker run --rm -p 8080:8080 registry.company.com/demo/spring-boot-app:1.0.0 # 3. 推送到镜像仓库 docker push registry.company.com/demo/spring-boot-app:1.0.0

注意推送的镜像名。K8s节点拉镜像时如果用的是私有仓库,需要在Deployment里配置imagePullSecrets,否则Pod启动时会报ImagePullBackOff。这个Secrets需要提前创建:

kubectl create secret docker-registry registry-secret \ --docker-server=registry.company.com \ --docker-username=yourname \ --docker-password=yourpassword \ -n production

3.2 YAML文件逐段解读与部署

现在写一个完整的Deployment YAML。我强烈建议你自己手敲一遍,而不是从网上复制,因为里面每一个字段都得知道干什么:

apiVersion: apps/v1 kind: Deployment metadata: name: spring-boot-demo namespace: production labels: app: spring-boot-demo spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 0 selector: matchLabels: app: spring-boot-demo template: metadata: labels: app: spring-boot-demo spec: imagePullSecrets: - name: registry-secret containers: - name: spring-boot-app image: registry.company.com/demo/spring-boot-app:1.0.0 imagePullPolicy: Always ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "prod" resources: requests: cpu: 500m memory: 512Mi limits: cpu: "1" memory: 1Gi startupProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 0 periodSeconds: 5 failureThreshold: 60 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 volumeMounts: - name: app-config mountPath: /app/config volumes: - name: app-config configMap: name: app-config

resources里requests和limits的区别一定要搞清楚。requests是调度依据,K8s根据它决定把Pod调度到哪个节点;limits是运行限制,超过会被限流甚至被杀。我见过很多Spring Boot应用只写了limits不写requests,或者requests等于limits,前者容易导致节点资源超卖时Pod被挤死,后者容易让调度器以为Pod吃得很多而拒绝调度。合理的做法是requests略低于平时消耗,limits设为峰值上限,给JVM和GC留缓冲区。

执行发布:

kubectl apply -f app-deployment.yaml kubectl apply -f app-service.yaml kubectl apply -f app-ingress.yaml # 查看状态 kubectl get pods -n production -o wide kubectl rollout status deployment/spring-boot-demo -n production

等所有Pod都变成RunningReady 1/1,用kubectl port-forward先做本地验证:

kubectl port-forward service/spring-boot-demo -n production 8080:80 curl http://localhost:8080/actuator/health

返回{"status":"UP"}说明基础链路已经通了。

3.3 滚动发布、回滚与优雅下线验证

镜像更新后,重新打tag再apply一次Deployment就触发滚动更新:

docker build -t registry.company.com/demo/spring-boot-app:1.0.1 . docker push registry.company.com/demo/spring-boot-app:1.0.1 kubectl set image deployment/spring-boot-demo spring-boot-app=registry.company.com/demo/spring-boot-app:1.0.1 -n production

或者直接改YAML里的image标签再kubectl apply。观察滚动过程,建议开一个窗口不停watch kubectl get pods,你会看到新的Pod先起来,等Readiness通过后,旧的Pod才逐个被终止。这就是maxUnavailable: 0的直观效果,流量全程没有空窗。

如果发布后发现新版本有问题,第一时间回滚:

kubectl rollout undo deployment/spring-boot-demo -n production

回滚是重新部署上一版本的镜像,同样走滚动更新。这个命令在企业故障处理里价值极高,但回滚前一定要确认是要回到上一版本,而不是撤销多次更新。用kubectl rollout history deployment/spring-boot-demo可以查历史的版本号。

很多人在部署Spring Boot项目时忽略了优雅下线验证。发布过程中如果旧Pod还在处理长请求,SIGTERM之后K8s会先从Service的Endpoints列表里把这个Pod摘掉,再发SIGTERM。这个摘除是异步的,可能有一小段时间Pod已经被移除但还是有连着的长连接。对策是给Service配上publishNotReadyAddresses: false(默认就是false),同时让application开启graceful shutdown。用压测工具稍微打一点QPS再触发滚动,观察错误率是否为零,这个测试建议每次结构调整后都做一遍。

4. 企业落地时的常见问题与调试技巧

4.1 Pod状态异常排查:CrashLoopBackOff与ImagePullBackOff

这类问题是新人的头号杀手。CrashLoopBackOff先看日志,kubectl logs <pod-name> -n production,如果上一次是OOMKilled,需要加--previous看退出前日志。Spring Boot应用Crash最常见的原因是内存超限导致OOMKilled,这种只会在资源限制很小的时候出现,处理办法是调大limits或者压低-Xmx。其次是配置加载失败,比如ConfigMap里application.yml格式错误,Spring启动直接抛异常,看日志会非常明显。还有一种情况是健康检查失败被反复重启,这个要看kubectl describe pod里的事件。

ImagePullBackOff的排查思路比较固定。先确认镜像名和tag是否正确,再确认节点是否能访问镜像仓库,最后确认私有仓库认证的secret是否存在于对应namespace。比较隐蔽的坑是Digest权限问题,如果镜像仓库开启了严格的防篡改策略,必须使用sha256摘要拉取,这时要改用image: registry.company.com/demo/spring-boot-app@sha256:xxxxx

# 快速定位Pod为什么起不来 kubectl describe pod <pod-name> -n production kubectl get events --sort-by=.lastTimestamp -n production | tail -20

4.2 访问链路不通:Service与Ingress排错

服务部署好了但访问不了,这是最常见的企业现场问题。先分清楚是Service不通还是Ingress不通。用kubectl exec进一个同namespace的Pod,curl Service的ClusterIP:

kubectl run curl-test --image=curlimages/curl -it --rm --restart=Never --namespace production -- sh curl http://spring-boot-service:80/actuator/health

如果curl通但浏览器/域名不通,问题在Ingress或DNS。如果curl都不通,大概率是Service的selector和Pod标签对不上。用kubectl get endpoints <service-name>看Endpoints是否有Pod地址,如果没有说明selector匹配失败;如果有但访问异常,检查targetPort是否指向了正确的容器端口。

另一个常见坑是Ingress controller和Service的端口协作。nginx-ingress转发到Service默认用port字段(这里是80),但如果targetPort写的是name,要确认这个命名和容器端口一致。之前遇到过targetPort: web,但Deployment容器端口没有定义name: web,导致后端完全不可达,这个问题在YAML校验时还不报错,非常隐蔽。

4.3 探针相关的玄学故障与JVM信号问题

探针是把双刃剑,配好了自动恢复,配不好天天误杀。最常见的是内存充足但Pod不断重启,查看describe后是Liveness探针失败。原因通常是Actuator的health端点里某个子依赖健康检查耗时太长,比如数据库连接池在冷启动时会先初始化,如果liveness探针的periodSeconds太短或timeoutSeconds不够,就会被判定失败。解决方法是给health的依赖项配置合并不必要的检查,或者对探针的timeout、failureThreshold放宽一些。

JVM还有一个著名的坑:K8s滚动更新时旧Pod收到SIGTERM,但JVM没来得及处理完就被SIGKILL。原因通常有两个,一是前面提到的graceful shutdown没开,二是容器里的ENTRYPOINT写成了java -jar但没有正确接收信号。在Dockerfile里如果用了一个shell包装脚本,要注意SIGTERM并不会自动转发给java子进程,需要在脚本里加上exec或者正确的trap。上面的Dockerfile直接用sh -c "java $JAVA_OPTS -jar app.jar",这样sh其实会exec掉java,信号能直达,但如果用了自定义脚本循环等待就要非常小心。

Spring Boot 3.x + Java 21虚拟线程的部署环境,探针行为也略有不同。虚拟线程不占平台线程栈,启动阶段堆内存压力会小一些,但因为线程数可以开到很大,随便一个并发请求就可能创建几万个虚拟线程,如果日志框架没有适配,调试过程中会看到线程创建异常频繁,需要调整日志的异步队列参数。

4.4 企业项目里的安全基线:防止未授权访问

K8s未授权访问漏洞在业界被反复提及,部署Spring Boot项目时尤其要注意控制面组件不能裸奔。API Server、Dashboard、kubelet的端口不要直接暴露到公网,访问控制至少三层:网络策略限制来源IP、RBAC最小权限、TLS证书认证。很多团队为了方便,把Dashboard的Service类型改成NodePort直接暴露,这是高危操作。如果你确实需要通过Dashboard操作集群,尽量用kubectl proxy或绑定内网域名,加上身份认证。

还有一点是企业落地时容易忽略的命名空间隔离和资源配额(ResourceQuota)。仓库里只给测试环境,上了生产后不同团队的服务混在一个namespace,配置互相覆盖、Pod抢占资源,极难排查。建议每个业务线一个namespace,配上ResourceQuota和LimitRange,让默认资源限制自动生效。

4.5 可观测性:日志、监控与告警

Spring Boot应用在K8s里排查问题,最舒服的方式是直接把日志打到stdout,让容器运行时收集,再用Loki或ELK统一检索。Actuator的metrics接Prometheus,Grafana做面板,点击就能看到JVM堆内存、GC、QPS、RT的指标。这个组合在企业项目里是标配,部署时只需要在Deployment注解里加上Prometheus抓取配置:

metadata: annotations: prometheus.io/scrape: "true" prometheus.io/path: "/actuator/prometheus" prometheus.io/port: "8080"

不过前提是Actuator暴露了prometheus端点:

management: endpoints: web: exposure: include: health,info,prometheus

我第一次排查生产问题就靠这套链路快速定位到是GC停顿太频繁导致接口变慢,而不是以为K8s本身出毛病。日志采集那边,Filebeat/DaemonSet方式在节点采集,比每Pod塞sidecar省资源得多,除非你们需要针对个别Pod做精细的日志解析,一般不需要用sidecar模式。

4.6 特殊部署形态:GraalVM原生镜像与多架构

Spring Boot应用除了标准JVM镜像,现在也有不少人尝试用GraalVM Native Image打成原生可执行文件,部署到K8s里最大的优势是启动时间从秒级降到毫秒级,内存占用也大幅减少。这一套在K8s环境里收益非常明显,尤其是弹性伸缩场景,Pod扩容可以做到秒级完成。但代价是构建时需要对反射、动态代理、资源文件做额外配置,很多Spring Boot的生态库(比如MyBatis、Spring Security)需要额外适配。如果你们的项目用了不少反射或SPI机制,优先小范围试点,别一上来全量迁移。

多架构镜像在混合架构集群里也很实用。如果集群里有x86节点和ARM节点(比如Mac M系列搭的集群和云上x86混编),打镜像时用docker buildx同时构建linux/amd64linux/arm64,推送到镜像仓库后用manifest列表实现按需拉取。之前我们没注意这个,结果一些Pod在ARM节点上拉了amd64镜像直接exec format error。改起来很简单,就是构建命令加上--platform参数,CI流水线里写两条build任务。

4.7 常见问题速查表

现象可能原因排查/解决思路
Pod一直Pending节点资源不足、没有匹配的节点标签kubectl describe pod看调度事件,检查节点资源和污点容忍度
Pod Running但Ready 0/1启动慢或readiness失败kubectl logs看启动日志,检查Actuator health状态,确认探针路径
CrashLoopBackOff启动异常、OOM、探针误杀kubectl logs --previous,看退出码和OOMKilled事件
ImagePullBackOff镜像名错误、仓库不可达、凭证缺失检查imagePullPolicy、imagePullSecrets、仓库网络连通性
服务访问超时Service selector不匹配、targetPort错误、Ingress后端异常检查Endpoints、用临时Pod curl测试
滚动发布中断readiness探针没通过、maxUnavailable配置不当观察rollout status,检查新Pod日志,必要时回滚
内存被OOMKilledJVM堆设置超过容器limit显式设置MaxRAMPercentage或Xmx
时间差8小时镜像层没设置时区Dockerfile里ENV TZ=Asia/Shanghai
配置改了不生效ConfigMap更新不会热加载到进程改完配置滚动重启Deployment
网关超时但日志正常线程池阻塞或GC停顿用Prometheus+Actuator看线程活跃数和GC时间

实际执行中很多问题的根源都是资源配置和探针配置互相不匹配。我建议每次微调探针或resources之后,都主动做一次滚动发布验证,不要只在出问题时才去看。

我个人在实际操作中的体会是,把Spring Boot部署到Kubernetes这件事,技术密度并不在于那一堆YAML的字段拼写,而在于你到底愿不愿意花时间去搞清楚“流量的生命周期”——从镜像构建、到调度、到启动探活、到优雅停机、到滚动切换,每一个环节里JVM和K8s的边界在哪里,谁负责什么。早期我也经历过照着模板一梭子apply下去,页面500了完全不知道从哪查起。后来习惯养成了,每写一个资源先画一遍流量路径,再看一眼对应的存活探针和资源配置,问题数量直线下降。最后再分享一个小经验:测试环境的集群配置别和生产差太多,资源限制、探针参数都不一致会导致测试环境一切正常、生产发布必出事。尽量通过Helm或Kustomize维护一套可参数化的部署模板,环境差异只体现在values里,这样才能在上线前把绝大多数坑先踩掉。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 19:26:32

TCP滑动窗口全解析:原理、流量控制与拥塞控制

TCP 滑动窗口这个概念&#xff0c;很多人学的时候觉得不难&#xff0c;但一到实际调优就翻车。面试被问到"滑动窗口怎么实现流量控制"&#xff0c;能说出"控制发送速率"的人不少&#xff0c;再往下问一句"它和拥塞控制的窗口有什么区别"&#xf…

作者头像 李华
网站建设 2026/9/24 19:26:14

鸿蒙Flutter网络层实战:dio配置、权限与踩坑指南

Flutter开发鸿蒙应用聊到网络&#xff0c;十个群里九个会问dio怎么配。前面两篇我们把开发环境、工程骨架和基础组件都过了一遍&#xff0c;这篇直接进入正题&#xff1a;用dio把网络请求跑通&#xff0c;并且能应对鉴权、超时、取消、上传下载这些真实场景。内容不光是贴代码&…

作者头像 李华
网站建设 2026/9/24 19:26:14

C盘清理六大方法:从系统工具到用户文件迁移的完整指南

1. C盘清理的底层逻辑与方案选型 1.1 为什么C盘总是最先满 Windows系统默认把用户文件夹、临时目录、系统还原点、休眠文件、虚拟内存页面文件全部放在C盘。你装软件时如果一路点“下一步”&#xff0c;绝大多数程序也会默认往 C:\Program Files 或 C:\Program Files (x86)…

作者头像 李华
网站建设 2026/9/24 19:25:47

JavaWeb活动管理系统开发:JSP+Servlet+MySQL从零到可运行

简介&#xff1a;这是一份基于JavaWeb的活动管理系统完整项目&#xff0c;采用JSPServletBootstrapMySQL技术栈&#xff0c;分为前后台&#xff0c;覆盖管理员与普通用户两类角色。管理员端包含登录、个人信息维护、活动管理、活动类型管理、报名管理、游客管理等功能&#xff…

作者头像 李华
网站建设 2026/9/24 19:25:00

Ping Ping Ping 命令注入实战:从空格绕过到关键字过滤的 CTF 通关思路

BUUCTF 平台上挂着的一道 [GXYCTF2019]Ping Ping Ping&#xff0c;算是我见过最适合入门 Web 命令注入的题目之一。界面简单到不能再简单&#xff0c;就是给你一个输入框让你填 IP&#xff0c;填完以后页面会模拟 ping 命令把结果回显出来。可就是这么个“玩具题”&#xff0c;…

作者头像 李华
网站建设 2026/9/24 19:24:57

Java Web智慧医疗平台源码拆解:SpringBoot+Vue前后端分离实战

1. 医疗信息化项目&#xff0c;为什么值得你关注这套源码医疗行业的信息化改造一直是Java后端开发者绕不开的业务场景。早年间我做过的医疗项目大多是SSH框架配合JSP页面&#xff0c;前后端耦合严重&#xff0c;改个字段要同时动三四个文件。这两年随着SpringBoot和Vue这类前后…

作者头像 李华