Day 02 / 共 20 天 · 第 1 周 控制面全景

istiod 启动

istiod 是个"模块化单体"——一个进程装了配置摄取、xDS 生成、CA 等一堆模块。今天追它的启动:pilot-discovery 入口 → NewServer(装配总入口)→ Start(开始监听 xDS)。

📍 你在控制面 istiod 之旅的位置(昨天看了全景,今天看总调度室怎么"开门营业")
D1 全景· D2 启动· D3 配置CRD· D4 服务注册· D5 快照· D6 xDS服务· D7 生成· D8 Builder· D9 推送· D10 连接
L01

二进制入口

🤔 痛点:一个大脑要管这么多事,怎么"开机"? istiod 一个进程里塞了配置摄取、xDS 生成、证书 CA、注入 webhook……如果开机顺序乱来(比如还没连上 K8s 就开始给 Envoy 发配置),会发出残缺配置酿成事故。所以"怎么按顺序装配、什么时候才开门"本身就是一门学问。
💡 本质:开一家总调度室的标准流程 启动就像新店开业:先装修布置(NewServer 把各模块装配好)接通水电、清点货物(等 K8s 缓存同步)挂"营业中"招牌(标记 ready)开门迎客(在端口 serve xDS)。货没盘点完绝不开门,免得客人买到空气。
// pilot/cmd/pilot-discovery/main.go:23 极简 main,构造 cobra 根命令
func main() {
  rootCmd := app.NewRootCommand()
  rootCmd.Execute()
}
// app/cmd.go: NewRootCommand(:41)注册 discovery/version/request 子命令
//   newDiscoveryCommand(:71)是核心子命令
读法:istiod 的二进制名叫 pilot-discovery(历史原因,Pilot 是 istiod 的配置代理模块)。main 极简——cobra 命令行框架,核心是 discovery 子命令。
L02

RunE 三步

// app/cmd.go newDiscoveryCommand RunE(:92)启动主逻辑:
stop := make(chan struct{})
discoveryServer, _ := bootstrap.NewServer(serverArgs)  // 构造(装配)
discoveryServer.Start(stop)                            // 启动(监听)
cmd.WaitSignal(stop)                                   // 阻塞等信号
discoveryServer.WaitUntilCompletion()                  // 优雅退出
读法:启动就三步:NewServer(把所有模块装配好)→ Start(开始监听/服务)→ WaitSignal(阻塞运行直到收到退出信号)。和 Envoy 课 Day 02 的"构造 → run → 等信号"结构一模一样——不同项目同一套启动骨架。
NewServer装配所有模块 Start缓存同步→ready→serve WaitSignal阻塞运行 收到 Ctrl-C
istiod 启动三步曲:装配 → 启动服务 → 阻塞等退出信号。几乎所有 Go 长驻服务都是这个骨架。
L03

关键端口

// app/cmd.go addFlags(:159)重要默认端口:
// HTTP :8080 | Webhook HTTPS :15017 | 监控 :15014
// 明文 gRPC(xDS) :15010  |  安全 gRPC(xDS) :15012  ★
15010 vs 15012:明文 vs mTLS istiod 在两个端口提供 xDS 服务::15012(安全,mTLS)——生产用,Envoy 用工作负载证书双向认证连它;:15010(明文)——调试用,不加密。Envoy 课讲的 bootstrap 里那个"discovery address",指向的就是这里(通过 pilot-agent 代理)。记住 15012 是生产 xDS 端口——排查"Envoy 连不上控制面"时先看它。其余端口:8080 调试 HTTP、15014 监控指标、15017 注入 webhook。
📝 举个例子:Envoy 到底连哪个端口 生产环境 Envoy 的 bootstrap 里 discovery address 一般是 istiod.istio-system.svc:15012(走 mTLS,互相验工牌);你本地想 istioctl proxy-config 抓包调试时,可临时用 :15010 明文端口看原始 xDS。面试题:Envoy 报 "connection refused / no healthy upstream 控制面",第一反应就是查 15012 通不通。
L04

NewServer 装配

bootstrap/server.goNewServer:230-431)是 istiod 装配总入口,按顺序组装:

// :232 建 Environment + 聚合服务发现 aggregate.NewController
// :267 建 xDS 服务器 xds.NewDiscoveryServer(e, ...)
// :268 建配置生成器 core.NewConfigGenerator(...)
// :291 初始化 MeshConfig / MeshNetworks
// :315 CA options + maybeCreateCA
// :331 initControllers(args)  // 启动 config/service 控制器
// :335 InitGenerators(...)    // 注册所有 xDS 生成器(L05)
// :375 initRegistryEventHandlers()  // 事件接到 ConfigUpdate(Day 09)
// :415 startCA(caOpts)
"模块化单体"的装配 istiod 一个进程里塞了很多模块(xDS 服务器、配置生成器、服务发现、CA、webhook…)。NewServer 就是"总装配线"——按依赖顺序把这些模块一个个建好、接起来。先建数据源(Environment/服务发现)→ 建 xDS 服务器和生成器 → 建 CA → 启动控制器(开始 watch K8s)→ 注册生成器和事件处理器。读懂这个装配顺序,就有了 istiod 的"零件清单"——后面每天深入其中一个零件。
L05

生成器注册

// bootstrap/discovery.go:29 InitGenerators —— typeURL → generator 路由表
generators[v3.ClusterType]  = &xds.CdsGenerator{...}   // CDS
generators[v3.ListenerType] = &xds.LdsGenerator{...}   // LDS
generators[v3.RouteType]    = &xds.RdsGenerator{...}   // RDS
generators[v3.EndpointType] = edsGen                    // EDS(独立)
generators[v3.SecretType]   = xds.NewSecretGen(...)     // SDS
generators[v3.ScopedRouteType] = &xds.SrdsGenerator{...} // Added by ingress(Higress)
读法:一张"资源类型 → 生成器"的路由表。Envoy 请求某类型(如 CDS),istiod 就用对应的生成器(CdsGenerator)生成配置。SrdsGenerator(ScopedRoute)是 Higress 加的(大规模路由优化)。Day 07 详讲生成器。
L06

Start 监听

// server.go Start(:463):
// :471 启动所有组件(含 xDS server 的 handleUpdates/sendPushes 协程)
// :474 waitForCacheSync 等 K8s 缓存同步
// :478 XDSServer.CachesSynced() —— 标记 ready
// :482 在 :15012(secure) 与 :15010(plaintext) 上 grpcServer.Serve()
读法:Start = "启动后台协程 → 等缓存同步好 → 标记 ready → 在两个端口开始 serve xDS gRPC"。DiscoveryServer.Register 把 ADS 服务注册进 gRPC(Day 06)。后台协程 handleUpdates(debounce)和 sendPushes(推送)是第 2 周主角。
L07

ready 门禁

CachesSynced()discovery.go:208)在 K8s 缓存同步完成前,istiod 不接受 Envoy 连接ads.go:204 未 ready 返回 Unavailable)。

为什么要"缓存同步才服务"? istiod 启动时要从 K8s 拉取所有服务、CRD、端点信息(建 informer 缓存)。如果缓存还没同步完就给 Envoy 发配置,会发出"不完整"的配置——比如漏了一半服务,导致 Envoy 把流量发到黑洞。所以有个"ready 门禁":缓存全同步好了才标记 ready、才接受 Envoy 连接(issue #25495 就是这个问题)。这是分布式系统"启动时数据完整性"的经典处理——宁可晚点服务,也不发错配置。
💬 错误驱动:如果没有 ready 门禁会怎样 👶 小白:缓存没同步就服务,能有多严重?
👨‍🏫 老师:istiod 刚启动只从 K8s 拉到了一半服务,这时 Envoy 来连、拿到一份"缺了一半集群"的 CDS。它会把发往那些"暂时不存在"的服务的流量直接 503 no healthy upstream——用户侧就是间歇性报错。ready 门禁(issue #25495)就是为堵这个洞:盘点完货再开门。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • istiod 的二进制叫什么?RunE 三步?
  • 15010 vs 15012 端口的区别?
  • NewServer 装配了哪些核心模块?
  • InitGenerators 的路由表是什么?
  • 为什么要"缓存同步才接受连接"?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/istio
sed -n '23,30p' pilot/cmd/pilot-discovery/main.go
grep -n 'func NewServer\|InitGenerators\|func (s \*Server) Start' pilot/pkg/bootstrap/server.go
sed -n '29,72p' pilot/pkg/bootstrap/discovery.go
明天预告 · Day 03配置模型与 CRD——用户写的 VirtualService/DestinationRule 等 CRD,在 istiod 内部怎么表示?config.Config 统一模型、ConfigStore 接口、schema 类型系统。
← Day 01 全景 Day 03 · 配置模型与 CRD →