土法炼钢兴趣小组的算法知识备份

【Istio 控制面】DestinationRule:Cluster 之上的 subset、outlier 与 TLS 挂载

文章导航

分类入口
networkkubernetes
标签入口
#istio#istiod#destinationrule#cds#subset#outlier-detection#mtls#traffic-policy

目录

第 5 篇 讲过:CDS 按「Service × 端口」生成主 Cluster,DestinationRule 存在时还会在同一步派生出 subset Cluster。第 6 篇 讲的是路由怎么选出 Cluster 名字。本文补上中间这一层:DestinationRule 具体往 Cluster 上挂了什么、四种作用域(mesh 默认 / DestinationRule 级 / port 级 / subset 级)怎么合并、结果落到哪些 Envoy Cluster 字段。Envoy 侧的 LB 算法细节与 outlier 驱逐算法不在本文重复,见 Envoy Cluster/LB连接池/outlier

本文是「Istio / 网格控制面内核」系列第 7 篇。→ 系列目录

上接 第 6 篇:VirtualService/HTTPRoute → RDS;下接 第 8 篇:Authn/Authz/SDS

版本锚定:Istio 1.30.3,源码路径对应 pilot/pkg/networking/core/cluster.gocluster_builder.gocluster_traffic_policy.go(函数签名以 master 分支验证,1.30.x 语义一致)。


一、DestinationRule 是「追加配置」,不是「新建服务」

DestinationRule.host 必须匹配一个已经存在的服务 hostname——它不会让 istiod 凭空生成一个 Cluster,只会在 BuildClusters 遍历到该 host 对应的 Service 时,追加调用 applyDestinationRulepilot/pkg/networking/core/cluster_builder.go):

destRule := proxy.SidecarScope.DestinationRule(model.TrafficDirectionOutbound, proxy, service.Hostname)
subsetClusters := cb.applyDestinationRule(defaultCluster, DefaultClusterMode, service, port,
    clusterKey.endpointBuilder, clusterKey.destinationRule.GetRule(), clusterKey.serviceAccounts)

三个直接后果:

  1. host 拼错或大小写不一致时,规则会静默不生效——不会报错,只是这条 DestinationRuleSidecarScope.DestinationRule 查找时命中不到对应 host,主 Cluster 照常按 mesh 默认生成。排障时先确认 istioctl proxy-config cluster 里是否出现了预期的 subset 名,而不是先怀疑 outlier 参数错了。
  2. SidecarScope.DestinationRule 的查找范围受 第 9 篇Sidecar egress 作用域约束——egress 未导入某个命名空间,该命名空间下再精确的 DestinationRule 也不会被这个代理看到。
  3. 同一 host 存在多个 DestinationRule 时按命名空间可见性(exportTo)和创建顺序做合并/去重,这是与 第 6 篇 里 VirtualService 冲突同构的问题——本文不重复展开。

二、TrafficPolicy 的四个组件与挂载顺序

applyTrafficPolicycluster_traffic_policy.go)从 TrafficPolicy 里拆出四个组件,按固定顺序挂到 Cluster 的不同字段:

flowchart TD
  tp["TrafficPolicy"] --> cp["connectionPool<br/>→ circuit_breakers / max_requests"]
  tp --> od["outlierDetection<br/>→ outlier_detection"]
  tp --> lb["loadBalancer<br/>→ lb_policy / lb_subset_config"]
  tp --> tls["tls<br/>→ transport_socket"]
  cp --> cluster["Envoy Cluster proto"]
  od --> cluster
  lb --> cluster
  tls --> cluster
组件 目标 Envoy 字段 备注
connectionPool circuit_breakers、连接/请求上限 对 inbound cluster 也生效(源码注释:「connection pool settings are applicable for both inbound and outbound clusters」)
outlierDetection outlier_detection 仅对 outbound 生效;OutlierDetectionHttpErrorCodes 额外走 applyOutlierDetectionErrorCodes
loadBalancer lb_policylb_subset_config 仅对 outbound 生效
tls transport_socket(经 buildUpstreamTLSSettings 结合 serviceMTLSMode(第五节)与 autoMTLSEnabled 判定是否自动升级为 ISTIO_MUTUAL

applyOutlierDetection 把 DestinationRule 的字段按名字直接映射到 Envoy OutlierDetection proto——consecutive5xxErrorsconsecutive_5xxbaseEjectionTimebase_ejection_timeconsecutiveGatewayErrorsconsecutive_gateway_failure 等,语义与默认值细节见 Envoy 连接池/outlier 篇Istio 这一层只做字段翻译,不改变 Envoy 的驱逐算法本身——这也是为什么调参手册应该查 Envoy 的 outlier 文档,而不是只看 DestinationRule 的字段说明。


三、Subset:一个 label selector,一份独立 Cluster

subsets[].labels 是对服务注册里端点标签的过滤器(见 第 5 篇 的端点聚合层),不是路由规则本身——官方文档明确提醒:「Policies specified for subsets will not take effect until a route rule explicitly sends traffic to this subset」。也就是说,声明一个 subset 只是多生成一个 Cluster(outbound|port|subsetName|hostname),真正把流量导过去要靠 VirtualService 的 route.destination.subset——两个资源各管一段,缺一个都不会报错,只会表现成「配了却没用」。

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews.default.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: LEAST_REQUEST
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
    trafficPolicy:
      loadBalancer:
        simple: ROUND_ROBIN
      outlierDetection:
        consecutive5xxErrors: 3
        interval: 5s
        baseEjectionTime: 60s

四、四层合并:mesh 默认 → DestinationRule 级 → port 级 → subset 级——但不是同一种继承

生产上最容易踩的坑是假设四层作用域都按同一种「深合并」规则叠加。源码和官方文档都不支持这个假设:

4.1 mesh 默认 → DestinationRule 级:整块兜底

applyDestinationRule 在解析完 port-level 合并结果后,先调用 applyDefaultTrafficPolicy(cb.req.Push.Mesh.GetDefaultTrafficPolicy(), trafficPolicy)。源码注释直接写明意图:

Seed the mesh-wide baseline so per-host clusters (including those with no matching DestinationRule) inherit it for any connectionPool / outlierDetection block the DR leaves unset. Subset / portLevelSettings still override on top via the merge below.

即:mesh 级 defaultTrafficPolicy 是最底层兜底,即便某个 Service 完全没有 DestinationRule,也会先套上 mesh 默认值,再谈后续覆盖。

4.2 DestinationRule 级 → subset 级:按整块字段覆盖,不逐字段深合并

官方 DestinationRule 参考文档对 Subset.trafficPolicy 的描述:「Subsets inherit the traffic policies specified at the DestinationRule level. Settings specified at the subset level will override the corresponding settings specified at the DestinationRule level.」——继承发生,但覆盖粒度是 TrafficPolicy 的四个顶级字段(loadBalancer/connectionPool/outlierDetection/tls)整块替换,不是逐个子字段合并。第三节示例里 v2 只重写了 loadBalanceroutlierDetectionconnectionPool(如果顶层配了)会原样继承;但如果 v2 也想改 outlierDetection 里的某一个字段,必须把这个块里其余想保留的字段一起重写一遍——遗漏的字段不会「从 DestinationRule 级补齐」,而是回落到 Envoy 的默认值。

4.3 DestinationRule 级 → port 级:不继承,直接落到 Envoy 默认值

这是四层里最反直觉的一层。官方文档原文:「port level settings will override the destination-level settings. Traffic settings specified at the destination-level will not be inherited when overridden by port-level settings, i.e. default values will be applied to fields omitted in port-level traffic policies。」

对照 4.2 就能看出这个不对称:

覆盖层级 未显式设置的字段 来源
subset 覆盖 DestinationRule 级 回落到 DestinationRule 顶层同名字段 DestinationRule 顶层配置
port 级覆盖 DestinationRule 级 回落到 Envoy 默认值 Envoy 自身默认,不是 DestinationRule 顶层

也就是说,同一份 trafficPolicy.portLevelSettings 一旦对某个端口写了 loadBalancer,这个端口上的 connectionPool/outlierDetection 即便顶层配置得很仔细,也不会自动带过来,而是取 Envoy 内建默认值(例如熔断阈值回到 Envoy 的温和默认,见 Envoy 熔断篇)——除非在 portLevelSettings 里把这些字段也重复写一遍。这类问题的典型症状是「只在某个端口上调过一次 LB 策略,结果这个端口的熔断阈值突然变得比其他端口宽松很多」。


五、TLS mode/context:DestinationRule 管的是「客户端怎么发」

ClientTLSSettings.mode 有四档:DISABLESIMPLE(无客户端证书的 TLS)、MUTUAL(用户提供证书的 mTLS)、ISTIO_MUTUAL(Istio 管理证书的 mTLS,证书来自 第 8 篇 的 SDS 分发)。这里最容易混淆的一点:

DestinationRule.trafficPolicy.tls 决定的是「作为客户端连接这个上游时用什么 TLS」;PeerAuthentication 决定的是「作为服务端接受什么样的入站连接」——两者是同一条连接的两端,配置在两个不同资源里。

istiod 用 PushContext.BestEffortInferServiceMTLSModepilot/pkg/model/push_context.go)在 DestinationRule 没有显式设置 tls.mode 时,读取 mesh/namespace 级 PeerAuthentication 的 mTLS 模式给出「客户端是否应自动升级到 mTLS」的推断——源码注释明确这是best-effort:「因为 PeerAuthentication 是按 workload 生效的,这个函数无法在不知道 service-to-workload 绑定关系的前提下算出精确的服务级 mTLS 模式,目前只看 mesh 与 namespace 级策略,忽略 workload/port 级策略」。这就是auto mTLS的实现基础:autoMTLSEnabledmesh.GetEnableAutoMtls().Value)打开且没有更具体信息时,Envoy 客户端侧默认按「best-effort 推断」自动切 ISTIO_MUTUAL,不需要每个 Service 手写 DestinationRule。

两者不一致时的典型故障PeerAuthentication 设了 STRICT(只收 mTLS),但某条 DestinationRule 显式把 tls.mode 写成 DISABLE 或压根没有 sidecar 的旧命名空间被一个宽泛 host(例如 *.local)的 DestinationRule 意外覆盖成 ISTIO_MUTUAL——前者是客户端发了明文给只收 mTLS 的服务端,后者是客户端对一个没有 sidecar 的目标发了它根本处理不了的 mTLS 握手,两种情况现网表现都是 503,且 CDS/RDS 单独看都「正确」。auto mTLS 的推断范围仅覆盖 mesh/namespace 级,workload 级 PeerAuthentication 需要运维在 DestinationRule 里显式对齐 tls.mode,不能指望自动推断兜底。


六、谱系、争论与开放问题

谱系:DestinationRule 把「服务发现产出的裸端点集合」与「怎么访问这些端点」显式拆成两个资源(Service/EndpointSlice 负责前者,DestinationRule 负责后者),呼应了 Envoy 自身把 Cluster 配置和 LB/outlier 参数放在同一层但由控制面统一管理的设计(Envoy 07)。这不是 Istio 独创,而是延续了「服务发现与访问策略解耦」这条更早的服务网格思路。

争论:port 级设置「不继承」而 subset 级「继承」,是不是一个自洽的设计——官方文档承认两者行为不同,但没有给出统一的设计理由说明为什么这两层选择了不同的默认值来源。一种工程判断是:port 级设置通常用于「个别端口需要完全不同的策略」(如 mTLS 管理端口),刻意不继承能避免顶层策略意外渗漏;subset 级更多用于「同一服务的不同版本做局部调参」,继承顶层更符合直觉。但这只是推测,源码与文档都没有明确写出这条取舍的动机。

开放问题:auto mTLS 的 best-effort 推断只覆盖 mesh/namespace 级 PeerAuthentication,明确放弃了 workload/port 级的精确对齐。随着 workload 级策略在生产中使用得越来越细,「客户端侧自动推断」与「服务端侧精确策略」之间的信息差会不会需要更强的一致性检查(例如在 istioctl analyze 里加一条规则),目前没有看到官方路线图。


七、参考资料

源码(A)

规范 / 官方文档(A)

站内

实验台账


八、小结

  1. DestinationRule 不创建服务,只在 host 匹配到已有 Service 时追加 Cluster 配置;host 拼错会静默失效。
  2. TrafficPolicy 的四个组件(connectionPool/outlierDetection/loadBalancer/tls)按固定顺序挂到 Envoy Cluster 的对应字段,Istio 只做字段翻译,不改变 Envoy 的算法语义。
  3. Subset 只生成 Cluster,不路由流量——真正的流量分配要靠 VirtualService 的 route.destination.subset
  4. 四层合并不是统一的深合并:subset 级覆盖会继承 DestinationRule 顶层同名字段兜底;port 级覆盖不继承,未写字段直接落到 Envoy 默认值——这是最容易被忽略的不对称。
  5. DestinationRule 的 tls.mode 管客户端出站行为,与管服务端入站的 PeerAuthentication 是同一条连接的两端;auto mTLS 的 best-effort 推断只覆盖 mesh/namespace 级——见下一篇。

系列目录 · 上一篇:VirtualService/HTTPRoute → RDS · 下一篇:Authn/Authz/SDS

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .