云原生架构落地实践:Kubernetes 生产级配置与 Service Mesh 流量治理

目录

    • [一、资源配置:requests、limits 与 QoS](#一、资源配置:requests、limits 与 QoS)
      • [1.1 两个参数的真实含义](#1.1 两个参数的真实含义)
      • [1.2 CPU limits 的争议](#1.2 CPU limits 的争议)
      • [1.3 JVM 在容器里的内存](#1.3 JVM 在容器里的内存)
    • 二、探针:三种探针各管一件事
    • [三、优雅发布:消除发布期间的 502](#三、优雅发布:消除发布期间的 502)
      • [3.1 问题根源:两件事并行发生](#3.1 问题根源:两件事并行发生)
      • [3.2 解决方案](#3.2 解决方案)
      • [3.3 滚动更新参数](#3.3 滚动更新参数)
    • 四、高可用:不要把鸡蛋放在一个篮子里
      • [4.1 拓扑分布](#4.1 拓扑分布)
      • [4.2 PodDisruptionBudget](#4.2 PodDisruptionBudget)
    • [五、弹性伸缩:HPA 的计算方式与调优](#五、弹性伸缩:HPA 的计算方式与调优)
      • [5.1 HPA 怎么算副本数](#5.1 HPA 怎么算副本数)
      • [5.2 用 behavior 控制扩缩节奏](#5.2 用 behavior 控制扩缩节奏)
      • [5.3 HPA 的局限](#5.3 HPA 的局限)
    • [六、Service Mesh:解决什么问题,付出什么代价](#六、Service Mesh:解决什么问题,付出什么代价)
      • [6.1 它解决的问题](#6.1 它解决的问题)
      • [6.2 Istio 流量治理示例](#6.2 Istio 流量治理示例)
      • [6.3 代价](#6.3 代价)
      • [6.4 引入 Mesh 前的自查](#6.4 引入 Mesh 前的自查)
    • [七、交付:GitOps 与渐进式发布](#七、交付:GitOps 与渐进式发布)
    • 八、生产就绪检查清单
    • 九、总结
    • 参考资料

适合谁读:正在把 Java / Spring Boot 服务迁移到 Kubernetes,或者已经上了 K8s 但发布时偶发 502、Pod 频繁 OOMKilled 的后端与运维工程师。

解决什么问题 :把应用打成镜像、写个 Deployment 跑起来,只需要半天;但让它在 K8s 上稳定地跑------发布时不报错、节点故障不掉线、流量上涨能扩容、内存不被 OOMKilled------需要处理一长串细节。本文以一个 Spring Boot 订单服务上 K8s 为主线,逐项讲清楚这些细节背后的机制,最后讨论 Service Mesh 到底什么时候值得引入。

本文是"技术架构实战"系列的第 4 篇。应用层的限流、熔断与重试设计见 高并发高可用系统设计:限流、熔断、降级与多级缓存实战指南,本文第六节会讨论它们与 Service Mesh 的重叠问题。

一、资源配置:requests、limits 与 QoS

1.1 两个参数的真实含义

  • requests:调度依据。调度器保证节点上所有 Pod 的 requests 之和不超过节点可分配资源;CPU requests 还决定了 CPU 争抢时的权重;
  • limits :运行时上限。CPU 超过 limits 会被 CFS 限流(throttling) ,内存超过 limits 会被内核 OOMKill。

由二者关系决定 Pod 的 QoS 等级,节点内存紧张时按 BestEffort → Burstable → Guaranteed 的顺序驱逐:

QoS 条件 适用
Guaranteed 所有容器都设置了 CPU 和内存,且 requests = limits 核心在线服务、数据库
Burstable 至少一个容器设置了 requests 或 limits,但不满足 Guaranteed 大多数业务服务
BestEffort 都没设置 只适合可随时丢弃的任务,生产环境不建议

1.2 CPU limits 的争议

CFS 限流按 100ms 周期计算配额。设置 limits.cpu: 2 意味着每 100ms 内最多使用 200ms 的 CPU 时间。一个有 8 个活跃线程的 Java 服务,可能在周期前 25ms 就用完了配额,剩下 75ms 全部被挂起,表现为 P99 延迟周期性地出现几十毫秒的毛刺,而平均 CPU 使用率看起来还很低。

因此对延迟敏感的服务,常见的两种做法是:

  • 设置 CPU requests、不设 CPU limits,依靠 requests 保证份额,允许利用节点空闲 CPU(需要集群层面有合理的超卖控制);
  • 或者设置 limits,但通过 container_cpu_cfs_throttled_periods_total 监控限流比例,超过 10%~20% 就调高。

内存则必须设置 limits,而且建议 requests = limits:内存不可压缩,超卖内存的后果是 OOMKill 或节点被驱逐。

1.3 JVM 在容器里的内存

Java 服务最常见的问题:堆设置了 -Xmx2g,容器 limits 也是 2Gi,结果频繁 OOMKilled。原因是 JVM 进程内存 ≠ 堆内存:

进程内存 ≈ 堆 + 元空间 + 线程数 × 1 MB + 直接内存 + 代码缓存 + GC 开销 \text{进程内存} \approx \text{堆} + \text{元空间} + \text{线程数} \times 1\text{MB} + \text{直接内存} + \text{代码缓存} + \text{GC 开销} 进程内存≈堆+元空间+线程数×1MB+直接内存+代码缓存+GC 开销

推荐做法:

yaml 复制代码
resources:
  requests: { cpu: "1", memory: 2Gi }
  limits:   { memory: 2Gi }
env:
  - name: JAVA_TOOL_OPTIONS
    value: >-
      -XX:MaxRAMPercentage=70.0
      -XX:MaxMetaspaceSize=256m
      -XX:MaxDirectMemorySize=256m
      -XX:+ExitOnOutOfMemoryError
  • 使用 MaxRAMPercentage 让堆跟随容器 limits 变化(JDK 8u191 及以上支持容器感知),堆占 60%~75%,留出非堆空间;
  • ExitOnOutOfMemoryError 让 JVM 在堆 OOM 时直接退出,由 K8s 重启,而不是半死不活地继续接流量;
  • 内存使用以 container_memory_working_set_bytes 为准,这是 kubelet 判断驱逐和 OOM 时参考的指标。

二、探针:三种探针各管一件事

探针 失败后果 用来回答的问题
startupProbe 超过阈值后重启容器 应用启动完成了吗?(成功前不执行另外两个探针)
readinessProbe 从 Service 的 Endpoints 中摘除,不重启 现在可以接收流量吗?
livenessProbe 重启容器 进程是否陷入了无法自愈的状态?

最常见、也是最危险的错误:在 liveness 探针里检查数据库、Redis 等依赖。数据库抖动 10 秒,所有 Pod 的 liveness 同时失败,K8s 把整个服务的所有实例一起重启,一次依赖抖动被放大成全面故障,而重启本身对修复数据库毫无帮助。

正确配置(Spring Boot Actuator 已内置 liveness 与 readiness 两个健康组):

yaml 复制代码
startupProbe:
  httpGet: { path: /actuator/health/liveness, port: 8081 }
  periodSeconds: 5
  failureThreshold: 36          # 最多允许 180 秒启动时间
readinessProbe:
  httpGet: { path: /actuator/health/readiness, port: 8081 }
  periodSeconds: 5
  failureThreshold: 3
livenessProbe:
  httpGet: { path: /actuator/health/liveness, port: 8081 }
  periodSeconds: 10
  failureThreshold: 6           # 连续 60 秒失败才重启,避免 Full GC 时误杀
yaml 复制代码
# application.yml
management:
  server.port: 8081             # 管理端口与业务端口分离,避免业务线程池打满时探针也超时
  endpoint.health.probes.enabled: true
  endpoint.health.group.readiness.include: readinessState,db

liveness 只反映进程自身状态;readiness 可以包含关键依赖,但要谨慎:如果所有实例都依赖同一个数据库,数据库故障时所有实例同时变为未就绪,效果和全部宕机一样。是否把依赖纳入 readiness,取决于"摘除流量"是否真的有助于用户体验。

三、优雅发布:消除发布期间的 502

3.1 问题根源:两件事并行发生

删除一个 Pod 时,K8s 同时做两件事:
Endpoints/kube-proxy/网关 应用容器 kubelet API Server Endpoints/kube-proxy/网关 应用容器 kubelet API Server #mermaid-svg-iHaGp2T1TDPJmTMW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iHaGp2T1TDPJmTMW .error-icon{fill:#552222;}#mermaid-svg-iHaGp2T1TDPJmTMW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iHaGp2T1TDPJmTMW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iHaGp2T1TDPJmTMW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iHaGp2T1TDPJmTMW .marker.cross{stroke:#333333;}#mermaid-svg-iHaGp2T1TDPJmTMW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iHaGp2T1TDPJmTMW p{margin:0;}#mermaid-svg-iHaGp2T1TDPJmTMW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iHaGp2T1TDPJmTMW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-iHaGp2T1TDPJmTMW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iHaGp2T1TDPJmTMW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-iHaGp2T1TDPJmTMW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-iHaGp2T1TDPJmTMW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-iHaGp2T1TDPJmTMW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-iHaGp2T1TDPJmTMW .sequenceNumber{fill:white;}#mermaid-svg-iHaGp2T1TDPJmTMW #sequencenumber{fill:#333;}#mermaid-svg-iHaGp2T1TDPJmTMW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-iHaGp2T1TDPJmTMW .messageText{fill:#333;stroke:none;}#mermaid-svg-iHaGp2T1TDPJmTMW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iHaGp2T1TDPJmTMW .labelText,#mermaid-svg-iHaGp2T1TDPJmTMW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-iHaGp2T1TDPJmTMW .loopText,#mermaid-svg-iHaGp2T1TDPJmTMW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-iHaGp2T1TDPJmTMW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iHaGp2T1TDPJmTMW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-iHaGp2T1TDPJmTMW .noteText,#mermaid-svg-iHaGp2T1TDPJmTMW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-iHaGp2T1TDPJmTMW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iHaGp2T1TDPJmTMW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iHaGp2T1TDPJmTMW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iHaGp2T1TDPJmTMW .actorPopupMenu{position:absolute;}#mermaid-svg-iHaGp2T1TDPJmTMW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-iHaGp2T1TDPJmTMW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iHaGp2T1TDPJmTMW .actor-man circle,#mermaid-svg-iHaGp2T1TDPJmTMW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-iHaGp2T1TDPJmTMW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 各节点 iptables/IPVS、Ingress、 注册中心更新需要若干秒 若应用立即退出, 仍在路上的请求会失败 Pod 标记为 Terminating 从 Endpoints 中移除该 Pod 执行 preStop,然后发送 SIGTERM

这两条路径没有先后保证。如果应用收到 SIGTERM 立刻关闭,而 kube-proxy 和 Ingress 还没完成更新,新请求仍会被转发到这个已经关闭的 Pod,结果就是 502 或连接被拒绝。

3.2 解决方案

yaml 复制代码
spec:
  terminationGracePeriodSeconds: 45     # 必须大于 preStop + 应用优雅停机时间
  containers:
    - name: order-service
      lifecycle:
        preStop:
          exec:
            command: ["sh", "-c", "sleep 10"]   # 等待各处摘除流量
yaml 复制代码
# application.yml
server:
  shutdown: graceful                     # 停止接收新请求,等待处理中的请求完成
spring:
  lifecycle:
    timeout-per-shutdown-phase: 25s

时间线:preStop 等待 10 秒(这期间流量逐步摘除)→ 收到 SIGTERM → Spring 等待处理中的请求完成(最多 25 秒)→ 退出。总计 35 秒,小于 45 秒的宽限期。

1.30 及以上版本可以直接使用 preStop.sleep.seconds,不再依赖镜像里有 sleep 命令。

如果服务还注册在 Nacos、Eureka 这类注册中心,preStop 中应该先主动注销实例再 sleep,因为客户端缓存刷新通常比 kube-proxy 更慢。

3.3 滚动更新参数

yaml 复制代码
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 25%
    maxUnavailable: 0         # 先起新 Pod、就绪后再停旧 Pod,容量不下降
minReadySeconds: 10           # 新 Pod 就绪后再观察 10 秒才算可用,过滤"就绪后立刻崩溃"

四、高可用:不要把鸡蛋放在一个篮子里

4.1 拓扑分布

3 个副本恰好被调度到同一节点,节点一挂服务就全挂了。用拓扑分布约束把副本分散到不同可用区和节点:

yaml 复制代码
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway   # 尽量跨可用区,资源不足时允许不均匀
    labelSelector: { matchLabels: { app: order-service } }
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: DoNotSchedule    # 严格要求跨节点
    labelSelector: { matchLabels: { app: order-service } }

4.2 PodDisruptionBudget

节点升级、缩容时执行的 kubectl drain 属于自愿中断。没有 PDB 时,drain 可能同时驱逐同一服务的多个副本:

yaml 复制代码
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-service
spec:
  minAvailable: 2               # 驱逐时至少保留 2 个可用副本
  selector:
    matchLabels: { app: order-service }

注意:副本数为 2 时设置 minAvailable: 2,会导致 drain 永远无法完成,运维升级节点时会被卡住。PDB 要和副本数一起设计。

五、弹性伸缩:HPA 的计算方式与调优

5.1 HPA 怎么算副本数

期望副本数 = ⌈ 当前副本数 × 当前指标值 目标值 ⌉ \text{期望副本数} = \left\lceil \text{当前副本数} \times \frac{\text{当前指标值}}{\text{目标值}} \right\rceil 期望副本数=⌈当前副本数×目标值当前指标值⌉

例如目标 CPU 使用率 60%(相对于 requests ),当前 4 个副本平均 90%:ceil(4 × 90 / 60) = 6。

这个公式揭示了两个容易忽略的点:

  1. CPU 使用率是相对于 requests 计算的,requests 设得过小,HPA 会频繁扩容;不设 requests,HPA 根本无法工作;
  2. Java 服务启动时 JIT 编译会使 CPU 飙高,新 Pod 刚启动就可能触发下一轮扩容。HPA 在 Pod 启动初期会忽略部分 CPU 样本,但仍建议配合 startupProbe 和扩容稳定窗口使用。

5.2 用 behavior 控制扩缩节奏

yaml 复制代码
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service
spec:
  scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: order-service }
  minReplicas: 4
  maxReplicas: 40
  metrics:
    - type: Resource
      resource:
        name: cpu
        target: { type: Utilization, averageUtilization: 60 }
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - { type: Percent, value: 100, periodSeconds: 30 }   # 30 秒内最多翻倍
    scaleDown:
      stabilizationWindowSeconds: 300                        # 5 分钟内取最高建议值,防抖动
      policies:
        - { type: Percent, value: 10, periodSeconds: 60 }    # 每分钟最多缩 10%

原则:扩容要快,缩容要慢。

5.3 HPA 的局限

  • HPA 是被动的 :从流量上涨到新 Pod 就绪,需要指标采集(约 15~30 秒)+ 调度 + 拉镜像 + 应用启动(Java 服务常常 30~90 秒),总计可能超过 2 分钟。大促、秒杀这类可预期的峰值必须提前扩容,不能依赖 HPA;
  • CPU 不一定是最佳指标:I/O 密集型服务的瓶颈在下游,CPU 不高但延迟已经很高。可以通过 Prometheus Adapter 或 KEDA,基于 QPS、队列积压长度扩缩;
  • 扩容 Pod 会放大下游压力:订单服务从 4 个扩到 40 个,数据库连接数也会增加 10 倍。maxReplicas 要结合下游容量来设置。

六、Service Mesh:解决什么问题,付出什么代价

6.1 它解决的问题

Service Mesh 把服务间通信的通用能力从应用代码中剥离,下沉到基础设施层:

能力 没有 Mesh 时 有 Mesh 时
mTLS 加密与身份认证 每个服务自己管理证书,基本没人做 自动签发、轮换证书,基于身份授权
超时、重试、熔断 每种语言各自引入 SDK,配置分散 统一在代理层配置
金丝雀发布 依赖网关或注册中心的定制能力 按权重、按请求头路由
黄金指标与链路 依赖各语言埋点 代理层自动产生请求量、错误率、延迟

多语言技术栈 和零信任安全要求,是引入 Service Mesh 最有说服力的两个理由。如果团队是纯 Java 技术栈、已经用 Spring Cloud 解决了服务治理,引入 Mesh 的收益会小很多。

6.2 Istio 流量治理示例

yaml 复制代码
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: order-service
spec:
  hosts: [order-service]
  http:
    - match:
        - headers:
            x-canary: { exact: "true" }
      route:
        - destination: { host: order-service, subset: v2 }
    - route:
        - destination: { host: order-service, subset: v1 }
          weight: 90
        - destination: { host: order-service, subset: v2 }
          weight: 10
      timeout: 2s
      retries:
        attempts: 2
        perTryTimeout: 800ms
        retryOn: connect-failure,refused-stream,unavailable
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: order-service
spec:
  host: order-service
  trafficPolicy:
    connectionPool:
      http:
        http2MaxRequests: 1000
        http1MaxPendingRequests: 100
    outlierDetection:                 # 被动健康检查:剔除异常实例
      consecutive5xxErrors: 5
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 50          # 最多剔除一半实例,避免全部被剔除
  subsets:
    - name: v1
      labels: { version: v1 }
    - name: v2
      labels: { version: v2 }

需要注意的地方:

  • 重试只在一层做:如果应用里已经有 Resilience4j 或 Feign 重试,Mesh 层再配置重试,流量会成倍放大。迁移到 Mesh 时要同步移除应用层的重试配置;
  • retryOn 不要包含 5xx,非幂等的写请求(如下单)在服务端已经处理成功但返回 5xx 时,重试会导致重复下单;
  • maxEjectionPercent 要设置上限,否则下游整体故障时所有实例都被剔除。

6.3 代价

  • 资源开销:Sidecar 模式下每个 Pod 多一个 Envoy 容器。根据 Istio 官方的性能测试数据,每 1000 RPS 大约消耗 0.2 vCPU 和数十 MB 内存,Pod 数量多的集群总开销可观;
  • 延迟:每一跳多两次代理转发,官方测试 P90 延迟增加约在毫秒级;
  • 启动顺序 :应用比 Sidecar 先启动时,启动阶段的出站请求会失败,需要配置 holdApplicationUntilProxyStarts: true;K8s 1.28+ 的原生 Sidecar 容器可以从根本上解决启动和退出顺序问题;
  • 排障复杂度 :一个 503 可能来自应用、本地 Envoy、对端 Envoy、控制面下发的配置,排查需要掌握 istioctl proxy-config、Envoy access log 等工具;
  • 控制面升级:Istio 版本迭代快,升级需要灰度(revision-based 升级),本身就是一项持续的运维工作。

Istio 的 Ambient 模式(1.24 起 GA)用节点级的 ztunnel 处理 L4 和 mTLS,按需部署 waypoint 代理处理 L7,不再为每个 Pod 注入 Sidecar,能显著降低资源开销和运维复杂度,新建集群值得优先评估。

6.4 引入 Mesh 前的自查

  1. 是否有多语言服务,或者有明确的服务间加密、零信任合规要求?
  2. 团队是否有人能读懂 Envoy 配置、能在 Mesh 出问题时定位?
  3. 是否愿意为 Sidecar 支付额外的资源成本?
  4. 现有的服务治理(Spring Cloud、Dubbo)是否已经足够?

如果前两个问题的答案都是否定的,建议暂缓引入。

七、交付:GitOps 与渐进式发布

  • GitOps :所有 K8s 清单放在 Git 仓库,由 Argo CD 或 Flux 持续把集群状态同步到 Git 中声明的状态。好处是变更可审计、可回滚(回滚就是 git revert),且杜绝了手工 kubectl edit 导致的配置漂移;
  • 渐进式发布:Argo Rollouts 或 Flagger 可以与 Istio 配合,按"5% → 25% → 50% → 100%"逐步切流,每一步自动查询 Prometheus 的错误率和延迟,指标不达标时自动回滚;
  • 镜像不可变 :镜像标签使用 Git commit SHA,不使用 latest,确保每次部署可追溯、可复现。

八、生产就绪检查清单

  • 内存 requests = limits,JVM 使用 MaxRAMPercentage 并为非堆内存留足空间
  • 是否监控 CPU 限流比例,延迟敏感服务是否评估过去掉 CPU limits
  • 配置了 startupProbe;liveness 不检查外部依赖
  • preStop + 优雅停机 + 足够的 terminationGracePeriodSeconds
  • 滚动更新 maxUnavailable: 0,设置 minReadySeconds
  • 副本跨节点、跨可用区分布,配置了 PDB 且不会卡住 drain
  • HPA 设置了 behavior,maxReplicas 与下游容量匹配,大促前提前扩容
  • 重试只在一层配置(应用层或 Mesh 层),且只重试幂等请求
  • 部署清单纳入 Git 管理,镜像标签不可变

九、总结

云原生的核心不是"用上了 K8s",而是让应用的行为与平台的机制正确配合:

  1. 资源配置要理解 CFS 限流和 JVM 内存模型;
  2. 探针要区分"活着"和"能接流量",不让依赖故障放大为全面重启;
  3. 优雅发布要承认"摘流量"和"停进程"是并行发生的;
  4. 弹性伸缩是被动的,可预期峰值要提前准备;
  5. Service Mesh 是解决多语言和零信任问题的有力工具,但它的成本是实实在在的。

回到开头的四个现象:发布时 502,是因为没有 preStop 等待摘流量;节点故障整体掉线,是因为副本没有跨节点分布;流量上涨扩容不及时,是因为 HPA 本身是被动的;频繁 OOMKilled,是因为把堆大小等同于进程内存。每个现象背后都有一个明确的机制,理解机制比记住配置更重要。

如果你的服务之间通过消息队列协作,消费者的优雅停机与位点提交同样需要配合设计,可参考 事件驱动架构实战:可靠投递、幂等消费、消息顺序与MQ选型。

参考资料

相关推荐
小坏讲微服务2 小时前
刚发布一个月的JDK 27,为什么我建议你立刻升级?
java·开发语言·spring boot·spring·架构·求职招聘·springboot4
迪康妍妍2 小时前
终端管控实操:封堵网盘与IM外发通道的落地思路
运维·网络·安全·架构·安全性测试
光影少年5 小时前
AI Agent、自动化与未来工作
人工智能·算法·架构
xianyuCcCcCCCcc6 小时前
Ceph 分布式存储:核心架构与集群部署
分布式·ceph·架构
东风微鸣7 小时前
K8s 最佳实践已更新:请使用 Node Swap!
kubernetes
小天源7 小时前
智叟云集系统整体架构解析:多语言 API 接入、异步监控与本地化部署
架构·数据采集·数据中台·ai监测系统·geo数据监测
真上帝的左手7 小时前
10. 软件设计&架构-分布式文件存储系统
分布式·架构·应用架构
弈栈录7 小时前
大模型服务抽象:兼容 OpenAI、通义千问与本地模型
java·后端·架构
CCblandy8 小时前
一致性哈希到底省了多少搬迁?本机实测 3 轮:取模 6→7 搬 85.8%,V=128 只搬 14.3%,虚拟节点太少会被倾斜骗
后端·python·云原生·性能优化·架构