Skip to content

微服务治理实战:灰度、混沌与 K8s 落地

本文是微服务架构系统学习系列的 L3 实战篇。前置:28. 注册发现与配置中心32. 可观测性三支柱。 学完可以配合面试题食用:微服务发布与灰度服务网格与 IstioK8s 基础

为什么需要微服务治理

微服务拆完之后,服务数从个位涨到几十上百,单靠人工运维已经管不过来。一个改配置、一个发布顺序错、一个依赖超时,都可能引发雪崩。治理就是把这套"管不过来"的事变成自动化的规则和流程——灰度发布让你敢上线,混沌工程让你知道上线会出什么问题,K8s 把这些规则变成声明式配置。

三个治理维度各自解决不同痛点:发布防故障扩散,混沌提前暴露隐患,平台把前两者落地到基础设施。

发布策略谱系:从滚动到灰度

生产环境的上线不能停服,也不能一口气全换。常见发布策略按风险递增排列:

mermaid
graph LR
    A[滚动更新] --> B[蓝绿部署]
    B --> C[灰度/金丝雀]
    C --> D[全链路灰度]

滚动更新(Rolling Update):分批替换实例,每批就绪再下一批。K8s Deployment 默认策略,简单但缺乏精准控制——新版本有 bug 可能已经污染了一批流量。

蓝绿部署(Blue-Green):同时维护两套完整环境,切流量时一次性从蓝切到绿。回滚快(切回 DNS 即可),但资源成本翻倍,且数据库兼容性问题容易被忽略。

灰度/金丝雀(Canary):只让一小部分流量(比如 5% 的用户/请求)走新版本,观察一段时间无异常后再全量。流量切分有两种实现方式:

  • 网关权重:Nginx / Spring Cloud Gateway 按 weight 分配流量到不同后端
  • 标签路由:根据请求头 / Cookie / 用户 ID 哈希路由到指定版本,精确到用户级别

灰度发布的核心是可观测性+快速回滚——灰度期间如果错误率上升或 P50 延迟翻倍,必须能在 1 分钟内切回旧版本。

全链路灰度:流量标记的透传

单服务灰度容易,但一次发布可能涉及 A->B->C 三条链路。如果 A 灰度了,B 和 C 还是旧版,A 的新请求打到 B 的旧接口可能协议不兼容。

全链路灰度的解决思路:给请求打标,标记沿调用链透传,每个服务根据标记路由到对应版本

mermaid
sequenceDiagram
    participant GW as 网关
    participant A as 服务A(v2)
    participant B as 服务B(v2)
    participant C as 服务C(v1)
    Note over GW: 请求头 x-version=gray
    GW->>A: x-version=gray
    A->>B: 传递 x-version=gray
    B->>C: 传递 x-version=gray
    C-->>B: 返回(v1响应)
    B-->>A: 返回
    A-->>GW: 返回

实现步骤:

  1. Nacos 元数据标记:在 Nacos 注册时给实例打标签,如 version=grayversion=base
  2. 网关层选择:Spring Cloud Gateway 根据请求头(x-version: gray)路由到对应标签的服务实例
  3. Feign 拦截器传递:用 RequestInterceptor 把灰度标记从上游请求复制到下游 RPC 调用
  4. 下游按标签路由:每个服务拿到标记后,从 Nacos 拉取对应标签的实例列表

这样一条请求从网关到最底层,全程走灰度版本;没有标记的请求默认走 base 版本。

混沌工程:主动找故障

混沌工程不是随便搞破坏,而是有假设地验证系统韧性。三步走:

  1. 稳态假设:定义系统正常状态(如 P99 延迟 < 200ms、错误率 < 0.1%)
  2. 注入故障:在可控范围内模拟真实故障(CPU 飙高、网络延迟、杀 Pod)
  3. 验证假设:故障期间的稳态指标是否达标?不达标说明系统有薄弱点

ChaosBlade 常用场景

bash
# CPU 满载(模拟计算密集型服务被攻击)
blade create cpu load --cpu-percent 90 --timeout 60

# 网络延迟 3000ms(模拟跨机房网络抖动)
blade create network delay --time 3000 --interface eth0

# 杀指定 Pod(模拟节点宕机)
blade create k8s pod-kill --namespace prod --labels app=order-service

混沌工程的关键不是"能扛住所有故障",而是建立故障恢复的预期——比如"单个 Pod 被杀后 30 秒内应恢复",如果实际恢复用了 2 分钟,说明就绪探针或 HPA 配置有问题。

K8s 落地:探针与优雅停机

微服务跑在 K8s 上,最基本的治理是让 K8s 知道服务"活没活"。

三种探针

  • livenessProbe:检查容器是否还活着(死锁了返回失败,K8s 重启容器)
  • readinessProbe:检查服务是否准备好接收流量(刚启动或正在重载配置时返回失败,K8s 从 Service 摘掉该 Pod)
  • startupProbe:慢启动服务专用,给初始化留足时间

优雅停机流程

K8s 发 SIGTERM → 服务拦截信号 → 关闭新请求入口 → 处理完进行中请求(wait 30s)→ 退出
yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: order-service
        image: order-service:v2
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 15
        lifecycle:
          preStop:
            exec:
              command: ["sh", "-c", "sleep 30"]

配置要点:

  • readinessProbe 周期短(5s),检测快;livenessProbe 周期长(15s),避免误杀
  • preStop sleep 30:给 K8s 从 Service Endpoints 中移除 Pod 的时间,防止在 SIGTERM 瞬间还有流量打进来
  • Spring Boot 的 spring.lifecycle.timeout-per-shutdown-phase=30s 配合 server.shutdown=graceful

治理度量:用数据驱动优先级

治理不是一次性做完,是个持续投入的过程。用四类指标定优先级:

指标含义什么算好
变更失败率发布导致故障的比例< 15%
MTTR(平均修复时间)从故障到恢复< 30 min
回滚时长确认回滚到完成< 5 min
错误预算消耗SLO 允许的故障时间每月消耗 < 20%

实践建议:优先治"回滚慢"和"MTTR 长"的问题,因为这俩直接影响线上故障的止损速度。灰度发布和混沌工程先做最核心的 3-5 个服务,不要一口气铺全量。

动手实操:全链路灰度配置

以下是一个最小可运行的全链路灰度配置,包含网关、Nacos 元数据、Feign 拦截器。

1. 网关路由(Spring Cloud Gateway)

yaml
# application.yml
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - AddRequestHeader=x-version, ${request.headers.x-version}

2. Nacos 注册时打版本标签

yaml
# bootstrap.yml
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          version: gray  # 灰度实例打 gray,基线打 base

3. Feign 拦截器传递灰度标

java
@Component
public class GrayTagInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attrs = 
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attrs != null) {
            String version = attrs.getRequest().getHeader("x-version");
            if (version != null) {
                template.header("x-version", version);
            }
        }
    }
}

4. 下游服务调用时按标签选实例

java
// 获取所有实例,再按 metadata key=version 过滤出灰度版本
List<Instance> allInstances = namingService.selectInstances(
    "order-service", "DEFAULT_GROUP", true);
List<Instance> instances = allInstances.stream()
    .filter(inst -> "gray".equals(inst.getMetadata().get("version")))
    .collect(Collectors.toList());

这个配置完整跑通后,灰度流量就能从网关一路透传到最底层服务。

常见误区与小结

  • 灰度比例不等于风险比例:5% 的灰度流量如果触发的是数据库死锁,影响范围会扩散到全量。灰度期间必须监控数据库连接池和慢查询。
  • 混沌工程不是测试:混沌验证的是"故障发生时系统表现是否符合预期",而不是测 bug。应在已有监控告警的稳定系统上做。
  • 探针路径不要用复杂逻辑/actuator/health 不应检查数据库连接,否则数据库抖动会导致 K8s 反复重启整个集群。用 livenessProbe 的轻量检查和 readinessProbe 的独立路径。
  • preStop sleep 太长也可能有问题:30s 够用,设到 120s 会让滚动更新变慢。配合 K8s 的 terminationGracePeriodSeconds 一起调,保持两者一致。
  • 治理不是一次做完:先做最痛的(回滚流程、发布失败率),再逐步铺灰度、混沌、SLO 驱动。

小结:微服务治理的本质是把"人肉运维"变成"代码规则"。灰度发布降低了上线风险,混沌工程验证了系统韧性,K8s 探针和优雅停机保证了发布过程的稳定性。从最痛的指标开始,逐步构建治理闭环。

下一篇:系统设计篇,从分布式系统设计面试的角度重新组织这些知识。

参考

参考:ChaosBlade 官方文档(https://chaosblade.io)、K8s 官方 Pod Lifecycle 文档、Spring Cloud Gateway 标签路由文档

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1