简介这是一份面向云原生入门者、容器与编排技术学习者以及希望系统梳理云原生脉络的开发者的PDF资料围绕云原生概念、容器技术演进与集群编排架构展开可用于建立整体认知、准备技术分享和查漏补缺。压缩包内共1个PDF文件大小约3.98MB内容以云原生计算基金会的定义、容器发展历程、容器编排技术架构为主线并涉及服务网格、微服务、不可变基础设施与声明式接口等代表技术。资料还提及监控告警、服务发现、网络接口规范以及无服务器架构、边缘计算等生态方向帮助读者理解从传统云计算到云原生阶段的转变把握容器、编排与服务治理之间的关系。已有205人学习适合入门通读、知识框架搭建和后续专题学习前的背景铺垫。1. 云原生不是“上云”把运维对象从机器换成一份声明式期望状态云原生这三个字被用得最滥的场景是机房搬迁把虚拟机上的应用原样塞进容器改一份 YAML 就宣布改造完成。半年后账单涨了、定位一次故障的时间翻倍团队才反应过来自己只是换了一台更贵的机器。真正的分界线不在容器而在运维对象的改变。过去你运维的是一台台有 IP、有磁盘、会漂移的机器出问题就 SSH 上去看现在你运维的是一份声明式的期望状态由控制循环不断把现实往这个状态上拉。镜像能不能复现、副本能不能自愈、发布能不能回滚、扩容要不要人工值班全都由这一句决定。下面按“前世、今生、落地”三段走先说这条路线是被哪些具体问题一步步逼出来的再用一套能跑起来的最小栈把镜像、编排、探针、弹性、灰度串成一条线最后给迁移评估表和五个真踩过的坑。2. 四个技术拐点镜像、编排、控制循环、可观测性是怎么被逼出来的把云原生当成一堆工具清单去背很快就会迷失在组件名里。它更像是四次被迫的妥协每一次都是上一代做法在规模、在变更频率上撑不住了才长出新的一层。理解这四层各自解决什么问题后面配参数的时候你才知道每个数字在防什么。2.1 拐点一镜像把“环境”变成可版本化的产物早期部署的经典事故是依赖漂移。同一份代码测试环境跑的是某个中间版本的解释器生产环境是另一个运维用配置管理工具把机器状态写成了脚本脚本本身能保证“这次装得一样”却保证不了“三个月后还能装出一样的东西”因为上游仓库早就换版本了。配置管理解决的是机器之间的一致性解决不了时间上的一致性。镜像真正带来的不是打包而是不可变 可寻址。一份镜像构建出来之后用 digest 锁定它永远不会变要改就只能构建新的一份。这一步把“环境”从一台会被人登录、会被手工改配置的机器变成了一个有版本号、能回滚、能 diff 的产物。分层机制则让这件事在经济上成立依赖层几乎不变每次构建只重传最上面那层业务代码。配套的约束也得跟上。容器里应该是单进程模型日志直接打 stdout 由平台收集而不是往容器内写文件再想办法捞出来状态要么外置到数据库和对象存储要么明确挂载持久卷。这些约束不是洁癖是因为容器的生命周期被设计成可以随时被替换——你今天写进容器本地磁盘的东西明天重建就没了。维度传统虚拟机部署镜像 容器部署交付物安装包 一堆部署脚本一份带 digest 的镜像环境差异靠文档和人工核对靠基础镜像统一差异被压到配置层回滚方式重新跑上一版脚本常常回不去换成上一版镜像 digest秒级扩容粒度整台机器分钟级单个副本秒级故障排查入口登录机器看日志文件平台侧日志与指标机器不可登录2.2 拐点二编排把“调度”从人手里拿走有了镜像下一个问题立刻出现这份镜像该跑在哪台机器上、跑几份、某台机器挂了怎么办。早期是用脚本 标签选择来做的运维在发布系统里写“这台机器跑 A那台跑 B”。机器一多这张人肉维护的映射表就成了全公司最容易出错的地方机器下线忘了摘、新机器加进去忘了分配、某台机器上堆了三个吃内存的服务。编排器接管的是两件事一是把“我要几份、每份要多少资源”这件事存下来二是每次调度时按当前集群的真实水位做决策。调度不是随机的它有一套打分逻辑节点剩余资源够不够、有没有打上合适的标签、亲和与反亲和规则满不满足、同一个服务的多个副本有没有被强行摊到不同机架上。这些规则的价值在故障域上体现得最明显——你不写拓扑分布调度器很可能把三个副本塞到同一台机器这台机器一挂服务直接全灭。# 让同一服务的副本尽量摊开在不同节点上避免单机故障导致全挂 topologySpreadConstraints: - maxSkew: 1 # 允许的最大不均衡度1 表示基本平均 topologyKey: kubernetes.io/hostname # 按节点维度打散 whenUnsatisfiable: ScheduleAnyway # 资源不够时降级为尽量不阻塞发布 labelSelector: matchLabels: app: order-apimaxSkew设得太小比如 0 配合 DoNotSchedule在节点数不是副本数整数倍时会直接把 Pod 卡在 Pending这是新手最常撞的墙whenUnsatisfiable用ScheduleAnyway相当于给调度器一个“优先满足、实在不行也别停摆”的台阶。生产上我一般先把 skew 放到 1观察一周再收紧。2.3 拐点三控制循环把运维动作写成代码命令式脚本的根本毛病是它只描述动作不描述结果。“如果副本数少于 3 就加一个”这种脚本跑两次就出错中途被打断就留下半截状态。而声明式控制器描述的是终态“我要 3 个副本”。控制器不停地比对期望与实际不一致就动手动作本身是幂等的跑一百次和跑一次结果一样。这个模式一般叫 level-triggered电平触发对应的旧做法是 edge-triggered边沿触发只在事件发生时响应一次。差别在故障场景下非常致命边沿触发漏掉一个事件就永久错了电平触发下一轮循环会自动纠正回来。沿着这个思路所有运维动作都能被“代码化”证书快到期自动续、数据库从库延迟超标自动摘流量、某个自定义资源被创建后自动拉起一整套依赖。做法是把领域知识写进自定义资源定义和对应的控制器里让平台侧只认终态不认流程。# 判断集群现状和你提交的声明之间差了什么比改完看有没有报错可靠得多 kubectl diff -f deploy/order-api.yaml # 看某个控制器是不是一直在纠正重启次数异常增长通常意味着控制循环在打架 kubectl -n prod rollout status deploy/order-api --timeout120s kubectl -n prod get pods -l apporder-api -o widekubectl diff返回非零退出码就代表有差异可以接进流水线做“计划阶段”的展示让人在合并前就看到这次变更到底动了哪几个字段。rollout status卡住不动八成不是镜像拉取慢而是新副本一直没通过就绪探针——这时候别急着重启先去看事件。2.4 拐点四可观测性把黑匣子打开容器化之后“上机器看看”这条路被彻底堵死了实例随时被替换、IP 随时在变、容器里连 shell 都不一定有。可观测性不是“多装几个监控大盘”而是把三类信号打通指标回答“有没有问题、问题多大”日志回答“具体哪一行出错”链路回答“慢在哪一跳”。方法上我习惯两套并行面向服务的 RED请求速率、错误率、耗时分布面向资源的 USE使用率、饱和度、错误。容器场景里最容易被忽略的是饱和度——CPU 使用率 60% 看起来很健康但如果进程一直在被限流throttling实际响应时间已经烂了这个信号只有专门的指标才有。指标基数是最容易翻车的地方。把用户 ID、请求路径里的动态 ID 直接当标签打进去时间序列数量会指数爆炸最后把监控后端写垮。规矩很简单标签只放枚举值可控的维度服务名、接口名、状态码、实例任何可能无限增长的字符串一律不许进标签。3. 本地跑通一套最小云原生栈从 Dockerfile 到 HPA 的五个步骤原理讲完就该动手了。这一章用一套最小的 Java 服务走完整条链路构建镜像、推仓库、发 Deployment、接流量、配探针和弹性。每一步我都给出可直接抄的片段再说明每个参数在防什么事故。本地用 kind 或者单节点集群都行差别只在 Ingress 那一层。3.1 最小服务与 Dockerfile把构建和运行彻底分开先写一个只暴露一个健康接口和一个业务接口的服务端口 8080。关键是 Dockerfile 要分两阶段构建阶段带全套工具链运行阶段只留 JRE 和产物。不是为了省空间好看是因为构建工具留在运行镜像里等于把一整套攻击面和几百兆无关文件塞进了生产环境。# 阶段一构建。依赖下载单独成层pom 不变时这层直接命中缓存 FROM maven:3.9-eclipse-temurin-17 AS build WORKDIR /src COPY pom.xml . RUN mvn -B -q dependency:go-offline COPY src ./src RUN mvn -B -q clean package -DskipTests # 阶段二运行。只带 JRE不带源码和构建缓存 FROM eclipse-temurin:17-jre WORKDIR /app COPY --frombuild /src/target/app.jar /app/app.jar # 让 JVM 按容器可见内存算堆而不是按宿主机内存 ENV JAVA_TOOL_OPTIONS-XX:MaxRAMPercentage75.0 -XX:UseG1GC -XX:ExitOnOutOfMemoryError EXPOSE 8080 ENTRYPOINT [java,-jar,/app/app.jar]MaxRAMPercentage75.0是这一整篇里最值钱的一行。JVM 默认按宿主机物理内存推算堆大小容器被限制在 1 GiB 时它会以为自己有几十 GiB于是堆一路涨到被内核 OOM Kill而监控面板上显示的内存使用率却一直很“正常”。ExitOnOutOfMemoryError则是让进程在内存耗尽时直接退出交给编排器重启而不是半死不活地卡着继续接流量。多个COPY的顺序也有讲究变化频率低的放前面pom.xml在源码之前就是为了缓存不失效。3.2 构建与推送三个必须固定的参数# 用提交号做标签杜绝 latest 在生产出现 GIT_SHA$(git rev-parse --short HEAD) REGISTRYregistry.internal.example/order # 跨架构构建时显式指定平台避免在 ARM 笔记本上构建出跑不了 x86 的镜像 docker build \ --platform linux/amd64 \ --build-arg BUILD_DATE$(date -u %Y-%m-%dT%H:%M:%SZ) \ -t ${REGISTRY}:${GIT_SHA} . docker push ${REGISTRY}:${GIT_SHA} # 推送后立刻把 digest 记下来部署时用 digest 而不是 tag docker inspect --format{{index .RepoDigests 0}} ${REGISTRY}:${GIT_SHA}三个参数值得单独说。--platform不写的话在 ARM 机器上构建出来的镜像到了 x86 节点会直接exec format error报错信息还很不直观。标签用提交号是为了让“线上跑的是哪份代码”这个问题有一个确定答案latest加默认拉取策略的组合会在回滚时把你坑得很难受第 5 章会细说。部署时用 digest 而不是 tag是因为 tag 可以被覆盖digest 不能——这一步做了才真正兑现了镜像的不可变性。3.3 Deploymentrequests/limits、副本、策略这四组参数apiVersion: apps/v1 kind: Deployment metadata: name: order-api namespace: prod spec: replicas: 3 revisionHistoryLimit: 5 # 保留可回滚的历史版本数 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 先多起 1 个新副本 maxUnavailable: 0 # 老副本一个都不许先停保证容量不塌 selector: matchLabels: { app: order-api } template: metadata: labels: { app: order-api } spec: terminationGracePeriodSeconds: 30 # 留给优雅停机的时间 containers: - name: app image: registry.internal.example/order:${GIT_SHA} ports: [{ containerPort: 8080 }] resources: requests: { cpu: 500m, memory: 1Gi } # 调度依据 limits: { cpu: 2, memory: 1536Mi } # 硬上限 env: - name: JAVA_TOOL_OPTIONS value: -XX:MaxRAMPercentage70.0 readinessProbe: httpGet: { path: /health/ready, port: 8080 } initialDelaySeconds: 20 periodSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: { path: /health/live, port: 8080 } initialDelaySeconds: 60 periodSeconds: 10 failureThreshold: 3requests和limits不是一回事。requests是调度器排座位时看的数写了 500m 就保证这个节点上有 500m 的余量留给你limits是运行时的硬上限超了 CPU 被限流、超了内存被杀。最常见的错误是 requests 写得很小、limits 写得很大结果节点被严重超卖平时没事一到晚高峰所有 Pod 一起抢 CPU谁都跑不动。maxUnavailable: 0配合maxSurge: 1是最稳的一档先起新副本等它就绪了再停老的。代价是发布期间会多占一份资源节点水位紧张的集群上可能起不来新 Pod这时要么加节点要么把 surge 调大、unavailable 调成 1做取舍。terminationGracePeriodSeconds和优雅停机要配合着看。编排器发删除信号后会先给进程一个 TERM等宽限期过再 KILL。如果应用本身没有处理 TERM正在处理的请求会被直接掐断用户侧看到的就是发布期间零星的 502。3.4 Service 与 Ingress把流量接进来apiVersion: v1 kind: Service metadata: name: order-api namespace: prod spec: selector: { app: order-api } ports: - name: http port: 80 targetPort: 8080 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: order-api namespace: prod annotations: nginx.ingress.kubernetes.io/proxy-read-timeout: 30 nginx.ingress.kubernetes.io/proxy-body-size: 8m spec: ingressClassName: nginx rules: - host: order.internal.example http: paths: - path: /api/order pathType: Prefix backend: service: name: order-api port: { number: 80 }Service 只做一件事给一组会不断变化的 Pod 一个稳定的虚拟地址。它的 selector 必须和 Pod 的 label 完全对上对不上时 Service 的 Endpoints 是空的表现是 Ingress 返回 503但 Pod 自己跑得好好的——这个现象和“服务挂了”很像实际只是标签写错了。Ingress 上的两个注解是踩过坑才加的。proxy-read-timeout默认值偏短导出类接口很容易超时proxy-body-size默认 1m上传稍大一点的文件直接 413。这类参数不属于业务代码但线上故障里占比不低。3.5 探针与 HPA让自愈和弹性真正生效apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: order-api namespace: prod spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: order-api minReplicas: 3 maxReplicas: 12 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 65 # 按 requests 的百分比算 behavior: scaleUp: stabilizationWindowSeconds: 0 policies: [{ type: Percent, value: 100, periodSeconds: 60 }] scaleDown: stabilizationWindowSeconds: 300 # 缩容前观察 5 分钟防止抖动 policies: [{ type: Percent, value: 50, periodSeconds: 60 }]这里有个必须讲清楚的点HPA 的 CPU 利用率是相对 requests算的。requests 写 500m实际用了 400m利用率就是 80%哪怕这台机器有 64 核。所以 requests 调小会让扩容变得异常敏感一有流量就疯狂扩调大则永远扩不起来。我的做法是拿稳定期 P95 的实际用量往上浮 30% 作为 requests再把 HPA 阈值定在 65% 左右留出突刺的缓冲。scaleDown的稳定窗口是关键。没有这 5 分钟流量一抖动副本数就上上下下冷启动跟不上用户看到的就是间歇性超时。scaleUp的窗口设 0 是为了抢时间扩容慢比扩容多更伤。探针的分工也要说清readiness决定这个 Pod 要不要接流量liveness决定要不要杀掉重启。把依赖检查数据库连不上、下游超时写进 liveness 是最常见的自伤式配置——下游一抖所有副本被判定为“死了”集体重启服务雪崩。依赖检查放 readinessliveness 只检查进程自身是否还能响应这是条硬规矩。4. 传统应用迁移落地一份 12 要素体检表和四步改造顺序真到迁移这一步最忌讳的是“先上容器再说”。我见过太多团队把一坨有状态、启动要三分钟、日志写本地文件的应用直接塞进容器结果副本数不敢调、发布不敢做、一出问题还得登机器。改造顺序错了后面全是补丁。4.1 先做 12 要素体检别急着改代码云原生的十二条原则本质是一张“这个应用能不能被随时替换”的体检表。我一般拿它做一轮快速评估每一项打“符合 / 部分符合 / 不符合”不符合的项就是改造工作量的来源。检查项判断标准不符合时的典型代价代码库一个仓库一个服务可独立构建改一行要全量发布回滚粒度失控依赖依赖声明在清单里不依赖机器预装换台机器就跑不起来配置配置来自环境变量或配置中心一份镜像只能跑一个环境后端服务数据库、缓存、消息通过地址访问无法横向扩容构建发布构建与运行严格分离镜像里混进构建工具和源码进程无状态、单进程、可随时启停副本数不能大于 1端口绑定自己监听端口不依赖外部容器无法被服务发现接管并发通过多副本水平扩展只能垂直扩成本陡增易处理启动快、能优雅停机发布期间必然掉请求环境等价开发/预发/生产尽量一致“测试没问题”变成口头禅日志当作事件流写 stdout日志留在容器里重建即丢管理进程管理任务一次性执行不常驻定时任务和主服务抢资源这张表填完改造的工作量基本就能估出来了。我的经验是配置外置和日志改造通常占 60% 的工作量但收益也最直接无状态化最难往往涉及会话存储和本地文件需要单独排期。4.2 改造顺序无状态化 → 配置外置 → 健康检查 → 弹性顺序不能反。先把会话从本地内存挪到共享存储再谈多副本先把配置从打包文件里挪到环境变量或配置中心再谈一份镜像多环境跑先有能区分“活着”和“能干活”的健康接口再配探针先有探针和资源声明HPA 才有意义。跳过前面几步直接配 HPA结果就是扩容出来的新副本因为缺配置起不来越扩越糟。无状态化这一步有个折中方案值得说把会话放外部缓存把上传文件放对象存储把定时任务拆成独立进程单独部署。这三件事做完绝大多数业务应用就能安全地跑 3 个以上副本了。配置外置要注意一件事不是所有配置都适合热更新。连接池大小、线程池大小这类参数改了必须重启才能生效硬做成热更新只会让人误以为生效了。我一般把配置分两类能热更新的开关、阈值、限流值走配置中心推送不能的池大小、JVM 参数走环境变量改了就滚动发布。4.3 渐进式切流双跑、灰度、回滚三件套迁移不是一次性切换而是让新旧两套同时跑一段把流量一点点搬过去。我习惯分三段第一段旧系统接 100%新系统接内部测试流量只验证功能和日志第二段拿出 5% 真实流量进新系统观察错误率和耗时分布这一段至少跑满一个业务高峰第三段按 25%、50%、100% 逐步放量每一档都要看指标不看就往下走等于赌博。回滚方案必须在放量之前就写好而不是出事再想。要回滚的东西有三层镜像版本换回上一个 digest、配置版本配置中心的历史版本、数据这一层最麻烦涉及表结构变更时基本回不去。所以数据库变更一律拆成“先加字段兼容双写、再切读、最后删旧字段”三步任何一步都能停在中间状态不被发现。灰度还有一个容易被忽略的细节按什么维度切。按用户 ID 取模最稳同一个用户始终走同一侧体验一致问题也容易复现按随机比例切同一个用户可能一会儿新一会儿旧登录态和缓存都会出问题。5. 避坑与排查云原生落地最容易翻车的五个点下面五条都是我在真实排障里反复遇到的症状和根因往往隔得很远。每一条按“现象 → 原因 → 解决”写能对号入座就直接抄结论。5.1 Pod 被 OOMKilled但监控里内存只用了 60%现象容器反复重启事件里写着退出码 137但内存监控面板的曲线一直在 limit 的六成左右徘徊。原因统计口径不一致。监控采集的是容器工作组内存里的一部分而内核判定 OOM 看的是包括页缓存、堆外内存在内的总量。JVM 应用尤其典型MaxRAMPercentage按宿主机内存算堆堆外还有元空间、直接内存、线程栈加起来轻松越过 limit。Go 和 Node 也有类似问题GC 目标值和实际驻留集不是一回事。解决先把 limit 提到实测峰值的 1.5 倍观察一周同时把MaxRAMPercentage降到 65%70%给堆外留出空间再打开ExitOnOutOfMemoryError让进程主动退出而不是被内核杀。排查阶段用容器内的实际内存指标而不是节点级指标两者差得很远。5.2 滚动更新把自己滚没了readiness 探针配错的三种写法现象一次普通发布新副本一直起不来老副本又被按maxUnavailable停掉了服务出现整段不可用。原因initialDelaySeconds太短应用还没启动完就被判定为未就绪failureThreshold太小一次网络抖动就摘流量更隐蔽的是把 readiness 指向了一个会检查下游依赖的接口下游一慢全集群副本同时被摘流量——这不是发布事故是设计事故。解决initialDelaySeconds按冷启动 P99 再乘 1.5 设置periodSeconds用 5 秒、failureThreshold给 3 次别为了“快速发现故障”把参数压到极限。readiness 接口只检查自身是否完成初始化下游依赖的健康状态放到熔断和降级逻辑里处理。发布前先在预发用同样参数跑一遍能提前暴露大半问题。5.3 回滚回到了“更旧的代码”latest 标签加默认拉取策略现象线上出问题要紧急回滚改回上一个标签Pod 重建了但跑的还是出错的那版代码。原因用了latest或者可变的标签加上节点上已有同名镜像imagePullPolicy: IfNotPresent直接复用了本地缓存。更麻烦的是多个环境的同一个 tag 指向了不同内容谁也说不清线上到底是什么。解决标签一律用提交号或构建号部署时写 digestimagePullPolicy用Always或者干脆不写让 digest 生效。回滚用kubectl rollout undo或者改回上一版 digest回滚后必须验证一次健康检查和关键接口别只看 Pod 是不是 Running。这条规矩定下来之后我们的发布事故定位时间从小时级降到了分钟级。5.4 CPU 没跑满却响应变慢limit 导致的限流现象节点 CPU 使用率 40%服务 P99 耗时却从 80ms 涨到 900ms压测和线上都是同一个现象。原因容器被 CPU limit 限流了。limit 是硬上限超过就 throttling进程被强制挂起等下一个周期这段时间请求就卡着。使用率 40% 是节点维度和单个容器的限流完全是两个指标。解决先看容器的限流指标确认是不是这个问题确认之后要么把 limits 提到 requests 的 23 倍要么对延迟敏感的服务干脆不设 CPU limit、只设 requests 保住调度。我的默认做法是 CPU 只设 requests 不设 limits内存两个都设——内存超了是 OOM性质完全不同。5.5 改了配置没生效挂载与环境变量的差别现象在配置中心改了参数、也更新了对应的配置对象重启了应用行为还是旧的。原因用环境变量注入的配置值在容器启动时就固化了更新配置对象不会影响已运行的容器而且有些应用框架在启动时把配置读进了内存改文件也没用。用卷挂载的配置会在一段时间后同步到容器里但应用不重新读文件照样无效。解决先确认注入方式。环境变量类的配置更新后必须滚动重启才生效可以在配置对象上加一个校验和注解触发滚动挂载类的配置应用要监听文件变更或者定期重载否则一样没效果。我倾向于把“改了必须重启”这类配置直接写在配置项的注释里减少沟通成本。6. 验收口径用四个指标判断这次云原生改造到底值不值改造做完最容易陷入的错觉是“Pod 都是 Running成了”。Running 只说明进程活着说明不了这次改造有没有价值。我一般用四个可量化的口径来验收每个都能从现有监控里直接取到。口径具体指标参考目标交付速度从合并代码到生产可用的中位耗时从小时级降到 15 分钟以内变更风险发布引发的故障占比、平均回滚耗时回滚 2 分钟内完成资源效率单位请求的成本、节点平均利用率利用率从 15% 提到 40% 以上恢复能力单实例故障的业务影响时长单副本挂掉无用户可感知影响四个口径里我更看重第二和第四因为它们直接对应“敢不敢频繁发布”这件事。前面那些技术选型再漂亮如果团队还是一个月发一次版、出问题还是手工登机器重启那这次改造就只完成了搬迁。验收脚本我常备一个跑一遍就能把关键状态拉齐比翻好几个页面快#!/usr/bin/env bash set -euo pipefail NS${1:-prod} APP${2:-order-api} echo 副本是否达到期望 kubectl -n $NS get deploy $APP -o jsonpath{.spec.replicas}{ 期望 / }{.status.readyReplicas}{ 就绪\n} echo 最近 10 分钟内重启过的容器 kubectl -n $NS get pods -l app$APP \ -o jsonpath{range .items[*]}{.metadata.name}{\t}{.status.containerStatuses[0].restartCount}{\n}{end} echo 是否有 Pod 处于非 Running kubectl -n $NS get pods -l app$APP --field-selectorstatus.phase!Running echo 最近 20 条事件按时间倒序重点看 FailedMount/OOMKilled/Unhealthy kubectl -n $NS get events --sort-by.lastTimestamp | tail -n 20 echo HPA 当前副本与目标 kubectl -n $NS get hpa $APP -o jsonpath{.status.currentReplicas}{ 当前 / }{.status.desiredReplicas}{ 期望\n}脚本里第五段最容易被忽略但最有信息量HPA 的当前副本数和期望副本数长期不一致说明扩容跟不上或者资源不够调度光看 Pod 是 Running 发现不了。第二段的重启次数则是最灵敏的“隐性故障”信号重启数缓慢上涨往往比一次明显的故障更值得追。最后说个我自己的习惯每次做完一轮迁移我会把当次的探针参数、requests/limits、HPA 阈值和当时的流量曲线一起存档标注清楚为什么是这几个数。半年后再看这几个数字是拍脑袋定的还是有依据的一目了然。云原生这套东西参数多、默认值坑多靠记忆是靠不住的能留下来的只有当时写下的记录。希望帮到你。本文还有配套的精品资源点击获取