第
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.3(
istio/istiotag)。源码路径:pilot/cmd/pilot-discovery/、pilot/pkg/server/instance.go、pilot/pkg/bootstrap/server.go、pilot/pkg/leaderelection/。本文不做 CA 证书运维手册,只钉边界;证书轮转细节留给后续 SDS/安全相关篇目视需要展开。
一、一个二进制,一个通用组件运行器
pilot-discovery(pilot/cmd/pilot-discovery/)是
istiod
实际运行的可执行文件。它内部不是一坨顺序执行的初始化代码,而是先构造一个通用的组件运行器
server.Instance(pilot/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.go 的
DiscoveryServer(bootstrap.Server.XDSServer) |
只认已经在 PushContext 里的数据,不直接读
K8s API |
| Config(配置摄取与翻译) | Config Ingestion + Config Translation | ConfigStore/ServiceDiscovery
聚合层(pilot/pkg/bootstrap/configcontroller.go、servicecontroller.go)、model.PushContext、各
XdsResourceGenerator |
产出喂给 Discovery 的输入,自己不发送 gRPC 响应 |
| CA(证书颁发) | 官方 Architecture:Istiod acts as a Certificate Authority | security/pkg/pki/ca(bootstrap.Server.CA/RA/caServer),由
maybeCreateCA/startCA 驱动 |
签发工作负载与 istiod 自身证书;不参与 xDS 资源生成 |
| Injection(注入与校验) | 官方文档未单列,但代码明确拆成独立 webhook | pilot/pkg/bootstrap/sidecarinjector.go、webhook.go、validation.go |
是 K8s
MutatingWebhookConfiguration/ValidatingWebhookConfiguration
的 HTTPS 后端,响应对象是 API Server,不是数据面代理 |
官方架构文档把 Discovery 拆得更细——Config Ingestion 里
ConfigStore 与 ServiceDiscovery
是两条独立聚合管线,前者给出通用资源(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 在资源写入时调用,校验通过与「该资源是否已经被翻译并推送」是两件事 |
NamespaceController、GatewayStatusController
等写回状态的控制器 |
否,且需要选主 | 见下节 |
这条边界解释了两类常见的误判:CA 或注入 webhook
出问题时,xDS
推送通常完全正常——因为它们不共享同一条执行路径,只共享同一个进程和同一份证书信任链;反过来,xDS
推送延迟增大时,先查
PushQueue/pushChannel,而不是怀疑证书或
webhook,除非现象是「代理连接建立失败」(这才会牵扯到安全
gRPC 服务的证书链)。
五、多副本 istiod:xDS 无主,部分控制器要选主
istiod 通常以多副本 Deployment 运行,Service
把连接分散到各副本。xDS
推送本身不需要选主:每个副本各自维护自己的
adsClients、PushQueue,只服务连到自己的代理,互不通信也不需要互相同步谁在推什么——一个代理连到哪个副本,就由那个副本的
PushContext 状态服务它。
但并非所有组件都这样。pilot/pkg/leaderelection/leaderelection.go(1.30.3)定义了一组需要选主的锁名,包括
NamespaceController、GatewayStatusController、StatusController、IngressController、GatewayDeploymentController
等——这些组件的共同特征是要把状态写回 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」,模块之间通过内存共享的
PushContext、ConfigStore
接口通信而非网络调用,这在单副本内部降低了延迟,但也意味着任何模块的
panic
或长时间阻塞都能拖累同一进程内的其他模块(例如 CA
相关的阻塞操作若共享事件循环资源,理论上可能影响推送时延)。多大规模的集群下这种「同进程多模块」的隔离性会成为瓶颈、是否需要拆回独立进程,官方文档没有给出量化边界,这需要按具体集群规模与故障注入实测——本篇不给未跑过的数字。
七、参考资料
官方文档(A)
- Istio 1.30 文档,Architecture(istiod = discovery + config + CA 的产品定位)。
源码(A)
istio/istiotag 1.30.3:architecture/networking/pilot.md(Config Ingestion / Translation / Serving 三段式官方描述)。istio/istiotag 1.30.3:pilot/pkg/server/instance.go(Instance/Component通用运行器)。istio/istiotag 1.30.3:pilot/pkg/bootstrap/server.go(NewServer初始化顺序:maybeCreateCA、initControllers、initIstiodCerts、initSecureDiscoveryService、initSidecarInjector、initConfigValidation、initDiscoveryService)。istio/istiotag 1.30.3:pilot/pkg/leaderelection/leaderelection.go(需要选主的控制器清单)。
站内对照
实验台账
- 本篇不粘贴
kubectl get pods -n istio-system或 istiod 日志实跑输出;组件边界与启动顺序均以源码为准。
八、小结
- istiod 是一个二进制、多个
Component:server.Instance提供通用的注册与启动机制,discovery/CA/injection/校验各自是独立注册的组件。 - 启动顺序有硬依赖:CA 先于 istiod 自身证书,证书先于安全 gRPC 与 webhook HTTPS 服务;配置控制器与生成器只依赖 K8s client,不依赖证书。
- 热路径很窄:只有
pushChannel/debounce/PushQueue/Generator这条链路直接影响 xDS 推送;CA 签发、注入 webhook、配置校验都在这条链路之外。 - xDS 推送无主,状态写回类控制器需要选主——这是判断「某个组件异常会不会影响其他副本」的关键区分。
→ 系列目录 · 上一篇:控制面全景 · 下一篇:代理身份与订阅
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Istio 控制面】控制面全景:从 CRD 到 xDS 的翻译与推送内核
定位 Istio 控制面内核相对 Envoy 消费侧、Service Mesh 税文与 Gateway API 资源模型的缺口;给出五条坐标系、16 篇地图与本系列明确不写的范围,钉住 istiod 1.30.3 为主线。
【Istio 控制面】PeerAuthentication / AuthorizationPolicy → filter chain 与 SDS:策略怎么变成 RBAC 与证书
拆解 PeerAuthentication 如何落到入站 filter chain 的 mTLS 要求、AuthorizationPolicy 如何编译成 Envoy RBAC filter 的 permission/principal,以及 istiod CA 签发证书后 istio-agent 作为本地 SDS server 如何把 SPIFFE 身份证书喂给 Envoy;策略语义不重复展开,见零信任系列。
Istio / 网格控制面内核:从 CRD 到 xDS
补齐 Envoy 数据面 xDS 消费侧与 Mesh 选型叙事之间的控制面内核:istiod 推送图、CRD/HTTPRoute 翻译、Ambient 多客户端、NACK/warming 责任与排障对齐。
【Istio 控制面】Service / Endpoint / Workload → CDS/EDS:istiod 如何拼出一个 Cluster
拆解 istiod 如何把 K8s Service、EndpointSlice、WorkloadEntry 聚合进内部服务模型,再经 CdsGenerator/EdsGenerator 分别生成 Cluster 与 ClusterLoadAssignment;钉住命名契约与部分推送边界,subset 留给 DestinationRule 篇。