Service Mesh内部原理与生产治理:Envoy运行时、xDS收敛、流量选择与排障
本页不是再背一遍“控制面、数据面、Sidecar”。它从一个请求进入 Envoy 开始,一直追踪到 Listener、Filter Chain、HTTP Connection Manager、Route、Cluster、Endpoint、连接池和上游响应;再反向追踪这些对象怎样由 Istio 控制面通过 xDS 生成、预热、ACK/NACK、发布和恢复。基础概念请先看 Service Mesh 从零到生产级。
一、真实性与版本基线
本页在 2026-07-19 对照以下官方资料核验:
- Envoy v1.39.0 Release;
- Envoy xDS protocol;
- Envoy architecture overview;
- Envoy outlier detection;
- Envoy panic threshold;
- Envoy admin interface;
- Istio 1.30.3 Release;
- Istio architecture;
- Istio ambient architecture;
- Istio traffic management 与 security。
版本号只是本页的审计基线,不表示生产必须立即升级到该版本。Listener、Route、Cluster、Endpoint、xDS ACK/NACK 等对象关系相对稳定;API 字段、默认值、功能成熟度和命令参数可能随版本变化,落地时必须同时检查:控制面版本、数据面代理版本、CRD 版本、升级说明和实际 config_dump。
二、学完应真正会什么
- 能把一次 HTTP 请求还原成 Envoy 内部对象与过滤器调用链,而不是只说“Sidecar 转发”。
- 能解释 Listener、Route、Cluster、Endpoint 和 Secret 为什么要拆成不同资源。
- 能解释 xDS 双向流、version、nonce、ACK、NACK、SotW、Delta 和 ADS 的真实语义。
- 能解释配置为什么需要 warming,以及“控制面保存成功”为什么不等于“全网生效”。
- 能解释 Priority、Locality、健康状态、Panic Mode、Outlier Detection 与 LB 的选择顺序。
- 能解释 HTTP/1.1 连接池、HTTP/2 多路复用、旧连接和摘流之间的关系。
- 能区分 Envoy 资源熔断、异常实例剔除、重试、过载保护和业务降级。
- 能解释 SDS 证书轮换为何通常不会让已有连接立刻切换证书。
- 能说明 Sidecar 与 Ambient 中 ztunnel、HBONE、Waypoint 的职责边界。
- 能从
NR/UH/UF/UO/UT/URX等响应标志追到真实配置、连接和 Endpoint。
三、先建立两条互相独立的链路
Service Mesh 最容易学乱,是因为把“配置如何到达代理”和“请求如何经过代理”画成了一条线。实际上它们是两条链:
flowchart TD
A["Kubernetes与Mesh策略发生变化"] --> B["Istiod计算目标配置"]
B --> C["xDS把资源推给Envoy"]
C --> D["Envoy校验、预热并发布"]
D --> E["本地形成可处理流量的配置快照"]flowchart TD
A["业务连接进入Envoy"] --> B["Listener与Filter Chain"]
B --> C["HTTP路由选择Cluster"]
C --> D["负载均衡选择Endpoint"]
D --> E["连接池发送上游请求"]
E --> F["响应经过滤器返回业务调用方"]关键结论:
- 正常请求不需要实时询问 Istiod;它使用 Envoy 本地已经接受的配置。
- xDS 更新成功也不会“回放”已经完成的请求,更不会自动改变所有既有连接。
- 控制面故障首先影响配置新鲜度;数据面故障直接影响真实请求。
- 配置事实、代理事实、连接事实和业务事实必须分别取证。
四、Envoy 进程与线程模型
Envoy 是事件驱动代理,不是“一个请求创建一个线程”。理解这一点,才能解释为什么少量 Worker 也能维护大量连接,以及为什么阻塞 Filter 会拖慢同一事件循环上的其他连接。
4.1 主要角色
| 角色 | 主要职责 | 生产含义 |
|---|---|---|
| Main Thread | 启动、配置更新、管理 Listener/Cluster、协调生命周期 | 不直接按传统线程池模型处理每个业务请求 |
| Worker Thread | 运行事件循环,处理连接、网络事件和大部分 Filter 回调 | 不能在同步 Filter 中长时间阻塞 |
| File Flush Thread 等辅助线程 | 日志落盘、特定后台工作 | 慢磁盘仍可能造成日志堆积和资源压力 |
连接通常被某个 Worker 接受后,在其生命周期内由该 Worker 的事件循环处理;这减少跨线程共享和锁竞争。多个 Worker 各自维护连接池视图或线程本地状态,因此“全局最大连接数”“每 Worker 连接分布”和“单个热点连接”不能混为一谈。
4.2 为什么一个慢 Filter 会影响很多请求
假设自定义 HTTP Filter 在 Worker 回调中同步访问慢磁盘 200ms:
- 当前请求不能继续。
- 同一 Worker 上其他就绪 Socket 事件也延后处理。
- 事件循环延迟上升,P99 扩大。
- 上游开始超时和重试,负载进一步增加。
- 应用容器可能完全正常,但 Sidecar 已成为瓶颈。
正确方式是使用 Envoy 支持的异步模式暂停过滤链,在异步结果返回时继续;同时设置超时、并发上限和取消逻辑。把 Java Servlet 的“请求线程”直觉直接套到 Envoy 上会误判问题。
五、Bootstrap、静态资源与动态资源
Envoy 启动先读取 Bootstrap 配置。它至少要告诉代理:节点身份、静态 Listener/Cluster、动态资源从哪里获取、Admin 监听在哪里、日志与运行参数是什么。
5.1 为什么必须有 Bootstrap
xDS 本身也是网络通信。代理若连“去哪里找控制面”都不知道,就无法动态获取 LDS/CDS。Bootstrap 相当于建立动态配置系统所需的最小根配置。
5.2 静态与动态并不互斥
static_resources:进程启动时就知道的 Listener 和 Cluster。dynamic_resources:通过 LDS/CDS/ADS 等动态获取。- 控制面 Cluster 经常在 Bootstrap 中静态声明,因为它是取得其他动态配置的前提。
- SDS 也需要一个可连接的 Secret 服务或本地机制作为信任起点。
5.3 Node 身份为什么重要
xDS 请求中的 Node 信息让控制面知道“这是哪个工作负载、集群、区域、代理版本和元数据”。Istiod 会据此裁剪每个代理可见的服务、路由和安全策略。两个 Envoy 即使连接同一控制面,也不一定收到相同配置。
若 Node 元数据、命名空间或 ServiceAccount 错误,控制面可能生成一个语法完全正确但不适用于该工作负载的快照;这类问题不会靠 YAML 校验自动发现。
六、Listener 到 Filter Chain:连接先进入哪条处理链
Listener 绑定地址和端口。连接到达后,Envoy 先运行 Listener Filter,再根据连接属性选择 Filter Chain,之后才进入网络过滤器。
flowchart TD
A["Socket到达Listener"] --> B["Listener Filter提取连接元数据"]
B --> C["按SNI、传输协议、ALPN等匹配Filter Chain"]
C --> D["Transport Socket执行TLS或明文处理"]
D --> E["Network Filter处理TCP或HTTP"]
E --> F["HTTP Connection Manager创建HTTP流"]6.1 Listener Filter 做什么
常见 Listener Filter:
- TLS Inspector:在不先终止 TLS 的情况下识别 ClientHello 中的 SNI、ALPN 等信息。
- HTTP Inspector:帮助推断明文 HTTP 协议。
- Original Destination/Original Source 相关能力:在透明代理场景恢复被重定向前的信息。
- Proxy Protocol:从受信任上游代理传递原始地址。
如果协议识别错误,后果不是只有“路由错了”:HTTP 过滤器、重试、Tracing、Header 规则都可能完全不执行,流量退化为 TCP 代理或直接找不到 Filter Chain。
6.2 Filter Chain 匹配不是路由匹配
Filter Chain 主要在连接层根据目标地址、端口、SNI、Transport Protocol、ALPN、来源等属性缩小候选;Route 则在已经识别 HTTP 后根据 Host、Path、Method、Header、Query 等匹配。两者发生在不同层次。
典型错误:
- SNI 不匹配:连接阶段就选不到正确证书或 Filter Chain。
- Host 不匹配:TLS 已成功,但 HTTP Route 返回
NR。 - 把应用 Header 路由写到四层配置中:代理根本看不到或不在该阶段判断。
七、HTTP Connection Manager 与 HTTP Filter 调用顺序
HTTP Connection Manager,简称 HCM,负责 HTTP 编解码、路由、访问日志、Tracing 和 HTTP Filter 链。它把一个底层连接上的 HTTP/1.1 请求或 HTTP/2 Stream 变成独立的 HTTP 流。
7.1 Decoder 与 Encoder 方向
请求方向通常按配置顺序经过 Decoder Filter,响应方向通常反向经过 Encoder Filter。Router Filter 通常位于 HTTP Filter 链末端,负责把请求交给 Cluster Manager 和上游连接池。
flowchart TD
A["HCM解析请求头"] --> B["Decoder Filter执行鉴权、限流、遥测"]
B --> C["Route匹配并生成路由结果"]
C --> D["Router Filter发起上游请求"]
D --> E["收到上游响应"]
E --> F["Encoder Filter反向处理响应"]
F --> G["HCM编码并写回下游"]7.2 Filter 可以暂停链路
Filter 不一定立即返回“继续”。它可以:
- 继续,让下一个 Filter 执行;
- 本地直接回复,例如鉴权拒绝返回 403;
- 暂停并等待异步检查,再恢复过滤链;
- 修改 Header、Body、Dynamic Metadata 或路由上下文;
- 重置 Stream。
因此看到 403、429、503 时,不能默认请求已经到达业务容器。先确定是哪个代理、哪个 Filter、哪个响应标志生成了本地响应。
7.3 路由何时确定
HCM 依据 Route Configuration 选择 Virtual Host 和 Route。某些早期 Filter 可以影响路由输入或请求重新计算路由,但生产上不应依赖隐晦副作用。排障要查看代理实际 Route,而不是只看 VirtualService 声明。
八、Route、Cluster、Endpoint 为什么必须分层
| 层次 | 回答的问题 | 变化频率示例 |
|---|---|---|
| Route | 这个 HTTP 请求逻辑上发给哪个服务或版本 | 灰度权重、Header 路由变化 |
| Cluster | 怎样连接这个逻辑上游,采用什么协议、TLS、LB、连接池 | 服务治理或安全配置变化 |
| Endpoint | 当前有哪些真实 IP:Port、地域、优先级和健康信息 | Pod 扩缩容、Ready 变化 |
如果把 IP 直接写进 Route,那么每个 Pod 变化都要重建整套路由;拆层后,RDS 可保持稳定,EDS 独立更新 Endpoint。这个设计降低变更范围,也允许不同资源独立订阅和增量推送。
8.1 Cluster 不是 Kubernetes Service 的简单复制
一个 Cluster 还包含:
- 服务发现类型与 EDS 订阅;
- 上游协议和 HTTP/2 配置;
- Connect Timeout;
- TLS Transport Socket;
- LB 策略;
- 健康检查与 Outlier Detection;
- 连接池和 Circuit Breaker 阈值;
- Locality、优先级、统计名称等。
同一个 Kubernetes Service 的 v1、v2 Subset 可能成为不同 Cluster;同一个 Endpoint 也可能在不同调用方代理中因可见性和策略不同而处于不同候选集合。
九、一次出站 HTTP 调用的 Envoy 内部全过程
以订单服务调用库存服务为例:
- 订单应用连接库存 Service 地址,透明拦截规则把连接重定向到出站 Listener。
- Listener Filter 恢复原始目标并识别协议。
- Listener Manager 选择 Filter Chain 和 Transport Socket。
- HCM 根据 HTTP/1.1、HTTP/2 或 HTTP/3 创建请求 Stream。
- Decoder Filter 执行上下文传播、策略、遥测或扩展逻辑。
- Virtual Host 先按域名匹配,Route 再按 Path、Header 等匹配。
- 路由结果得到 Cluster、超时、重试、Header 改写、Hash Policy 等。
- Cluster Manager 取得当前 Thread Local Cluster 视图。
- 先按 Priority、Locality、健康和策略计算可选 Host Set。
- Load Balancer 在候选 Endpoint 中选择一个 Host。
- 连接池尝试复用已有连接;没有可用连接才建立新连接。
- 建连时执行上游 mTLS,校验证书链、SAN、有效期和信任根。
- Router Filter 把请求编码到上游连接或 HTTP/2 Stream。
- 服务方代理完成入站身份、授权和遥测,再转给库存应用。
- 响应返回时更新连接、请求、重试、Outlier 和访问日志统计。
- 若满足重试条件且预算仍存在,Router 重新选择 Host 发起下一次物理 Attempt。
- 最终响应经过 Encoder Filter 反向处理后返回订单应用。
这里至少存在三个不同计数:用户逻辑请求数、Envoy Stream 数、下游物理 Attempt 数。发生重试时三者不相等,容量分析必须观察 Attempt。
十、连接池与 HTTP/2 多路复用
10.1 HTTP/1.1
一个连接通常同一时刻承载有限请求,是否启用 Pipeline 取决于协议和配置。并发增加时,连接池可能建立更多连接;达到最大连接后,新请求进入 Pending 队列或被资源保护拒绝。
10.2 HTTP/2
一个 TCP/TLS 连接可以并发承载多个 Stream。优点是减少握手和连接数;代价是:
- 大量请求可能集中在少数长连接上;
- Endpoint 已从 EDS 移除,旧连接上的在途 Stream 仍可能完成;
- 连接级丢包、Reset 或 GOAWAY 会同时影响多个 Stream;
- 仅看 TCP 连接数不能推断请求并发;
- 负载均衡通常在创建新 Stream/请求时选择 Host,但连接池可复用已选 Host 的连接。
10.3 为什么摘除 Pod 后仍有少量流量
可能原因:
- 调用方代理还没收到新 EDS。
- 已收到 EDS,但旧请求已经选中该 Endpoint。
- 已建立 HTTP/2 Stream 仍在完成。
- Pod 已 NotReady,但 EndpointSlice 传播、控制面计算和 xDS 下发尚未收敛。
- 业务应用没有在终止前先摘流和 Drain。
“从负载列表移除”主要阻止新的选择,不等于杀死所有已有连接。强制断连接会加速切断,却可能把在途写请求变成结果未知。
十一、Envoy Circuit Breaker 到底断什么
Envoy 的 Cluster Circuit Breaker 更接近资源上限保护,而不是 Resilience4j 的失败率状态机。常见阈值:
| 阈值 | 保护对象 | 达到后可能表现 |
|---|---|---|
max_connections | 到上游的活动连接 | 新建连接受限 |
max_pending_requests | 等待可用连接的请求 | Pending 溢出,常见 UO |
max_requests | 活动上游请求或 Stream | 新请求被拒绝 |
max_retries | 并发执行中的重试 | Retry Overflow,避免重试风暴 |
| Connection Pool 每连接 Stream 上限 | 单连接复用程度 | 创建新连接或排队 |
阈值的精确作用域和默认值必须按 Envoy 版本与协议检查。不要照抄默认值:Sidecar 每个调用方都有自己的本地上限,1000 个 Sidecar 各允许 100 个并发,不代表下游只会看到 100 个请求。
11.1 与其他保护器的区别
| 机制 | 依据 | 主要目的 |
|---|---|---|
| Envoy Circuit Breaker | 连接、Pending、请求、重试等资源计数 | 防止单代理无限排队或占满资源 |
| Outlier Detection | Endpoint 失败统计 | 暂时不再选择疑似异常实例 |
| Resilience4j CircuitBreaker | 调用失败率、慢调用率和状态机 | 在应用调用层快速短路 |
| Overload Manager | 代理自身资源压力 | 让 Envoy 自保和有序拒绝 |
| 业务降级 | 领域语义 | 返回缓存、待确认、只读或排队结果 |
十二、xDS 双向流:请求和响应真正携带什么
xDS 常基于 gRPC 双向流。客户端 Envoy 发送 DiscoveryRequest,控制面发送 DiscoveryResponse。它不是“控制面单向把 JSON 推过去”这么简单。
12.1 DiscoveryRequest 的关键语义
- Node:代理身份和元数据,通常在流开始时用于识别客户端。
type_url:订阅哪类资源。resource_names:客户端希望订阅的资源集合,具体语义随 SotW/Delta 模式不同。version_info:SotW 客户端已接受的版本。response_nonce:回显正在 ACK/NACK 的具体响应。error_detail:非空时通常表示 NACK,并解释为什么未接受响应。
12.2 DiscoveryResponse 的关键语义
resources:该类型的资源内容。version_info:控制面对这份 SotW 响应给出的版本标识。nonce:这一次具体响应的唯一关联标识。type_url:资源类型。
版本不要求一定是递增整数,控制面可以使用哈希或其他不透明字符串;客户端不能用字符串大小猜测新旧。Nonce 关联一次响应,Version 表达已接受资源状态,两者不能互换。
十三、ACK、NACK 与 Last Known Good 的精确边界
flowchart TD
A["Envoy收到xDS响应"] --> B["反序列化与字段校验"]
B --> C["检查资源引用和初始化依赖"]
C --> D["资源可接受并成为候选配置"]
D --> E["发布或进入Warming后ACK"]配置不可接受时走另一条独立失败链,拆图是为了在移动端仍能看清每一步:
flowchart TD
A["Envoy收到xDS响应"] --> B["校验发现字段或依赖错误"]
B --> C["携带error_detail发送NACK"]
C --> D["继续使用Last Known Good"]
D --> E["控制面修正后重新下发"]13.1 ACK 能证明什么
它能证明某个代理对某次响应没有报告拒绝,并接受了相应配置状态。它不能证明:
- 其他代理也已 ACK;
- 真实请求一定匹配新路由;
- Endpoint 一定健康;
- 新证书一定能与所有对端握手;
- 旧连接已经结束;
- 业务数据库操作成功。
13.2 NACK 时为什么不能清空旧配置
如果一次错误 RDS 到达就把旧 Route 全部删除,配置错误会直接扩大成全流量事故。Last Known Good 让代理继续使用上一份已接受配置,同时把错误返回控制面。代价是不同代理可能短暂运行不同版本,所以必须观察版本分布和 NACK,而不是只看总 ACK 数。
13.3 部分资源更新的风险
Route 引用了尚不存在的 Cluster、Cluster 依赖未到达的 EDS、Listener 依赖未就绪的 Secret,都可能造成初始化等待或拒绝。控制面必须按依赖关系组织资源,而不是把独立 CRUD 事件原样乱序广播。
十四、SotW、Delta 与 ADS
| 维度 | State of the World | Delta xDS |
|---|---|---|
| 响应表达 | 某类资源的目标整体状态 | 资源级新增、更新和删除 |
| 客户端状态 | 以已接受整体版本为核心 | 维护每个资源版本和订阅状态 |
| 删除表达 | 资源从整体集合消失 | 显式 removed_resources 等语义 |
| 适合场景 | 资源规模可控、实现相对直接 | 大规模、频繁局部变化 |
| 重连恢复 | 重新声明已知版本和订阅 | 报告初始资源版本并恢复差异 |
ADS 是 Aggregated Discovery Service,把多种 xDS 资源放在一个有序的双向流中传输,便于控制面协调跨资源依赖。ADS 不表示所有资源被压成一个对象,也不表示整个网格只有一个全局版本。
14.1 为什么 ADS 有助于依赖收敛
独立 LDS、RDS、CDS、EDS 流可能各自正常却观察到不同时间点。ADS 让同一控制面在一条流内更好地控制发送顺序,例如先使 Cluster/Endpoint 可用,再发布引用它的 Route;但控制面仍需正确实现依赖与重试,协议不会自动替错误实现兜底。
十五、Warming:为什么配置不是收到就立刻切换
15.1 Listener Warming
新 Listener 或更新后的 Listener 可能依赖 RDS Route、SDS Secret、Extension 配置等。依赖未初始化时,它处于 Warming;旧 Active Listener 可以继续服务。依赖就绪后,新版本才替换旧版本。
15.2 Cluster Warming
依赖 EDS 的 Cluster 需要取得初始 Endpoint 视图,其他初始化目标也可能参与等待。若新 Cluster 在没有任何端点信息时立刻对外可用,请求会出现无健康上游;Warming 用于避免这种半初始化状态。
15.3 Warming 不等于永远安全
- 依赖永远不返回,配置会长期卡在 Warming。
- 控制面和代理的初始化超时、失败策略依版本配置而异。
- 旧配置虽然继续工作,但 Endpoint 和证书会逐渐陈旧。
- 代理首次启动没有旧配置可退,依赖未就绪时 Readiness 应失败。
排障不能只问“ACK 了吗”,还要查看 Listener/Cluster 是 Active、Warming、Draining 还是已移除。
十六、Istiod 怎样把平台对象编译成 Envoy 配置
flowchart TD
A["监听Service、EndpointSlice与Pod"] --> B["读取VirtualService、DestinationRule与安全策略"]
B --> C["构建服务、端点、路由和身份模型"]
C --> D["按代理身份与可见范围生成目标资源"]
D --> E["通过ADS或xDS发送给Envoy"]
E --> F["收集ACK、NACK与同步状态"]16.1 为什么不同 Sidecar 配置不同
Istiod 会考虑:
- 代理所在集群、命名空间、网络和地域;
- Workload Labels 与 ServiceAccount;
- 服务导出/可见范围;
- Sidecar 或等价的配置作用域;
- VirtualService、DestinationRule、PeerAuthentication、AuthorizationPolicy;
- 代理版本和能力;
- Gateway、Sidecar、Waypoint 等代理角色。
因此生产排障的权威对象是“目标 Pod 的实际代理配置”,不是另一个 Pod 的配置,也不是控制台中看起来正确的 YAML。
16.2 全量 Push 与增量 Push
端点变化通常只需要更新受影响 EDS;路由和策略变化可能触发更广的配置重算。实现会尽量缩小影响范围,但具体全量/增量判定属于 Istio 版本实现细节。大范围服务可见性会增加每个 Sidecar 的 Cluster/Endpoint 数量、控制面计算量和代理内存。
十七、透明拦截:iptables、TPROXY 与 eBPF
17.1 REDIRECT 思路
Pod 网络命名空间中的规则把应用出站连接重定向到 Envoy 的出站入口;入站连接先重定向到 Envoy,再由代理转给应用端口。代理通过原始目标信息和元数据决定逻辑服务。
17.2 TPROXY 思路
TPROXY 可以在特定场景保留原始源地址/目标语义,但需要策略路由、权限和内核能力配合,部署复杂度更高。不能只切一个开关就假设所有协议行为相同。
17.3 必须排除什么
- Envoy 自己发出的连接,否则可能再次被捕获形成循环;
- 控制面、健康检查或明确旁路端口;
- 不应进入 Mesh 的管理流量;
- 已由其他代理处理的特定路径。
Istio 常见的 15001、15006、15000 等端口是实现默认,不是 Service Mesh 理论常量;升级或自定义安装必须查看实际 Pod 与规则。
17.4 eBPF 不会消灭代理语义
eBPF 可以改变捕获和转发实现,减少 iptables 规则复杂度或提供更早的内核可观测性,但 Listener、L7 Route、mTLS、身份和策略仍需要对应数据面能力。不要把“使用 eBPF”误解为所有七层代理成本自动消失。
十八、Endpoint 选择不是直接 Round Robin
一次 Host 选择可按以下概念顺序理解:
flowchart TD
A["取得Cluster当前Host Set"] --> B["选择可用Priority"]
B --> C["按Locality与故障转移策略分配流量"]
C --> D["过滤健康、降级和被剔除Endpoint"]
D --> E["检查是否进入Panic Mode"]
E --> F["LB算法选择具体Endpoint"]
F --> G["连接池复用或建连"]真实内部实现会对健康、Degraded、Priority 和 Locality 计算负载份额,不能把图理解成固定的源码函数调用顺序;它表达的是排障时必须逐层排除的决策维度。
十九、Priority 与 Locality
19.1 Priority
Priority 常用于主集群/灾备集群或优先级故障转移。P0 容量健康时优先使用;可用健康容量不足时,流量才逐步溢出到更低优先级。它不是简单的“P0 有一个实例就永远 100% 走 P0”。
19.2 Locality
Locality 通常包含 Region、Zone、Sub-zone。Locality 加权和故障转移用于:
- 优先同区,降低延迟与跨区费用;
- 本区容量不足时向其他区溢出;
- 整区故障时转移到备用区或区域。
风险:若所有调用方都在故障瞬间同时转向一个剩余区,备用区会遭受流量阶跃。必须预留故障容量,并对跨区切换做压测。
19.3 Overprovisioning Factor
Envoy 的优先级负载计算会考虑健康比例与过量配置因子。它允许某优先级不是 100% 健康时仍承担预期流量,从而避免轻微健康波动就频繁跨优先级切换。具体默认值和计算公式应以部署版本官方文档为准,容量规划不要靠背一个常数。
二十、Panic Mode:为什么大量实例不健康时反而还会发流量
Envoy 常见默认 Panic Threshold 为 50%,但该值可配置且需按实际版本确认。当健康 Endpoint 比例低于阈值时,代理可能进入 Panic Mode,将更多 Host(包括原本不健康的 Host)重新纳入选择,目标是在“全部拒绝”和“仍有概率成功”之间选择后者。
这会产生看似矛盾的现象:健康检查明确判定大量实例异常,流量却仍到达它们。它不是健康检查失效,而是 Panic 策略生效。
适用边界:
- 若健康判定存在假阳性,Panic 可避免把候选集清空。
- 若实例确实会产生危险副作用,向不健康实例发流量可能更糟。
- 配置 Outlier Detection 的最大剔除比例与 Panic Threshold 时要一起分析。
- Runbook 应查看
healthy_panic相关统计,而不是只数健康 Endpoint。
二十一、Outlier Detection 内部原理
Outlier Detection 是被动健康检测:代理根据真实请求结果识别异常 Endpoint,并在本代理视角中暂时剔除它。
21.1 常见检测器
- 连续 5xx 或网关错误;
- 成功率低于同集群统计基线;
- 失败百分比达到阈值;
- 可选地把本地连接错误与上游 HTTP 错误分开统计;
- 协议特定错误或本地 Origin Failure。
成功率类算法需要足够 Host 数和每 Host 最小请求量。只有两个实例、每个一分钟五次请求时做统计剔除,很容易把随机波动当故障。
21.2 剔除时间为什么会增长
Endpoint 第一次异常被剔除一个基础时间;反复异常时,剔除时间可按连续剔除次数增长,并受最大值约束。健康后计数会按规则逐步恢复,而不是永远拉黑。
21.3 Enforcement 与 Detection
某些检测器可以先统计“如果启用会剔除什么”,再按 Enforcement 百分比真正执行。这适合生产灰度校准。直接 100% 开启未经观察的规则,可能在高峰期把所有慢但可用实例一起剔除。
21.4 为什么不是全局一致
每个调用方代理看到的请求样本不同,所以 A Sidecar 可能剔除库存 Pod-3,B Sidecar 仍认为它健康。这是本地快速决策,不是共识协议。若需要全局摘除,应由 Kubernetes Readiness、服务注册健康或运维控制面改变 Endpoint 事实。
二十二、Envoy 重试引擎:一次逻辑请求怎样产生多个 Attempt
22.1 触发条件
可按配置对连接失败、Reset、特定 5xx、网关错误、gRPC 状态等重试。不能把所有 4xx/5xx 都设成可重试:业务校验失败重试不会成功,只会放大流量。
22.2 num_retries/attempts 容易误读
Envoy Route 的重试数量通常表达额外重试次数;Istio VirtualService 的 attempts 也应按目标版本 API 确认总尝试关系。生产验收必须用访问日志中的 Attempt 计数验证,不要凭字段名猜“总共两次还是额外两次”。
22.3 Retry Host Predicate 与 Retry Priority
失败后再次选择 Host 时,可以配置避免立即选择刚失败的 Host,或改变优先级选择。若只有一个 Endpoint,“换 Host 重试”不可能发生;若所有 Endpoint 共享同一数据库故障,换 Host 也无意义。
22.4 Retry Budget
固定最大重试并不能防止故障放大。更合理的预算思路是让允许的并发重试与当前成功/活跃请求规模相关,并设置最小保证值。无论采用何种字段,都应监控:
- 逻辑请求数;
- 总 Attempt 数;
- Retry Success;
- Retry Overflow;
- 每次尝试延迟;
- 最终业务成功率。
二十三、Hedging:超时后并发发第二份请求
普通重试通常在第一次 Attempt 失败或取消后再发第二次;Hedging 可以在 Per-try Timeout 到达时保留第一份在途请求,同时并发发第二份,以降低长尾延迟。
flowchart TD
A["Attempt 1仍在执行"] --> B["Per-try Timeout到达"]
B --> C["不立即取消或无法确认取消"]
C --> D["并发发出Attempt 2"]
D --> E["先成功的响应成为结果"]
E --> F["另一个Attempt可能仍产生副作用"]Hedging 对只读、幂等、长尾明显的请求可能有效;对支付扣款、发券、发短信等写操作非常危险。即使调用方丢弃慢响应,慢请求也可能已经提交事务。必须依靠业务幂等,且容量模型要按并发副本计算。
二十四、超时、Deadline 与取消传播
| 层级 | 典型作用 | 不正确时的后果 |
|---|---|---|
| Connect Timeout | 建立上游连接 | 失效地址长期占用建连资源 |
| Per-try Timeout | 单个 Attempt | 过大则没有时间重试,过小则制造假超时 |
| Route/Request Timeout | 一个代理看到的完整请求 | 与上游 Deadline 倒挂会产生无效工作 |
| Stream Idle Timeout | 一段时间无数据活动 | 长连接或流式调用被错误关闭 |
| Max Connection Duration | 控制长连接生命周期 | 太短会造成握手风暴,太长影响均衡和证书切换 |
| 业务 Deadline | 用户请求端到端剩余预算 | 不传播会让下游在调用方放弃后继续工作 |
代理发送 Reset 或取消只表示“发出了取消信号”,不保证应用线程、数据库语句或远程系统已经停止。Java 服务需要把剩余 Deadline 继续传给 JDBC、HTTP、RPC 和异步任务,并在事务提交前检查业务状态;不能只依靠 Sidecar 超时。
二十五、Overload Manager:代理自身快撑不住时怎么办
Overload Manager 根据内存、事件循环延迟等资源监控触发动作,例如停止接受新连接、拒绝部分请求、缩减功能或主动释放资源。它保护的是 Envoy 进程本身。
它与 Cluster Circuit Breaker 的差异:
- Circuit Breaker 通常按某个上游 Cluster 限制请求资源。
- Overload Manager 观察代理自身总体压力,可能影响多个 Listener/Cluster。
- Kubernetes CPU/Memory Limit 是容器级最后边界,触发 OOMKill 已经太晚。
生产应同时监控 Sidecar CPU、内存、FD、活动连接、堆分配、事件循环延迟和 Overload Action 状态。只给 Sidecar 加内存而不修复重试风暴、高基数统计或短连接,问题会再次发生。
二十六、SDS、mTLS 与证书轮换全过程
flowchart TD
A["工作负载向身份系统证明身份"] --> B["CA签发短期证书"]
B --> C["SDS把证书与信任根交给Envoy"]
C --> D["新Secret通过校验并原子生效"]
D --> E["新建TLS连接使用新证书"]
E --> F["既有连接按原会话继续到关闭"]26.1 证书里证明谁
Mesh 通常把命名空间、ServiceAccount 或工作负载身份编码到 URI SAN 等字段。对端验证证书链后,还应验证 SAN 身份是否符合授权策略。只验证“由同一个 CA 签发”会把整个信任域都当成同一个调用者。
26.2 为什么轮换后还有旧证书连接
TLS 身份在握手时建立。SDS 更新 Secret 后,新连接使用新材料;已建立连接通常不会为了换证书自动重新握手。若要验证轮换,既要查看 /certs,也要观察新连接握手,而不是只发一条复用旧连接的请求。
26.3 根证书迁移为什么需要重叠信任
直接把旧根替换成新根可能造成一侧已使用新证书、另一侧仍只信旧根。安全迁移通常经历:
- 双方先同时信任旧根和新根。
- 开始签发新根链证书。
- 等旧证书和旧连接有界退出。
- 最后移除旧根。
顺序错误会形成大面积 TLS 握手失败。必须监控代理 Secret 同步状态、证书链和到期时间。
二十七、工作负载认证与授权不是一回事
mTLS Authentication 先确认来源身份;Authorization 再判断该身份能否访问目标、端口、方法或路径。
27.1 四层与七层条件
- ztunnel 或 TCP 代理可基于来源身份、目标工作负载、端口等做四层授权。
- HTTP 代理或 Waypoint 能依据 Method、Path、Host、Header 等做七层授权。
- 加密的未知协议未在代理处终止时,代理看不到内部 HTTP Path。
27.2 用户 JWT 与工作负载 mTLS
- mTLS 证明“订单服务这个工作负载正在调用”。
- JWT 可能证明“用户 1001 已登录并带有某 Claim”。
- 退款资格还需要订单归属、金额、状态等业务授权。
三层身份不能互相替代。把用户 Header 未经验证地透传给下游,再由 Mesh 信任该 Header,会产生权限伪造。
二十八、Ambient:ztunnel、HBONE 与 Waypoint
Ambient 不是“把 Sidecar 移到节点上”这么简单,它把安全四层能力与可选七层能力拆开。
| 组件/协议 | 主要职责 | 不负责什么 |
|---|---|---|
| ztunnel | 节点级安全隧道、工作负载身份、L4 连接与基础遥测 | 不承担完整任意 HTTP L7 治理 |
| HBONE | 基于 HTTP 的安全覆盖网络承载工作负载流量 | 不等同于业务 HTTP API |
| Waypoint Proxy | 面向特定服务/命名空间的 L7 路由、策略和遥测 | 不是每条流量必然经过 |
| Istiod | 控制面计算身份、路由与策略配置 | 不转发普通业务数据包 |
flowchart TD
A["来源工作负载"] --> B["来源节点ztunnel"]
B --> C["HBONE安全隧道"]
C --> D["目标节点ztunnel执行L4身份与转发"]
D --> E["目标工作负载"]需要七层治理的目标会多经过 Waypoint:
flowchart TD
A["来源节点ztunnel"] --> B["HBONE安全隧道"]
B --> C["Waypoint执行HTTP策略与路由"]
C --> D["目标节点ztunnel"]
D --> E["目标工作负载"]迁移检查:
- 哪些策略只需要 L4 身份,哪些依赖 HTTP Path/Header。
- Waypoint 是按服务还是命名空间绑定,真实流量是否已纳管。
- Sidecar 特有 Filter、EnvoyFilter 或端口捕获假设是否仍成立。
- ztunnel 是共享故障域,节点级异常影响面与 Sidecar 不同。
- 观测指标、访问日志和 Trace 在不同跳点产生,查询方式要调整。
二十九、可运行 Demo:从 Listener 走到 Cluster 与 Endpoint
下面 Demo 不依赖 Istio,直接使用 Envoy v3 配置展示最小运行链。需要本机安装 Envoy;若使用容器,把本地文件挂载到镜像即可。
29.1 envoy.yaml
node:
id: learning-proxy
cluster: learning
static_resources:
listeners:
- name: listener_http_10000
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
log_format:
text_format_source:
inline_string: "%START_TIME% %REQ(:METHOD)% %REQ(:PATH)% %RESPONSE_CODE% %RESPONSE_FLAGS% %UPSTREAM_HOST% %DURATION%ms\n"
route_config:
name: local_route
virtual_hosts:
- name: inventory
domains: ["*"]
routes:
- match:
prefix: "/inventory"
route:
cluster: inventory_backend
timeout: 2s
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: inventory_backend
type: STATIC
connect_timeout: 500ms
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: inventory_backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 18080
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 20
max_pending_requests: 20
max_requests: 100
max_retries: 3
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 990129.2 Python 上游服务 inventory_server.py
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
class InventoryHandler(BaseHTTPRequestHandler):
def do_GET(self):
body = json.dumps({
"sku": "SKU-1001",
"available": 37,
"path": self.path
}).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
if __name__ == "__main__":
server = ThreadingHTTPServer(("127.0.0.1", 18080), InventoryHandler)
print("inventory upstream listening on 127.0.0.1:18080")
server.serve_forever()29.3 验证与运行
# 第一步必须先做 Envoy 语义校验,而不是只校验 YAML 缩进
envoy --mode validate -c envoy.yaml
# 终端一:启动上游
python inventory_server.py
# 终端二:启动代理
envoy -c envoy.yaml --log-level info
# 终端三:请求先到 Listener,再由 Route 转发到 Cluster/Endpoint
curl -v http://127.0.0.1:10000/inventory/SKU-1001
# 查看真实运行配置与统计
curl -s http://127.0.0.1:9901/config_dump
curl -s http://127.0.0.1:9901/clusters
curl -s 'http://127.0.0.1:9901/stats?filter=cluster.inventory_backend'停止 Python 上游后再调用,会看到连接失败和非空 RESPONSE_FLAGS;把请求改为 /unknown,由于没有匹配 Route,通常得到 404/NR。这两个实验分别证明“Route 不存在”和“Cluster 存在但上游连接失败”是两个层次。
三十、商业配置 Demo:订单只允许调用库存,并做有界重试
30.1 PeerAuthentication:库存命名空间强制 mTLS
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: inventory-strict-mtls
namespace: inventory
spec:
mtls:
mode: STRICT30.2 AuthorizationPolicy:只允许订单 ServiceAccount
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: inventory-allow-order
namespace: inventory
spec:
selector:
matchLabels:
app: inventory
action: ALLOW
rules:
- from:
- source:
principals:
- "cluster.local/ns/order/sa/order-service"
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/inventory/*"]30.3 DestinationRule:版本、连接池与异常实例剔除
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: inventory
namespace: order
spec:
host: inventory.inventory.svc.cluster.local
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 100
http2MaxRequests: 500
outlierDetection:
consecutive5xxErrors: 5
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 50
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v230.4 VirtualService:灰度、总超时与单次尝试超时
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: inventory
namespace: order
spec:
hosts:
- inventory.inventory.svc.cluster.local
http:
- match:
- headers:
x-canary-user:
exact: "true"
route:
- destination:
host: inventory.inventory.svc.cluster.local
subset: v2
- route:
- destination:
host: inventory.inventory.svc.cluster.local
subset: v1
weight: 95
- destination:
host: inventory.inventory.svc.cluster.local
subset: v2
weight: 5
timeout: 1500ms
retries:
attempts: 1
perTryTimeout: 600ms
retryOn: connect-failure,reset,50330.5 配置为什么不能直接复制上线
100/500是演示阈值,不是容量推荐;必须根据单 Pod 并发、Sidecar 数和下游容量计算。- 写接口重试前必须用订单号/扣减流水做幂等。
maxEjectionPercent: 50与 Panic、实例数和故障容量要一起演练。paths、Principal 格式、API 版本要按实际 Istio 版本和 Waypoint/Sidecar 模式验证。- 先运行
istioctl analyze -A,再查看目标 Pod 的proxy-config,最后用真实请求证明路由。
三十一、商业场景:订单调用库存的职责拆分
| 层次 | 负责 | 失败后如何恢复 |
|---|---|---|
| Kubernetes | Pod 生命周期、EndpointSlice、Readiness | 修复实例并重新进入 Endpoint |
| Istiod | 把服务和策略编译成代理配置 | 重连、重算、重新推送,观察 ACK/NACK |
| 调用方 Envoy | 路由、LB、mTLS、超时、有界重试 | 使用 LKG、换 Host、快速失败 |
| 服务方 Envoy | 入站身份、授权、遥测 | 拒绝非法身份,暴露响应标志 |
| 库存应用 | 幂等、库存条件更新、本地事务 | 事实查询、补偿、人工处理 |
| 订单应用 | 业务状态机和结果未知处理 | PROCESSING 后查询库存事实,而非盲目回滚 |
真正可靠的调用不是“代理返回 200 就结束”,而是订单状态、库存流水和业务唯一键共同闭环。Mesh 只能提高通信治理一致性,不能判断库存事务是否已提交。
三十二、必须知道的失败窗口
| 失败窗口 | 外部现象 | 根因层次 | 正确恢复 |
|---|---|---|---|
| 策略已保存,Istiod 尚未重算 | 控制台显示成功,代理无变化 | 控制面计算 | 查事件、控制面日志和目标配置 |
| 资源已发送,部分代理 NACK | 只有部分 Pod 生效 | xDS/版本兼容 | 查 nonce、error_detail、代理版本 |
| 新 Listener 仍 Warming | 新端口未接流量 | 依赖 Route/Secret 未就绪 | 查 Warming 资源与依赖 |
| EDS 已删除 Pod,旧 Stream 未结束 | 摘流后仍有少量请求 | 连接/在途请求 | 有界 Drain,不粗暴断写请求 |
| Outlier 在部分 Sidecar 剔除 | 不同调用方成功率不同 | 本地统计 | 按来源 Sidecar 和 Endpoint 拆指标 |
| 大量实例失败进入 Panic | 不健康实例仍收到请求 | LB 可用性策略 | 查 Panic 指标、恢复容量 |
| SDS 更新失败,旧证书尚未过期 | 当前正常,稍后集中失败 | 安全材料传播 | 修复 SDS,监控到期时间和新连接 |
| 超时后下游事务提交 | 调用方失败,库存已扣 | 业务结果未知 | 幂等事实查询,不重复扣减 |
| Waypoint 未纳管目标服务 | L4 mTLS 正常但 L7 策略未执行 | Ambient 绑定 | 检查 enrollment 与真实路径 |
| Sidecar Overload | 应用 CPU 正常仍大量 503 | 数据面资源 | 查 Envoy 资源、重试和统计基数 |
三十三、从 Response Flag 反推失败阶段
| Flag | 常见含义 | 首要检查 |
|---|---|---|
NR | 没有匹配 Route,或路由层没有可用配置 | Host、Path、Virtual Host、RDS |
UH | 没有健康上游 | Cluster、EDS、Subset、健康与 Panic |
UF | 上游连接建立失败 | 地址、端口、NetworkPolicy、TLS、拒绝连接 |
UO | 上游资源溢出/Circuit Breaker | 最大连接、Pending、请求、重试 |
UT | 上游请求超时 | Route Timeout、下游耗时、排队 |
URX | 重试次数耗尽或重试相关失败 | Retry Policy、Attempt、剩余 Deadline |
UC | 上游连接终止 | 对端重启、GOAWAY、网络 Reset |
DC | 下游连接终止 | 调用方取消、网关断开、客户端超时 |
Flag 的精确定义以目标 Envoy 版本官方文档为准。同一个 503 可能由不同代理生成;一定要先找到访问日志所在跳点和 %RESPONSE_CODE_DETAILS%,不能只看客户端状态码猜根因。
三十四、Envoy Admin 与 Istio 生产 Runbook
Admin 接口暴露敏感配置和操作能力,只应监听回环地址或受严格访问控制的管理网络,绝不能直接暴露公网。
34.1 先确认控制面同步
istioctl proxy-status
istioctl analyze -A关注:代理是否连接、控制面版本、CDS/LDS/EDS/RDS 是否 SYNCED、是否 STALE/NOT SENT,以及具体 NACK。
34.2 再查看目标 Pod 的真实配置
istioctl proxy-config listeners order-7d9f -n order
istioctl proxy-config routes order-7d9f -n order
istioctl proxy-config clusters order-7d9f -n order
istioctl proxy-config endpoints order-7d9f -n order
istioctl proxy-config secret order-7d9f -n order顺序对应:是否接住连接 → 是否匹配 HTTP → 是否存在逻辑上游 → 是否有真实端点 → 是否有正确证书。
34.3 必要时查看 Envoy Admin
# Istio Sidecar 常见 Admin 端口为15000,实际值以Pod为准
kubectl exec -n order order-7d9f -c istio-proxy -- \
curl -s http://127.0.0.1:15000/server_info
kubectl exec -n order order-7d9f -c istio-proxy -- \
curl -s http://127.0.0.1:15000/config_dump
kubectl exec -n order order-7d9f -c istio-proxy -- \
curl -s http://127.0.0.1:15000/clusters
kubectl exec -n order order-7d9f -c istio-proxy -- \
curl -s http://127.0.0.1:15000/certs
kubectl exec -n order order-7d9f -c istio-proxy -- \
curl -s 'http://127.0.0.1:15000/stats?filter=inventory'34.4 NR 排查
- 确认响应由调用方 Sidecar、Gateway 还是服务方代理生成。
- 记录 Authority/Host、Path、Method、SNI、目标端口。
- 查 Listener 是否识别为 HTTP。
- 查 Virtual Host Domain 是否匹配 Authority。
- 查 Route 顺序和 Header 条件。
- 查 RDS 是否 NACK 或仍是旧版本。
34.5 UH 排查
- Route 选中的 Cluster 名称是什么。
- Cluster 是否存在、是否 Active/Warming。
- EDS 是否为空,Subset Label 是否匹配 Pod。
- EndpointSlice 是否包含 Ready 地址。
- Endpoint 是否被健康检查或 Outlier 剔除。
- 是否进入 Panic,Priority/Locality 是否无容量。
34.6 UF/TLS 排查
- 从代理所在网络连接目标 IP:Port,而不是只在本机测试。
- 查服务方 Sidecar 与应用端口是否监听。
- 查 NetworkPolicy、防火墙、MTU、Conntrack、节点局部故障。
- 查证书 SAN、信任根、到期时间和系统时钟。
- 查客户端 TLS 模式与服务方 STRICT/PERMISSIVE 是否匹配。
- 按 Endpoint、Node、Zone 拆分错误,避免把局部网络故障误判成全局服务故障。
34.7 Sidecar CPU/内存异常
- 查连接数、短连接率、TLS 握手和 HTTP/2 Stream。
- 查 Attempt/逻辑请求比,识别重试风暴。
- 查 Cluster、Endpoint 和统计项规模,收缩服务可见范围。
- 查高基数 Header、访问日志、Tracing 采样和 Wasm/自定义 Filter。
- 查事件循环延迟和 Overload Action。
- 修复根因后再调资源,不把扩容当永久答案。
三十五、路由与安全策略发布流程
- 在测试环境用相同控制面/代理版本做 API 校验。
- 运行
istioctl analyze,检查无效引用、端口和策略冲突。 - 先发布不改变流量的 DestinationRule/Subset,确认 Cluster 和 Endpoint 已生成。
- 再发布极小范围 Header 路由,只选内部账号。
- 查看目标代理 ACK/NACK、Active/Warming 和 Secret。
- 发真实请求验证实际 Route、Cluster、Endpoint 与业务结果。
- 逐步提高权重,每一步等待连接和指标达到稳定窗口。
- 同时观察代理成功率、Attempt、P99、业务成功率和数据库事实。
- 回滚时把新权重降为 0,并处理旧连接、在途请求和数据兼容。
- 保留变更版本、验证证据和自动回滚阈值。
三十六、必须建立的观测闭环
| 层次 | 最低观测项 | 能回答的问题 |
|---|---|---|
| 控制面 | Push 延迟、xDS 连接、ACK/NACK、版本分布 | 配置有没有收敛 |
| Listener/Route | 请求数、No Route、协议、Virtual Host | 请求有没有匹配正确入口与路由 |
| Cluster | 请求、连接、Pending、Overflow、Retry | 上游资源是否饱和 |
| Endpoint | 每 Host 成功率、延迟、Ejection | 是否局部实例异常 |
| TLS/SDS | 证书到期、Secret 更新、握手错误 | 身份材料是否健康 |
| Envoy 进程 | CPU、内存、FD、事件循环、Overload | 代理自身是否过载 |
| 应用业务 | 订单号、库存流水、业务码、事务状态 | 网络结果是否等于业务事实 |
避免把 SKU、订单号、用户 ID 直接作为 Prometheus Label;它们属于高基数维度,应进入 Trace、日志或可检索业务事件。指标用于聚合趋势,Trace/日志用于定位单请求,业务表用于确认最终事实。
三十七、JDK 8 与 Java 17+/Boot 3 的边界
Envoy/Istio 数据面原理与业务 JDK 基本解耦,但应用接入不同:
| 维度 | JDK 8 / Spring Boot 2.7 | Java 17+ / Spring Boot 3.x |
|---|---|---|
| 链路追踪 | Spring Cloud Sleuth 旧体系常见 | Micrometer Observation/Tracing 主线 |
| HTTP 客户端 | RestTemplate、旧 Feign 发行列常见 | WebClient、RestClient、新 Feign 发行列 |
| 命名空间 | javax.* | jakarta.* |
| 优雅停机 | 需按 Boot 2 配置和容器生命周期验证 | Boot 3 配置更现代,但仍要验证摘流顺序 |
| Context | ThreadLocal 传播常见 | 虚拟线程/异步场景仍需显式验证上下文 |
两条基线都必须做到:
- 业务幂等不交给 Mesh;
- Feign/HTTP Client 与 Sidecar 不重复重试;
- 应用超时小于上游入口允许预算,并传播剩余 Deadline;
- SIGTERM 后先停止接新请求,再完成在途请求并退出;
- 业务错误码、Trace 与代理 Access Log 可关联。
三十八、常见错误认知
| 错误说法 | 为什么错 |
|---|---|
| Envoy 每个请求一个线程 | Envoy 以事件循环处理连接和 Stream,阻塞 Filter 会拖慢 Worker |
| Route 直接选 Pod IP | Route 先选 Cluster,LB 再从 Host Set 选 Endpoint |
| xDS Push 成功等于全网生效 | 还要经过代理接收、校验、Warming、ACK、连接切换与真实请求 |
| ACK 后旧配置立即消失 | 旧 Listener/连接可能 Draining,在途请求仍继续 |
| Outlier Detection 会全网同时摘除 | 通常是每个代理基于本地样本独立判断 |
| 不健康比例越高越不会发流量 | 低于 Panic Threshold 时可能重新向更多 Host 发流量 |
| HTTP/2 一个连接就代表一个请求 | 一个连接可以并发承载大量 Stream |
| Sidecar 超时能撤销数据库事务 | Reset/取消信号不保证远端业务已停止或回滚 |
| SDS 更新会让所有连接立即换证书 | 新连接使用新 Secret,既有 TLS 会话通常继续 |
| Ambient 自动拥有全部 L7 能力 | L7 治理通常需要 Waypoint 且必须正确纳管 |
| Admin 端口可以公网开放便于排查 | 它暴露配置、证书信息和管理能力,必须隔离 |
| Mesh 解决 Exactly Once | 代理不知道业务提交事实,仍需幂等、查询和补偿 |
三十九、面试与继续学习
标准回答、追问和场景题统一放在 Service Mesh 独立面试题。回答时建议遵循:定位职责 → 配置链 → 请求链 → 连接与失败窗口 → 业务正确性边界 → 生产取证。
关联知识:
- Kubernetes 服务发现、EndpointSlice 与传播窗口
- 微服务负载均衡:P2C、EWMA、Maglev 与长连接治理
- 微服务稳定性:超时、重试、熔断与过载保护
- gRPC Java 内部原理与生产治理
- Spring Cloud 完整服务调用链
- Resilience4j 内部原理与生产治理
- 分布式幂等
- 微服务可观测性
本章小结
Service Mesh 的生产能力不在于会写一份 VirtualService,而在于能证明:控制面根据哪个事实生成了什么资源,目标代理是否正确接收并完成 Warming,请求在 Listener、Filter、Route、Cluster、Endpoint 和连接池的哪一步失败,旧连接与在途请求处于什么状态,最终业务事务又是什么事实。只有把配置链、数据链、连接链和业务链分开取证,才能真正理解并治理 Service Mesh。
