网格请求证据模型与选型:不要用一个 200 证明整条服务链
凌晨告警显示订单接口 503,应用日志却没有收到请求。平台看到 istiod 和所有 Pod 都是 Ready,拓扑图也有完整连线,于是判断业务实例异常;重启实例后故障仍在。最后才发现某批 sidecar 没有同步最新 endpoint,503 由源代理本地产生,应用从未参与这次失败。
另一类事故更隐蔽:请求持续返回 200,但 namespace 的默认拒绝策略并未覆盖旧 Pod。新 Pod 已注入代理并执行策略,旧 Pod 仍直连目标服务。团队用新 Pod 的成功与失败截图宣布发布完成,真实流量却长期存在两套安全语义。一次请求只有同时绑定工作负载、数据路径、身份、策略、端点、配置版本和响应责任,才足以支撑故障判断与架构决策。
六个平面不能压成一个健康状态
服务网格至少同时运行六个平面:
| 平面 | 关键对象 | 能证明什么 | 单独不能证明什么 |
|---|---|---|---|
| 声明平面 | label、annotation、CRD、Helm values、Operator CR | 团队表达了目标状态 | 控制器已接受、代理已装载、请求已执行 |
| 调谐平面 | condition、generation、event、controller log | 控制器如何解释声明并产生资源 | 数据面配置已同步、真实请求命中 |
| 数据平面 | sidecar、ztunnel、waypoint、Cilium agent/eBPF、Envoy | 流量捕获点和执行配置实际存在 | 身份正确、应用结果正确 |
| 身份平面 | ServiceAccount、SPIFFE ID、SVID、trust bundle、证书 | 双方使用什么身份和信任材料 | 访问被授权、租户隔离成立 |
| 业务平面 | 服务端点、版本、状态码、业务 ID、幂等键 | 权威业务动作及其结果 | 请求必然经过目标代理和策略 |
| 观测平面 | log、metric、Trace、flow、topology | 某时间窗内被记录的行为 | 未记录行为不存在、业务事实必然正确 |
控制面 Ready 只说明进程满足就绪探针;CR Accepted=True 只说明控制器接受了对象;代理 SYNCED 只说明它与某个配置版本同步;mTLS 指标只说明某段连接被识别为加密;应用 200 只说明某个处理路径返回成功。架构判断要组合这些局部事实,同时准备能推翻错误结论的反例。
六个平面的状态必须落在同一条请求记录中,不能用一个综合的 healthy=true 代替。声明与调谐通过对象 UID、generation 和 observed generation 关联;数据面、身份、业务与观测通过受控的 request ID 和时间窗关联。任一平面缺失时,结论只能停在该平面已证明的范围,不得外推。
给每次实验分配不可混淆的身份
统一实验使用固定且虚构的对象:
namespace: mesh-lab
clients:
- name: client-meshed
serviceAccount: client
- name: client-plain
serviceAccount: client-plain
services:
- name: orders
versions: [v1, v2]
request:
path: /orders/demo
header: x-lab-request-idorders-v1 与 orders-v2 必须在响应中返回不同版本标识和服务端生成的 request ID 回显。源端生成的 x-lab-request-id 只能作为关联输入,入口代理应限制长度和字符集,必要时生成内部 trace/request ID,避免攻击者用重复、超长或伪造标识污染日志。
每轮只改变一个变量,例如工作负载是否纳管、ServiceAccount、目标版本、策略或证书。时间窗、请求数、并发、连接复用和客户端配置保持不变。否则 90/10 路由、证书轮换和重试行为会被样本量或连接池差异误导。
实验必须位于与生产路由、共享信任根和共享控制面隔离的专用集群。mesh-lab 只是 namespace 名,它不能隔离 CRD、webhook、CNI、节点代理或根证书等集群级变更;若无专用集群,只采集现状证据,不执行故障注入和卸载。
请求 ID、连接 ID 和配置代际不能互相替代。HTTP/2、gRPC 与长连接会让多个请求共用一条传输连接;一次连接又可能在配置更新后继续沿用旧路由或旧证书。记录中至少保留三组键:
request identity = request_id + trace_id + business_id
connection identity = source_ip:port + destination_ip:port + protocol + opened_at
configuration = declaration_digest + object_generation + dataplane_version排查时先用请求身份定位业务事实,再用连接身份判断复用、排空和证书握手,最后用配置代际判断执行的是新规则还是旧规则。只保留 request ID,会把长连接上的配置漂移隐藏掉;只保留五元组,又会把同一连接中的多个业务操作混在一起。
给证据采集身份最小权限
不要让实验人员直接复用集群管理员凭据。先创建只读取 mesh-lab 业务对象的采集身份;代理配置转储、节点 flow、控制面日志和 Secret 另行按需授权,因为它们可能暴露内部地址、证书链、请求头与租户数据。
apiVersion: v1
kind: ServiceAccount
metadata:
name: mesh-evidence-reader
namespace: mesh-lab
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: mesh-evidence-reader
namespace: mesh-lab
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "serviceaccounts", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mesh-evidence-reader
namespace: mesh-lab
subjects:
- kind: ServiceAccount
name: mesh-evidence-reader
namespace: mesh-lab
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: mesh-evidence-reader应用前先切到隔离集群并核对上下文,再保存清单摘要:
kubectl config current-context
kubectl apply -f mesh-evidence-reader.yaml
sha256sum mesh-evidence-reader.yaml
kubectl auth can-i get pods/log \
--as=system:serviceaccount:mesh-lab:mesh-evidence-reader \
-n mesh-lab
kubectl auth can-i get secrets \
--as=system:serviceaccount:mesh-lab:mesh-evidence-reader \
-n mesh-lab
kubectl auth can-i list pods \
--as=system:serviceaccount:mesh-lab:mesh-evidence-reader \
-n kube-system预期依次为 yes、no、no。若读取 Secret 或跨 namespace 查询返回 yes,立即停止采集并修正 RoleBinding;不要把过宽权限解释为排障便利。清理时先删除 RoleBinding 阻断新访问,再删除 Role 和 ServiceAccount,并用同一组 kubectl auth can-i 确认权限已经消失。审计平台还应能关联授权人、使用者、时间窗和读取对象;RBAC 的 no 只能证明本次授权判定,不证明历史导出文件已经销毁。
从无网格直连建立权威基线
安装任何控制面之前,先证明应用和基础网络工作。基线至少保存:
client 解析到的 Service 名称与地址;EndpointSlice 中的 Pod IP、版本 label 和 readiness;直接请求的状态码、服务端版本、请求 ID 与延迟;
目标应用访问日志和接收计数;NetworkPolicy、CNI 和 DNS 的当前状态。
可使用一个最小循环生成证据,真实命令按镜像内可用工具调整:
kubectl -n mesh-lab get pod,service,endpointslice -o wide
kubectl -n mesh-lab exec deploy/client-meshed -- sh -ceu '
i=1
while [ "$i" -le 20 ]; do
id="baseline-$i"
curl --fail-with-body --silent --show-error \
-H "x-lab-request-id: $id" \
http://orders.mesh-lab.svc.cluster.local/orders/demo
printf "\n"
i=$((i + 1))
done
'预期不是“命令不报错”,而是 20 次响应都能关联到目标应用日志,版本计数与当前 Service selector/EndpointSlice 一致。若基线已经出现 DNS 失败、连接拒绝、无端点或应用 5xx,不要安装网格掩盖问题;先修复基础链路。
反向基线可以把 orders-v2 selector 临时改成不存在的 label,确认 EndpointSlice 变空时客户端得到怎样的 DNS、connect 或 503 证据。恢复 selector 后重新验证,这能为后续区分“网格无端点”和“应用失败”建立参照。
用一张账本收口正反实验
每轮请求都保存同一组字段,避免正例看应用日志、反例却只看控制台:
| 验证项 | 正例输入与预期证据 | 反例输入与预期证据 | 回滚后不变量 |
|---|---|---|---|
| 声明与调谐 | 合法对象的 generation 被 condition 和 observed generation 接受 | 错误引用或不支持字段得到 Accepted=False/ResolvedRefs=False 或等价原因 | 恢复旧对象后 condition 和数据面版本回到旧摘要 |
| 数据面 | 已纳管请求同时出现在源、目标执行点与应用日志 | 未纳管客户端、错误端口或绕过 waypoint 时,执行点证据按设计消失或请求被拒绝 | 路径恢复后,旧连接与新连接的行为分别符合预期 |
| 身份与策略 | 双端身份、证书有效期、信任包版本和 allow 决策关联到请求 | 错误 ServiceAccount、错误路径、明文客户端和不受信根分别产生可区分拒绝 | 回滚策略后只恢复预期访问,错误身份仍不能越权 |
| 业务 | 服务端版本、业务 ID、幂等键和权威结果一致 | 无端点、业务 409、上游 5xx 与响应丢失不被同一重试规则吞并 | 应用写入次数和预期预算不因回滚增长 |
| 观测 | access log/flow、metric 和 Trace 各自与 request ID 及时间窗匹配 | 去掉 Trace 上下文、停止 scrape 或制造日志丢失后,业务事实与观测缺口被明确分开 | 采集恢复后无持续丢样、高基数或敏感字段残留 |
一轮只有在正例、反例和回滚三列都有实际输出时才结束。无法制造反例的共享环境,不能用预测结果补齐账本。
证明流量接管,而不是证明资源存在
sidecar 路径
sidecar 型数据面至少核对四件事:
新 Pod 模板是否命中 injection 规则,旧 Pod 是否保持原状;init container、CNI 或 native sidecar 怎样建立重定向;应用监听端口、排除端口、探针和 loopback 流量是否进入代理;
代理实际连接哪个控制面、装载哪个配置版本。
Pod 出现 istio-proxy 或 linkerd-proxy 仍不足够。用代理状态命令、配置转储或 flow 证明请求在源代理产生 outbound 记录,在目标代理产生 inbound 记录,并与目标应用请求 ID 对齐。随后从 client-plain 发起同样请求,预期证据必须不同;若两者完全相同,就要检查目标端口是否绕过代理、策略是否只在某一侧执行。
ambient 路径
ambient 接入通常不在业务 Pod 中增加 sidecar。验证应从节点 ztunnel、工作负载映射、HBONE 连接和目标 waypoint 逐段取证;ztunnel 与 waypoint 的职责分层以 Istio ambient 数据面说明 和目标版本的 feature status 为准:
namespace 或 workload label 只说明候选集合;ztunnel 确认源/目标 workload identity 和 L4 连接;waypoint 只在需要且被正确选择时承担 L7 路由、授权和遥测;
没有 waypoint 时,HTTP 方法和路径策略不应被假装成已执行。
正例请求保存源、目标 ztunnel 的连接与身份;增加 HTTPRoute 或 L7 授权后,再证明请求进入目标 waypoint。反例删除 waypoint 绑定或使用错误 parentRef,预期 L4 仍可能成功,而 L7 行为和遥测发生明确变化。
还要保留一个网格外来源。Istio 当前数据面文档明确指出,网格外 workload 不理解 waypoint,可能直接连接目标;来自 sidecar 与 gateway 的流量是否经过 waypoint 也受目标版本和迁移配置约束。因此“目标绑定 waypoint”不是 L7 强制执行证据。若未纳管来源仍能成功,必须用目标侧身份策略、NetworkPolicy 或等价底层控制封口,并分别证明允许的网格身份成功、明文或无身份来源失败。
eBPF 与节点 Envoy 路径
Cilium 环境需要把 eBPF datapath 与 Envoy redirect 分开观察。Hubble flow 可以证明源 identity、目标 identity、verdict、端口和 L3/L4 路径;启用 L7 policy 后,还要证明流量经过目标 Envoy,并产生对应 L7 verdict/metric。看到业务 Pod 没有 sidecar,只能说明代理没有与 Pod 同生命周期,不能推出流量没有代理开销。
反例应覆盖未启用 L7 policy、错误 CiliumNetworkPolicy、Envoy 不可用和节点 agent 重启。每种故障对已有连接、新连接、L3/L4 verdict 与 L7 观测的影响不同,不能统一写成“Cilium 不可用”。
Hubble 的 allow verdict、透明加密和 Cilium Mutual Authentication 也是三类不同证据。当前稳定文档仍把 Mutual Authentication 标为 Beta,并说明它只在 Cilium 管理的单集群内工作,尚不能与 Cluster Mesh 组成统一信任域,也不兼容外部 mTLS。需要跨集群工作负载身份或既有企业 PKI 互操作时,应将候选标记为不满足,而不是用 WireGuard/IPsec 的节点加密替代工作负载双向认证。
身份证据要同时来自两端
一次 mTLS 成功至少绑定以下信息:
source workload = client-meshed
source identity = <trust-domain>/ns/mesh-lab/sa/client
destination workload = orders-v1
destination identity = <trust-domain>/ns/mesh-lab/sa/orders
certificate validity = notBefore / notAfter
trust bundle version = <digest-or-generation>
transport = mTLS / HBONE / implementation-specific secure path
request id = lab-<id>只读取源代理证书不能证明目标实际展示了预期身份;只看目标端日志也不能证明源身份没有被网关或代理替换。双方证书、身份 metric/log、连接和应用请求必须位于同一时间窗。
最小反例包括错误 ServiceAccount、未纳管客户端、过期或不受信任根、明显时钟偏移。策略处于 PERMISSIVE 时,明文客户端成功不代表 mTLS 失败;它只说明目标允许明文。切换到 STRICT 或等价安全策略后,网格身份成功、明文失败,才能证明 admission 和 transport 两个状态都成立。
策略证据必须包含决策输入和执行点
授权结论至少记录:
策略对象名称、namespace、generation 和目标选择器;执行点是源代理、目标代理、waypoint、节点 Envoy 还是外部授权服务;实际输入身份、目标服务、端口、协议、方法与路径;
allow/deny/no-match/error 的结论;本地响应还是上游响应;策略回滚后的配置版本和请求结果。
默认拒绝应分批发布。先建立审计或 dry-run 证据,确认正常流量身份;再对一个低风险服务拒绝未声明访问;最后逐步扩大。直接在根 namespace 发布宽泛 deny,可能把 DNS、探针、遥测或控制面流量一起阻断。
正向矩阵至少包含允许 ServiceAccount 访问允许路径;反向矩阵包含错误 ServiceAccount、正确身份访问错误路径、未纳管客户端和策略配置错误。四种失败都返回 403 时,仍要从代理日志、policy status 或 external auth 日志区分原因。
端点、路由和重试要用计数证明
权重路由不能用十次请求下结论。先确定连接是否复用、负载均衡在请求级还是连接级,然后选择样本量和容差。例如目标为 90/10,发送 1000 个独立或受控复用请求,保存 v1/v2 计数、置信区间和每个代理/端点的请求数。若只建立少量长连接,请求分布可能被连接级选择固定。
重试实验必须记录原始请求数、后端尝试数、最终成功数和截止时间:
amplification = upstream_attempts / client_requests
deadline_used = final_response_time / client_deadline
retry_success = requests_succeeded_after_retry制造 orders-v2 慢响应时,分别测试 GET 与带幂等键的写请求。源代理、gateway、客户端 SDK 和上游若都配置重试,实际尝试数可能乘法增加。停止条件应同时限制总 deadline、最大尝试、重试流量比例和队列水位,不能只写 retries: 3。
无端点、连接拒绝、reset、响应头后断连、HTTP 503 和业务 409 需要分别测试。代理能否重试取决于失败发生阶段、协议、请求体是否可重放和配置;业务冲突不应被基础设施自动重放。
配置收敛需要声明、状态和执行版本三方一致
对每次配置变更保存三层摘要:
Git/发布系统中的期望对象与提交 ID;Kubernetes/控制面对象的 generation、condition 与 observed generation;数据面实际装载的 route/cluster/listener/policy 版本或等价摘要。
Route 的 Accepted=True 但 ResolvedRefs=False、对象 generation 已增加但代理仍旧版本、部分代理 STALE 或 NOT SENT,都应阻止扩大流量。Programmed 并不是所有 Route 类型都定义的通用 condition;只在目标 API 和实现明确提供时使用,不能把 Gateway 的状态字段机械套到 Service 附着的 Route。配置转储可能包含内部域名、IP、证书、header 和集群拓扑,只在受控环境采集,归档前按字段允许列表脱敏并设置保留期。
控制面断连实验要分别观察:
已有连接是否继续;新连接是否能使用最后已知配置;endpoint 变化是否收敛;
新策略和撤销是否传播;新实例能否冷启动;证书到期前能否续签。
只有已有连接继续成功时,结论是“在本次断连和缓存条件下已有连接存活”,不能推广成控制面故障不影响业务。
响应责任决定排障方向
建立统一故障分类:
| 现象 | 第一证据 | 常见责任层 | 下一步 |
|---|---|---|---|
| DNS 失败 | DNS query/response、Service/EndpointSlice | DNS、服务发现、网络策略 | 核对名称、搜索域、endpoint 与 DNS 流量 |
| 连接拒绝/超时 | flow、TCP state、目标 listener | 网络、捕获规则、目标进程 | 区分丢包、无监听、代理未接管 |
本地 403 | 代理 response flag/policy decision | 网格授权或外部授权 | 核对身份、作用域、方法路径和策略版本 |
本地 503 | source proxy cluster/endpoint state | 端点、路由、配置同步 | 核对 xDS/KDS、EndpointSlice、outlier 状态 |
上游 5xx | 目标代理和应用日志都有请求 | 应用或目标依赖 | 用业务 request ID 继续追踪 |
| 延迟放大 | attempt count、queue、连接池、Trace | 重试、排队、故障转移 | 计算总 deadline 和放大系数 |
| 拓扑无流量 | metric scrape、时间窗、reporter | 遥测链或无真实流量 | 不先重启应用,逐段验证采集 |
代理状态码不能覆盖应用状态码。日志建议同时保存 downstream response、upstream response、response flags/termination details 和 upstream host;字段名称随实现变化,但责任分层必须保留。
根据组织约束选择数据面
sidecar 优先的信号
需要成熟的每工作负载身份和 L7 策略;已有 CNI 不希望整体迁移;能接受每 Pod 资源、注入和滚动升级成本;
希望故障与资源更贴近单个 workload;团队能治理版本 skew、端口捕获和代理生命周期。
Istio sidecar 提供更广的 L7 能力与生态;Linkerd 更强调轻量 Rust proxy、Kubernetes ServiceAccount identity 和简化运维。Linkerd Releases and Versions明确区分项目版本与 edge artifact,选择仍需绑定目标版本、发行渠道、Gateway API 和支持合同。
ambient 优先的信号
希望 L4 安全覆盖与应用 Pod 生命周期解耦;大部分服务只需要身份、mTLS 和 L4 授权;能为少量 L7 服务独立部署和扩容 waypoint;
能接受节点级 ztunnel/CNI 升级故障域;已验证目标能力的 feature stage 和平台矩阵。
ambient 不适合用“减少 sidecar”一条理由拍板。若几乎所有服务都需要 L7 waypoint,资源、拓扑和共享故障域要重新测量。
Cilium 组合优先的信号
愿意把 CNI、NetworkPolicy、负载均衡、流观测和网格统一治理;平台满足内核、Kubernetes 和云网络要求;L3/L4 identity 与 Hubble 是主要价值;
能承担节点特权、eBPF 调试和 CNI 迁移;已从目标版本的 Cilium Mutual Authentication 与 Gateway API 文档核对认证、GAMMA 和 L7 能力成熟度。
这条路径的退出成本通常高于普通应用 Helm release,因为 CNI、节点路由、Pod 重建和网络安全策略都参与迁移。
若业务要求跨 Cluster Mesh 统一工作负载 mTLS、接入外部 mTLS 体系,或只允许稳定级安全能力,当前 Mutual Authentication 的 Beta 状态与官方限制会直接否决这条组合;不能因为 L3/L4 flow 可见、NetworkPolicy 生效或节点间已加密,就把身份认证要求标记为满足。
Consul 或 Kuma 优先的信号
Kubernetes 与 VM/裸机长期并存;需要跨数据中心或 multi-zone 控制面;能管理 VM 注册、代理进程、证书和透明代理规则;
接受额外 service catalog/zone 状态和网络连通责任;已核对许可、edition、策略版本和商业支持。
混合平台能力不是零配置互通。每个 zone/datacenter 的服务身份、信任、DNS/地址、gateway、故障转移和退出清理都要有 owner。
用资源账本比较成本
成本至少拆成四类:
| 成本组 | 量化输入 | 容易遗漏的峰值 |
|---|---|---|
| 数据面 | proxy/ztunnel/Envoy CPU、RSS、连接、RPS、payload、TLS、L7 policy | 证书轮换、重试风暴、长连接重建、节点滚动 |
| 控制面 | workload/service/config/proxy 数、变更率、集群数 | 批量发布、API 限流、远端断连后全量重同步 |
| 遥测 | label cardinality、scrape interval、日志字节、采样率、保留期 | 故障时日志爆发、Trace 全采样、拓扑查询 |
| 组织 | 平台开发、值班、升级、证书、策略评审、培训与商业支持 | 双轨迁移、事故演练、归档项目退出 |
官方 benchmark 只能提供实验方法和初始量级,不能替代真实服务图、协议、策略、硬件和遥测配置。候选环境用生产代表性流量和故障放大系数测 p50/p99、CPU/RSS、配置收敛、连接恢复和遥测成本;任何一项超过预算都要停止扩大。
成本账本还要记录“每个受保护请求”的单位成本:
mesh_unit_cost = (data_plane + control_plane + telemetry + support) / protected_requests
evidence_cost = (log_bytes + trace_bytes + flow_events) / diagnosable_requests分母不能用所有集群请求冒充。未纳管、旁路、采样后无法定位或只保留聚合指标的请求,不属于可保护或可诊断请求。这样才能识别“代理资源下降,但 waypoint 热点与日志成本上升”一类结构性转移。
退出实验是选型的一部分
安装实验当天就执行一次退出:
导出 CRD、values、policy、证书引用和版本清单;停止新注入或新纳管,保留控制面;让一个低风险工作负载恢复非网格路径;
验证 DNS、NetworkPolicy、应用超时、身份和调用结果;滚动其余工作负载,确认无旧 sidecar、label、waypoint 绑定或流量捕获;删除扩展、控制面和集群级资源;
检查 webhook、CRD、RBAC、CNI/iptables/eBPF、证书、日志、Trace 和账单残留。
删除 CRD 可能级联删除策略事实,卸载 CNI 可能让现有 Pod 失去网络,移除根信任可能中断仍在运行的代理。清理命令只能在隔离环境直接执行;共享集群必须先做对象清单、备份、依赖图、候选验证和回滚演练。
退出不以 Helm release 消失或控制面 Pod 归零为完成条件。停止删除的条件是:非网格正反请求仍未通过、旧数据面还有新连接、身份或出口没有替代控制、替代告警未生效,或节点网络状态无法恢复。只有业务基线稳定、旧连接和策略引用归零、凭据已撤销、遥测保留策略已执行,且代理、节点、存储、负载均衡与商业许可成本已核销,才能结束退出。
团队门禁把证据变成日常能力
每次网格变更至少由服务 owner、平台 owner 和安全/身份 owner 共同确认:
服务 owner 证明业务基线、幂等和权威结果;平台 owner 证明数据路径、配置收敛、容量、回滚和资源核销;安全 owner 证明身份、信任、授权、敏感字段与凭证生命周期;
SRE/值班 owner 证明告警能区分 DNS、网络、代理、策略、端点和应用故障;成本 owner 追踪代理、节点、控制面、遥测和商业许可的单位成本。
发布记录保存精确版本、配置摘要、实验 request ID、正反结果、停止条件和回滚证据。配置转储、证书、访问日志与 Trace 到期销毁;策略例外带 owner、原因和失效时间。这样得到的不是“网格装好了”,而是一条能被复核、能在故障中解释、也能安全退出的服务通信能力。
