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

【Istio 控制面】istiod 进程模型:discovery、config、CA 与注入的边界

文章导航

分类入口
networkkubernetes
标签入口
#istio#istiod#pilot-discovery#control-plane#ca#sidecar-injection#leader-election

目录

第 1 篇 说清楚了 istiod 是 Pilot、Galley、Citadel、注入 webhook 合并后的单一二进制,但「合并成一个进程」不等于「所有职责共享同一条执行路径」。生产上常见的误判是:CA 证书轮转出问题,运维第一反应去查 xDS 推送日志;或者 sidecar 注入失败,去重启 istiod 指望「重启 discovery 服务」——这些操作大概率无效,因为它们是同一进程里彼此独立的组件,启动顺序、失败模式、乃至多副本行为都不同。本文只回答一件事:pilot-discovery 这个二进制里,discovery、config、CA、injection 各自的边界在哪,哪些组件在 xDS 推送的热路径上,哪些不在。

本文是「Istio / 网格控制面内核」系列第 2 篇(共 16 篇)。→ 系列目录

上接 第 1 篇 · 控制面全景;下接 第 3 篇 · 代理身份与订阅

版本锚定:Istio 1.30.3istio/istio tag)。源码路径:pilot/cmd/pilot-discovery/pilot/pkg/server/instance.gopilot/pkg/bootstrap/server.gopilot/pkg/leaderelection/。本文不做 CA 证书运维手册,只钉边界;证书轮转细节留给后续 SDS/安全相关篇目视需要展开。


一、一个二进制,一个通用组件运行器

pilot-discoverypilot/cmd/pilot-discovery/)是 istiod 实际运行的可执行文件。它内部不是一坨顺序执行的初始化代码,而是先构造一个通用的组件运行器 server.Instancepilot/pkg/server/instance.go),再把 discovery、CA、注入、校验等具体功能注册成一个个 Component

// pilot/pkg/server/instance.go(Istio 1.30.3,节选)
type Component func(stop <-chan struct{}) error

type Instance interface {
    Start(stop <-chan struct{}) error
    RunComponent(name string, t Component)
    RunComponentAsync(name string, t Component)
    RunComponentAsyncAndWait(name string, t Component)
    Wait()
}

Instance.Start 先把所有已注册的启动期组件排空执行,任何一个返回错误就整体启动失败并退出;启动完成后转入一个后台循环,继续接收运行期动态注册的组件。这个抽象本身不关心业务语义——它只保证「命名组件」「异步或同步」「关停时是否要等它跑完」这三件事被统一处理。真正决定「discovery 是什么、CA 是什么」的,是 pilot/pkg/bootstrap/server.go 里的 bootstrap.Server:它持有一个 server.Instance,把发现服务、证书颁发、注入 webhook、校验 webhook 等分别包成 Component,用 addStartFunc/addTerminatingStartFunc 注册进去。


二、启动顺序:CA 必须先于安全 gRPC,注入必须等 CA 就绪

bootstrap.NewServer 里的初始化顺序不是随意排列,而是显式的依赖链(pilot/pkg/bootstrap/server.go,1.30.3):

// pilot/pkg/bootstrap/server.go(Istio 1.30.3,节选,NewServer 主干顺序)
if err := s.maybeCreateCA(caOpts); err != nil { return nil, err }
if err := s.initControllers(args); err != nil { return nil, err }
InitGenerators(s.XDSServer, configGen, args.Namespace, s.clusterID, s.internalDebugMux)
if err := s.initWorkloadTrustBundle(args); err != nil { return nil, err }
if err := s.initIstiodCerts(args, string(istiodHost)); err != nil { return nil, err }
// Secure gRPC Server must be initialized after CA is created as may use a Citadel generated cert.
if err := s.initSecureDiscoveryService(args, ...); err != nil { return nil, err }
if s.kubeClient != nil {
    s.initSecureWebhookServer(args)
    wh, err := s.initSidecarInjector(args)
    if err := s.initConfigValidation(args); err != nil { return nil, err }
}
s.initRegistryEventHandlers()
s.initDiscoveryService()

代码里的注释「Secure gRPC Server must be initialized after CA is created as may use a Citadel generated cert」直接点明了依赖方向:istiod 自己的 gRPC/HTTPS 服务端证书,可能就是自己签发的——CA 组件不只是给数据面代理签证书,也给 istiod 自己的安全监听端口签证书。这决定了初始化顺序不能颠倒:先有 CA(maybeCreateCA),再有 istiod 自身证书(initIstiodCerts),再有用这份证书的安全 gRPC 服务(initSecureDiscoveryService)和注入/校验 webhook 的 HTTPS 服务(initSecureWebhookServer)。initControllers(配置与服务发现控制器)和 InitGenerators(把 XDS 生成器注册到 DiscoveryServer)则在 CA 之后、证书之前完成——它们不依赖证书,只依赖 K8s client 是否就绪。

flowchart TD
  ca["maybeCreateCA"] --> ctrl["initControllers<br/>Config + Service Discovery"]
  ctrl --> gen["InitGenerators<br/>register XDS generators"]
  ca --> certs["initIstiodCerts<br/>istiod's own cert"]
  certs --> secgrpc["initSecureDiscoveryService"]
  certs --> webhookhttps["initSecureWebhookServer"]
  webhookhttps --> inject["initSidecarInjector"]
  webhookhttps --> validate["initConfigValidation"]
  ctrl --> disc["initDiscoveryService<br/>plaintext + ADS handler"]

三、四大职责在源码里对应哪些组件

结合 architecture/networking/pilot.md(1.30.3 官方架构文档)的三段式描述与 bootstrap.Server 的字段,可以把「discovery / config / CA / injection」精确映射到代码:

职责 官方文档定位 关键源码对象 边界
Discovery(配置服务化) Config Serving:接受代理 gRPC 连接,按请求或推送发送资源 pilot/pkg/xds/discovery.goDiscoveryServerbootstrap.Server.XDSServer 只认已经在 PushContext 里的数据,不直接读 K8s API
Config(配置摄取与翻译) Config Ingestion + Config Translation ConfigStore/ServiceDiscovery 聚合层(pilot/pkg/bootstrap/configcontroller.goservicecontroller.go)、model.PushContext、各 XdsResourceGenerator 产出喂给 Discovery 的输入,自己不发送 gRPC 响应
CA(证书颁发) 官方 Architecture:Istiod acts as a Certificate Authority security/pkg/pki/cabootstrap.Server.CA/RA/caServer),由 maybeCreateCA/startCA 驱动 签发工作负载与 istiod 自身证书;不参与 xDS 资源生成
Injection(注入与校验) 官方文档未单列,但代码明确拆成独立 webhook pilot/pkg/bootstrap/sidecarinjector.gowebhook.govalidation.go 是 K8s MutatingWebhookConfiguration/ValidatingWebhookConfiguration 的 HTTPS 后端,响应对象是 API Server,不是数据面代理

官方架构文档把 Discovery 拆得更细——Config Ingestion 里 ConfigStoreServiceDiscovery 是两条独立聚合管线,前者给出通用资源(config.Config),后者预计算 model.Service/model.ServiceInstance 这类服务化视图;两者都汇入不可变的 PushContext 快照。这条链路的细节和「翻译成哪种 xDS 类型」留给第 5–9 篇,本篇只需要确认一点:Config 这一层不直接对外提供 gRPC 服务,它是 Discovery 的输入,不是并列的另一个网络端点。


四、什么在 xDS 推送热路径上,什么不在

排障时最容易搞混的就是这条边界。把 istiod 内部划成「热路径」和「非热路径」两组:

组件 是否在 xDS 推送热路径上 依据
DiscoveryServer.pushChannel / debounce / PushQueue 每次配置变化都要经过这条链路才能变成推送(第 4 篇展开)
XdsResourceGenerator(CDS/EDS/RDS/LDS 等) pushXds 直接调用 gen.Generate,是每次推送的 CPU 主要开销来源之一
CA 证书签发(istio-ca gRPC 服务) 只在代理首次连接或证书临近过期时触发一次 CSR 请求-响应,不随每次配置推送触发
注入 Webhook(initSidecarInjector 由 K8s API Server 在 Pod 创建时同步调用,与 xDS 推送时机无关
配置校验 Webhook(initConfigValidation 由 API Server 在资源写入时调用,校验通过与「该资源是否已经被翻译并推送」是两件事
NamespaceControllerGatewayStatusController 等写回状态的控制器 否,且需要选主 见下节

这条边界解释了两类常见的误判:CA 或注入 webhook 出问题时,xDS 推送通常完全正常——因为它们不共享同一条执行路径,只共享同一个进程和同一份证书信任链;反过来,xDS 推送延迟增大时,先查 PushQueue/pushChannel,而不是怀疑证书或 webhook,除非现象是「代理连接建立失败」(这才会牵扯到安全 gRPC 服务的证书链)。


五、多副本 istiod:xDS 无主,部分控制器要选主

istiod 通常以多副本 Deployment 运行,Service 把连接分散到各副本。xDS 推送本身不需要选主:每个副本各自维护自己的 adsClientsPushQueue,只服务连到自己的代理,互不通信也不需要互相同步谁在推什么——一个代理连到哪个副本,就由那个副本的 PushContext 状态服务它。

但并非所有组件都这样。pilot/pkg/leaderelection/leaderelection.go(1.30.3)定义了一组需要选主的锁名,包括 NamespaceControllerGatewayStatusControllerStatusControllerIngressControllerGatewayDeploymentController 等——这些组件的共同特征是要把状态写回 K8s API(例如 Gateway 资源的 status 字段、Ingress 的地址回填)。如果不选主,多个副本会重复写、产生冲突或抖动的状态更新。这是一条清晰的判断规则:只读、只服务代理连接的组件(Discovery、Config 聚合)天然多活;任何要写回集群状态的组件(状态上报、Gateway 资源生成)都走 leader election,同一时刻只有一个副本在执行。

flowchart LR
  subgraph Replica1["istiod replica A"]
    d1["DiscoveryServer<br/>(always active)"]
  end
  subgraph Replica2["istiod replica B"]
    d2["DiscoveryServer<br/>(always active)"]
  end
  subgraph Elected["Leader-elected (one replica)"]
    gsc["GatewayStatusController"]
    nsc["NamespaceController"]
  end
  Replica1 -.->|"lease contention"| Elected
  Replica2 -.->|"lease contention"| Elected

六、争论与开放问题

谱系:把「配置服务」与「证书颁发」揉进同一个二进制,是 Istio 1.5 的产品化决策(第 1 篇已引用官方博客),动机是运维复杂度而非性能。这与「微服务化控制面」的早期设计哲学(每个职责独立部署、独立伸缩)是直接的路线反转——本篇给出的边界表恰恰说明:进程合并了,故障域并没有完全合并,运维排障仍要按组件而不是按进程去定位问题。

开放问题:官方架构文档承认「Istiod 是一个 modular monolith」,模块之间通过内存共享的 PushContextConfigStore 接口通信而非网络调用,这在单副本内部降低了延迟,但也意味着任何模块的 panic 或长时间阻塞都能拖累同一进程内的其他模块(例如 CA 相关的阻塞操作若共享事件循环资源,理论上可能影响推送时延)。多大规模的集群下这种「同进程多模块」的隔离性会成为瓶颈、是否需要拆回独立进程,官方文档没有给出量化边界,这需要按具体集群规模与故障注入实测——本篇不给未跑过的数字。


七、参考资料

官方文档(A)

源码(A)

站内对照

实验台账


八、小结

  1. istiod 是一个二进制、多个 Componentserver.Instance 提供通用的注册与启动机制,discovery/CA/injection/校验各自是独立注册的组件。
  2. 启动顺序有硬依赖:CA 先于 istiod 自身证书,证书先于安全 gRPC 与 webhook HTTPS 服务;配置控制器与生成器只依赖 K8s client,不依赖证书。
  3. 热路径很窄:只有 pushChannel/debounce/PushQueue/Generator 这条链路直接影响 xDS 推送;CA 签发、注入 webhook、配置校验都在这条链路之外。
  4. xDS 推送无主,状态写回类控制器需要选主——这是判断「某个组件异常会不会影响其他副本」的关键区分。

系列目录 · 上一篇:控制面全景 · 下一篇:代理身份与订阅

同主题继续阅读

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

2026-08-11 · network / kubernetes

Istio / 网格控制面内核:从 CRD 到 xDS

补齐 Envoy 数据面 xDS 消费侧与 Mesh 选型叙事之间的控制面内核:istiod 推送图、CRD/HTTPRoute 翻译、Ambient 多客户端、NACK/warming 责任与排障对齐。


By .