上周帮某团队排查一个Spring Boot服务在Kubernetes集群里的诡异问题:Pod能正常起来,健康检查也通过,但每到流量高峰就有一批实例被杀掉重启,日志里全是数据库连接池排队超时。折腾了半天,最后定位到不是应用代码的问题,而是容器探针、内存参数和优雅停机这几处配置没有跟着部署方式升级。
这个case挺典型——很多团队在本地把Spring Boot跑得顺顺当当,但在Kubernetes里部署时就一个接一个地踩坑。这篇文章就把“使用Kubernetes部署Spring Boot项目”这条链路从头到尾拆一遍,包括镜像构建、资源清单、探针配置、配置管理、弹性伸缩和排障思路。不管是刚入门K8s的Java开发,还是已经部署了但经常遇到Pod重启、发布抖动、接口超时的团队,都能从这里找到对应的解法。
1. 为什么要把Spring Boot搬上Kubernetes:单体部署的瓶颈与云原生的解法
1.1 单体部署到底卡在哪
在聊Kubernetes之前,得先想清楚一件事:原来的部署方式到底哪里不舒服。我见过不少团队用传统方式部署Spring Boot应用——打一个jar包,丢到云服务器上,写个启动脚本,再用Nginx做反向代理。这套方案在应用少、流量平稳的时候确实能跑,但一旦服务数量上来,痛点就非常明显。
首先是扩容速度。流量上来了,你得手动多开两台服务器,重新上传jar、配环境、改Nginx上游列表。整个过程快则半小时,慢则半天。而Kubernetes的做法是声明式的——你告诉集群“这个服务我要跑5个副本”,它自己会创建、调度、保持健康。流量降下来之后,再把副本数缩回去就行,整个过程以秒为单位。
其次是环境一致性问题。同一套Spring Boot应用,在开发环境的JDK版本、服务器时区、字符集、内存参数和线上不一样,很容易出现“本地没问题,一上线就崩”的情况。容器镜像把运行环境固化到镜像里,开发环境和生产环境跑的是同一个镜像,这算是Kubernetes带来的附带红利,但很多人忽略了这一点。
最后是发布和回滚。传统方式发布新版本,最粗糙的做法是停服、传包、重启,或者用Shell脚本做蓝绿切换,流程繁琐还容易出错。Kubernetes天然支持滚动更新、暂停发布、一键回滚,这些能力不需要额外开发,只要把Deployment配置好就有。
1.2 K8s解决的三个核心问题
用Kubernetes部署Spring Boot,核心收益可以归纳成三句话:
- 资源管理自动化。CPU、内存、磁盘、网络这些资源由调度器统一管理,Pod被分配到哪台机器、怎么保证资源不冲突,都不需要人工干预。
- 服务自愈。进程崩溃、节点宕机、健康检查失败这些场景,Kubernetes会按照探针和重启策略自动处理,不需要半夜爬起来手动重启应用。
- 流量入口统一。多个Spring Boot服务创建后,通过Service和Ingress统一暴露端口和路由规则,内部服务互相调用直接走DNS解析,不再依赖手工维护的IP列表。
1.3 不是所有项目都适合立刻迁
说实话,Kubernetes不是银弹。如果只是一个小工具、一个管理后台,部署频率很低、依赖很少,那强行上K8s反而增加了运维复杂度。但如果是微服务架构、服务数量超过五个、需要频繁发版或需要弹性伸缩的项目,那就值得投入人力把事情做扎实。判断标准很简单:如果复制粘贴式部署已经让你觉得痛苦,或者发布流程需要两三个人盯,那就是该迁移的信号。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 镜像构建:Spring Boot进容器前的最后一公里
2.1 基础镜像怎么选:体积、安全与JVM的平衡
很多人在镜像这里第一个踩坑就是选了带完整JDK的通用镜像。Spring Boot运行时并不需要编译能力,所以常规做法是选JRE基础镜像。以Spring Boot 3.x为例,JVM版本要求17以上,可以直接用Temurin、Liberica这些提供JRE的开源镜像。
基础镜像的选择其实是体积、安全性和运维成本之间的权衡。Alpine系列体积小但用的是musl libc,某些Java本地库可能出现兼容问题;Ubuntu/Debian系体积大一些但兼容性好。更极致一点可以用Distroless镜像,里面连Shell都没有,攻击面非常小,但调试也不方便,不太推荐新手上来就用。我个人的做法是:线上用官方维护的JRE基础镜像,先跑通再说,后续再考虑精简。
2.2 Dockerfile多阶段构建与关键参数
我给Spring Boot项目写过不少Dockerfile,踩过很多坑之后,现在的标准模板是这样:
dockerfile复制# 构建阶段
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src/ ./src/
RUN mvn package -DskipTests
# 运行阶段
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
RUN useradd -r appuser -u 1001
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
多阶段构建的目的是让最终镜像只包含运行需要的文件和依赖,编译工具、中间产物全部留在构建阶段。这样镜像小、更快分发,也避免了把源代码或构建缓存带到生产环境。
这里有个容易被忽略的点:Spring Boot生成的fat jar在容器里运行时会多一层解压开销。如果追求极致性能,可以用 spring-boot-maven-plugin 的 layertools 把依赖层和应用层分开,利用镜像分层缓存来加速构建。对大型项目来说,这个优化能明显缩短发布流水线的耗时。
2.3 时区、编码与优雅停机的隐藏坑
镜像里藏着两个新手最容易踩的坑。第一个是时区,默认镜像时区是UTC,日志时间和数据库时间相差八小时,排查问题时非常痛苦。在Dockerfile里加一行就能解决:
dockerfile复制ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
第二个坑是JVM内存参数。很多人的启动命令还是 -Xmx512m 这样写死的,这在物理机部署时没问题,但进了容器就危险了。Java应用的堆内存不感知容器限制,如果你给容器的内存上限是1GB,但 -Xmx 写的是2GB,进程就会在内存压力下被操作系统或Kubernetes杀死。正确做法是像我上面那样,不写 -Xmx,而是用JVM的容器感知参数:
bash复制java -XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0 -jar app.jar
这个参数让JVM自己根据容器限制计算堆大小,而不是靠人工估算。预留的25%给Metaspace、线程栈、堆外内存,避免出现容器内存占用超过limit被OOMKilled的情况。
3. 资源清单的搭配逻辑:从Deployment到Ingress一次说清
3.1 核心配置的完整YAML示例
Kubernetes中一个Spring Boot应用通常涉及Deployment、Service、Ingress三件套。写成一份完整清单的话,大致是这样的:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-demo
labels:
app: spring-demo
spec:
replicas: 3
selector:
matchLabels:
app: spring-demo
template:
metadata:
labels:
app: spring-demo
spec:
containers:
- name: app
image: registry.example.com/spring-demo:v1.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: spring-demo-svc
spec:
selector:
app: spring-demo
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spring-demo-ing
spec:
ingressClassName: nginx
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spring-demo-svc
port:
number: 80
这份清单已经能跑,但里面每个字段背后都有文章。下面的小节逐个拆开说。
3.2 资源限制requests/limits怎么定
requests 是给调度器看的声明,表示这个Pod至少需要多少资源,调度器会据此找一个能满足要求的节点。limits 是硬上限,超过之后CPU会被限流,内存则可能被回收甚至杀掉Pod。
怎么定具体数值?不要拍脑袋。正确的流程是:先不设limits,让应用跑几天,观察监控曲线,然后取一个“稳定运行峰值再留20%余量”的数值。比如监控显示Spring Boot服务的堆内存峰值600MB,整个容器的RSS是800MB,那requests可以设512Mi,limits设1Gi比较合理。CPU的话,Spring Boot这类IO密集应用,一般500m到1个核就够了,可以后续根据压测调整。
requests 和 limits 还有一个要注意的点:如果设置得太紧,节点内存不足时会先杀那些占用超过requests但低于limits的Pod,也就是QoS等级为Burstable的Pod,这在稳定性要求高的服务上需要特别注意。
3.3 Service与Ingress:流量进出的两条路径
Service在Kubernetes里相当于内部负载均衡器,默认ClusterIP类型只在集群内部可达。Spring Boot服务之间的互相调用,走的就是这个机制——服务A访问服务B时直接请求 http://spring-demo-svc:80,Kubernetes会根据Pod标签自动把流量分发到某个后端实例。
Ingress则是外部流量进入集群的入口。常见的Ingress控制器是基于Nginx构建的,它读取Ingress规则,把域名和路径映射到对应的Service上。这里有一个Spring Boot项目特有的坑:如果应用配置了 server.servlet.context-path=/api,而Ingress转发规则里又配置了路径重写,两边不一致就会导致404。最简单的处理方式是在Ingress里只按域名转发,context-path统一交给应用处理;如果非要路径重写,记得确认Nginx的 rewrite-target 配置正确。
4. 探针与优雅停机:K8s里Spring Boot的命门
4.1 三种探针的分工与配置参数
探针是Kubernetes感知应用健康的眼睛,一共三种:
- StartupProbe:检测应用是否启动完成,适合启动慢的应用。
- ReadinessProbe:检测应用是否准备好接收流量,失败时Pod从Service的端点列表中被移除。
- LivenessProbe:检测应用是否存活,失败时Kubernetes会杀掉Pod并重启。
三种探针的分工非常关键。很多新手只用一种LivenessProbe,结果应用启动慢,在第一次探测失败后就被Kubernetes不断杀掉,形成CrashLoopBackOff。正确做法是:启动过程交给StartupProbe,启动完成后再由ReadinessProbe和LivenessProbe接管。StartupProbe的 failureThreshold 可以设大一些,比如30次,每次10秒,这样即使应用启动慢也能撑过去。
Spring Boot也有相应的原生支持。Spring Boot 2.3之后,Actuator提供了 /actuator/health/readiness 和 /actuator/health/liveness 两个独立端点。这两个端点基于 ApplicationAvailability 状态来判断——应用启动中时liveness状态是DOWN,如果这时候把LivenessProbe配上去,大概率会被误杀。
4.2 Actuator健康检查与Spring Boot 2.3+的可用状态
说句实话,直接把 /actuator/health 作为LivenessProbe和ReadinessProbe的端点是最常见的错误。因为 /actuator/health 聚合了数据库、Redis、消息队列这些依赖组件的健康状态,数据库一旦抖动,健康检查返回非200,LivenessProbe就开始杀Pod,这会让问题雪上加霜——本来数据库只是慢,结果应用被全部重启,流量全部打到剩余实例上,进一步加重数据库压力。
所以标准姿势是把两种探针分离开:ReadinessProbe检查依赖项状态(用 /actuator/health/readiness),LivenessProbe只检查进程是否活着(用 /actuator/health/liveness)。如果你的Spring Boot版本比较老,不具备这两个端点,可以退回用 /actuator/health 配合 management.endpoint.health.probes.enabled=true,但明确建议尽早升级到2.3以上的版本。
4.3 preStop钩子与terminationGracePeriodSeconds的配合
优雅停机是做发布的时候最容易忽略的事。默认情况下,Kubernetes要结束一个Pod时,会先通知节点上的kubelet,kubelet把Pod状态改为Terminating,然后:
- Pod从Service Endpoints中摘除(异步,需要一点时间)。
- kubelet向Pod里的主进程发送SIGTERM信号。
- 容器主进程在
terminationGracePeriodSeconds(默认30秒)内没有退出,kubelet就发SIGKILL强制杀死。
Spring Boot收到SIGTERM后,默认行为是直接退出,不会等待正在进行中的请求处理完。这会导致滚动发布时部分请求中断、报错。要解决这个问题,Spring Boot侧需要开启优雅停机:
properties复制server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=20s
Kubernetes侧还需要给Pod加一个 preStop 钩子来兜底。因为上面提到Endpoints摘除是异步的,存在一段时间内请求仍会打到Terminating的Pod上,此时应用已经进入关闭流程,连接会被拒绝。常见做法是在preStop里sleep几秒,给Controller缓冲时间:
yaml复制lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
同时把 terminationGracePeriodSeconds 调整为 sleep时间 + graceful shutdown的预估时间,比如35秒。这个组合在我实际验证下来,能够把滚动发布过程中的请求错误率降到接近零。
4.4 探针配置不当的典型事故
分享一个真实排过的case。某微服务在滚动更新时,旧Pod不断被LivenessProbe杀掉,新Pod也起不来,整个服务从5个副本跌到1个,线上接口大面积超时。排查了半天,根因是LivenessProbe的 initialDelaySeconds 设成了3秒,periodSeconds 设成了2秒,而应用冷启动时间要40秒。StartupProbe又没配,结果Liveness开始探测时应用还没就绪,直接判死。
这类问题在监控上看不到明显的报错,只会看到Pod反复重启。最好的预防手段是:先配StartupProbe并设置足够大的 failureThreshold,确认启动稳定后,再根据压测数据精确调整LivenessProbe和ReadinessProbe的周期。这个顺序不要反。
5. 配置管理:ConfigMap、Secret与外部配置中心的取舍
5.1 配置外置的标准姿势
Spring Boot的配置管理原则是“代码与配置分离”。本地开发用 application-dev.yml,生产环境不能把配置打包进镜像,否则每次改个数据库地址都要重新构建镜像,完全没有必要。
Kubernetes里最直接的方案是ConfigMap。把application.yml从项目里拿出去,用ConfigMap创建:
bash复制kubectl create configmap spring-demo-config --from-file=application.yml
然后在Deployment里选择挂载为文件,而不是直接用环境变量——因为Spring Boot的配置是层次化的,环境变量的形式很难表达嵌套结构:
yaml复制volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
volumes:
- name: config
configMap:
name: spring-demo-config
最后在启动命令里指定额外的配置位置:
bash复制java -jar app.jar --spring.config.additional-location=/app/config/
这里用的是 additional-location 而不是 location。区别在于前者是“附加”配置源,内置配置中如果某些项没有覆盖,依然会读取打包在jar里的默认值;后者是“替代”配置源,用不好会导致很多默认配置失效。
5.2 敏感信息的三种处理方式
数据库密码、消息队列Token这类敏感信息不要放ConfigMap,ConfigMap本质上只是Base64编码存储,并没有加密能力。敏感信息有三个常用方向:
- Kubernetes Secret:适合初次接入,使用简单,和ConfigMap一样挂载或注入环境变量。
- 外部密钥管理服务:比如云厂商的密钥管理服务,应用启动时通过SDK拉取。
- 配置中心:把配置和密钥都放到配置中心,用客户端动态刷新。
我倾向的做法是:Spring Boot应用先接主流的开源配置中心,把配置和密钥统一管理,K8s侧只需要传入配置中心的地址和认证信息。这个方案的优点是支持配置热更新,应用不用重启就能拿到新配置,这在处理紧急配置变更时非常有用。
5.3 配置变更触发热加载的实践
ConfigMap本身支持热更新——修改ConfigMap后,挂载的文件在几秒内会同步到Pod里。但Spring Boot默认不会监听文件变化,所以改完ConfigMap后通常还是要重启应用。
如果想要改配置后自动滚动重启Pod,业界常用的做法是安装一个能够监听ConfigMap变化并触发Deployment滚动更新的控制器,比如“Reloader”这类开源组件。它检测到ConfigMap或Secret更新后,会自动给对应的Deployment打一个新注解,从而触发滚动发布。
如果你不想引入额外组件,也可以手动给Deployment的template里加一个环境变量或注解,指向配置内容的hash值。每次更新配置时同步更新这个值,Kubernetes会因template变化而触发滚动更新。这是最朴素、最可控的方式,适合配置变更频率不高的场景。
6. 弹性伸缩与滚动更新:让Spring Boot应用真正具备云原生能力
6.1 基于CPU与内存的HPA配置
Kubernetes的弹性伸缩组件(HPA,HorizontalPodAutoscaler)能够根据资源指标自动调整副本数。Spring Boot应用的扩容指标,最常用的是CPU使用率和内存使用率。一个基础的HPA配置长这样:
yaml复制apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: spring-demo-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: spring-demo
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
averageUtilization: 60 的意思是当所有Pod的CPU平均使用率超过60%时开始扩容。实际配置经验是先压测,观察正常情况下CPU的均值,把目标值设在均值的上边一点,避免稍微波动就触发扩容。
内存指标比CPU更难调,因为Java堆内存有GC机制,内存使用率不会随负载平滑变化。如果内存指标设置不当,可能导致频繁扩容缩容。我的做法是:CPU作为默认的弹性伸缩指标,内存指标仅在确认存在内存瓶颈时才启用,并且设置合理的冷却时间。
6.2 滚动更新参数是怎么调出来的
Deployment的滚动更新策略里有两个关键参数:maxSurge 和 maxUnavailable。它们的含义分别是:
maxSurge:滚动更新时,最多允许比期望副本数多出多少个临时副本。默认值是25%,意味着如果期望3个副本,更新时可以最多同时有4个。maxUnavailable:滚动更新时,允许最多有多少个副本不可用。默认也是25%,即更新过程中可以有一个副本先被杀掉。
这两个参数决定了发布的可用性与速度。对Spring Boot这种启动较慢的应用,我建议把 maxUnavailable 设为0,maxSurge 设为1——先起一个新Pod,等它Ready后再停一个旧Pod,空窗期最小,代价是发布过程中会多占用一点资源。如果是内存比较紧张的小集群,就把 maxSurge 调小,接受一定的发布空窗期。
6.3 让Pod平滑上下线:从SLB摘流量到优雅退出
这一小节要澄清一个容易混淆的概念:Pod被删除时,“停止服务”和“摘除流量”是两回事。
流过Ingress和Service的流量,转发目标是后端的Pod IP。Pod进入Terminating状态后,Kubernetes会更新Endpoints,把该Pod的IP从可用列表里去掉。但这个更新需要一点时间,并且如果前端有SLB之类的云负载均衡器,它还有自己的健康检查周期。所以在Pod真正退出之前,很可能会有新请求被转发过来。
解决策略上一章已经提到:preStop里sleep足够的时间,让SLB和Ingress控制器完成端点摘除;同时应用开启优雅停机,保证“漏网”的存量请求能被正常处理完。一个完整的生命周期配置大概长这样:
- 应用层:
server.shutdown=graceful+ 超时时间20秒。 - Pod层:preStop sleep 10秒,
terminationGracePeriodSeconds=35。 - 滚动更新:
maxUnavailable=0,maxSurge=1。
这套组合在多次发布中都验证过效果,基本没有再出现过发布期间接口超时或报错的情况。
7. 上线之后的排障:从CrashLoopBackOff到内存泄漏的排查链路
7.1 CrashLoopBackOff的定位顺序
CrashLoopBackOff是每个用K8s部署Spring Boot的人都一定会遇到的错误。它表示Pod启动后不断崩溃又被重启,重试间隔会越来越长。拿到这个状态,我一般按顺序查三步:
第一步看Pod状态和重启原因:
bash复制kubectl get pod restarts
kubectl describe pod <pod-name>
describe 里如果看到 Last State: Terminated,下面一般有退出码。退出码是137表示被OOMKilled或被外部杀死,130和143多是收到SIGTERM,126/127多在容器里找不到命令或权限问题。
第二步看日志:
bash复制kubectl logs <pod-name> --previous
--previous 是看上一个崩溃的实例的日志,这个参数特别有用。很多应用崩溃前会打印异常堆栈,但日志已经被冲刷掉了,用这个参数能捞回来。
第三步是检查镜像和配置本身。比较隐蔽的问题是镜像启动命令写错、JVM无法解析参数、application.yml被挂载后格式不对导致启动失败。
7.2 内存限制与JVM参数的坑
有段时间我的服务隔三差五就出现一个Pod被OOMKilled,查看监控发现Pod实际内存占用已经逼近limits,但GC日志显示堆内存还有很大余量。原因就是JVM的 -Xmx 和容器limits脱节——堆内存预留不足,Metaspace、线程栈、Direct Buffer这些堆外部分把容器内存撑爆了。
用 -XX:MaxRAMPercentage=75.0 能解决大部分问题,但要记住两点:
- 不要同时设置
-Xmx和MaxRAMPercentage,否则-Xmx优先,比例参数失效。 - Docker/K8s环境和纯物理机上的JVM检测逻辑不同,JDK8u191之前的版本甚至不识别容器CPU限制,升级JDK也是解决问题的关键一步。
如果确认是线程数过多导致的内存增长,可以先用 jcmd 或 jstack 抓线程快照,看看线程栈上有几千个线程,排查连接池或线程池配置有没有失控。K8s环境里可以用 kubectl exec 进入运行中的Pod执行这些命令,前提是基础镜像里带了JDK工具——这也是有人坚持不用瘦身镜像的原因之一。
7.3 日志采集与查看的标准动作
Spring Boot默认把日志打到stdout,Kubernetes直接收集标准输出,所以 kubectl logs 能看到容器日志,但线上集群通常是多副本,日志分散在各Pod里。排查问题时,我会先在Deployment上把所有副本全部跑一遍:
bash复制kubectl logs -l app=spring-demo --tail=200
这样能同时看到所有实例最近的日志。当然,这只是临时手段。真正规范的日志方案是把容器日志采集到统一的日志平台,加上Kubernetes的Pod标签作为查询维度,才能高效地按服务、实例、时间范围检索。在日志采集方案落地前,至少要让每个Pod把日志写到stdout,不给文件路径,因为容器文件系统本来就是临时性的,Pod重建后日志就没了。
7.4 我在这个项目上踩过的三个典型坑
第一个坑是误把LivenessProbe和ReadinessProbe用同一个端口路径。数据库抖动时所有Pod都被kill,服务从3副本直接降到1个,雪崩式故障。后来的标准是前面讲的:Liveness只查进程状态,Readiness查依赖组件健康。
第二个坑是Pod在滚动更新时出现“流量中断”,看现象像是网络问题,实际是优雅停机没配置,旧Pod收到SIGTERM后立刻退出,连正在处理的请求都来不及返回。补上 server.shutdown=graceful 和preStop之后才彻底解决。
第三个坑是镜像里的时区问题。应用部署到K8s后日志时间比北京时间慢八小时,一开始还以为是日志组件bug,最后发现是基础镜像默认UTC时区。在Dockerfile里加了一行时区配置,问题立刻消失。这种问题不会导致线上故障,但排查问题的过程中浪费的时间比预想的多得多。
8. 写在最后的几个建议
这几年的实践经验总结下来,我想给准备把Spring Boot部署到Kubernetes的开发者几个建议。
先保持简单。第一次迁移时不要一次性把所有云原生组件都接进来,先做到“镜像可构建、Deployment可调度、Service可访问、日志可查看”,跑通之后再考虑配置中心、弹性伸缩、服务网格这些进阶能力。每增加一个组件,排障的复杂度就上升一个台阶。
探针和优雅停机一定要先于大规模流量验证之前配置好。这两个能力决定了你在发布时是不是从容的。我看到过太多团队在没有妥善处理这两个点时,直接把服务迁上集群,然后在一次凌晨发布中把线上服务全部搞挂,第二天早上才从日志里发现问题。
监控永远不嫌早。至少在集群一级把CPU、内存、网络、Pod重启次数这些指标采集起来,后续排查问题时才有据可依。应用层面的指标(慢请求、GC次数、线程池状态)再逐步补齐。没有这些数据,K8s排障基本等于盲人摸象。
