Day 03 / 共 20 天 · 第 1 周 控制面全景
配置模型与 CRD
用户写 VirtualService/DestinationRule 这些 YAML(CRD)来配置 Istio。它们在 istiod 内部怎么表示?今天看统一配置单元 config.Config、ConfigStore 接口、schema 类型系统。
📍 你在控制面 istiod 之旅的位置(大脑要工作,先得看懂用户递进来的"需求单"——CRD)
D1 全景·
D2 启动·
D3 配置CRD·
D4 服务注册·
D5 快照·
D6 xDS服务·
D7 生成·
D8 Builder·
D9 推送·
D10 连接
L01
CRD 是什么
🤔 痛点:你想改流量,总不能改 istiod 源码吧
你想让 10% 流量灰度到新版本。难道要登进 istiod 敲代码、重新编译?那运维根本没法用。需要一种"外部下单"的方式:你写一张单子,Istio 照着做。
💡 本质:CRD 是你递给总调度室的"需求工单"
CRD 就像去物业前台填一张标准表单:表单有固定格式(kind/字段),你只管填"我要 90% 到 v1、10% 到 v2",交上去(
kubectl apply)。总调度室(istiod)盯着这摞工单,一有新单就翻译成保安(Envoy)能执行的指令。CRD 是用户和 Istio 之间唯一的"下单语言"。CRD = "给 K8s 加的自定义资源类型"
Kubernetes 内置了 Pod/Service/Deployment 等资源。CRD(Custom Resource Definition,自定义资源定义)让你能给 K8s 加新的资源类型。Istio 就定义了一堆 CRD——VirtualService、DestinationRule、Gateway 等。于是你能
kubectl apply 一个 VirtualService YAML,就像操作原生资源一样。用户通过写这些 CRD 来"告诉 Istio 想要什么"(把 10% 流量导到 v2)。istiod watch 这些 CRD,翻译成 Envoy 配置下发。CRD 是用户和 Istio 之间的"接口语言"。L02
五大网络 CRD
| CRD | 作用 |
|---|---|
| VirtualService | 请求路由:匹配、重写、重试、超时、故障注入、镜像、金丝雀 |
| DestinationRule | 路由后对目标的策略:负载均衡、连接池、熔断、subset(版本) |
| Gateway | 配置网格边缘的独立 Envoy(暴露端口/协议/host/TLS) |
| ServiceEntry | 把外部/手工服务加入注册表 |
| Sidecar | 微调 sidecar 的入/出站范围(收敛配置,Day 05) |
VirtualService vs DestinationRule(最常混)
VirtualService 管"去哪"——请求怎么匹配、路由到哪个服务/版本、重试超时。DestinationRule 管"到了怎么办"——选中目标后的负载均衡策略、连接池、熔断,以及定义 subset(比如把 reviews 服务按 label 分成 v1/v2 两个子集)。典型配合:VirtualService 说"90% 流量到 reviews 的 v1、10% 到 v2",DestinationRule 定义 v1/v2 这两个 subset 是什么。它们的结构体在
istio.io/api(VirtualService :265,DestinationRule :415)。📝 举个例子:一张灰度工单长这样
# VirtualService:管"去哪"——90% 到 v1,10% 到 v2
spec:
hosts: [reviews]
http:
- route:
- destination: { host: reviews, subset: v1 }
weight: 90
- destination: { host: reviews, subset: v2 }
weight: 10
---
# DestinationRule:管"v1/v2 到底指谁"(按 label 分子集)
spec:
host: reviews
subsets:
- { name: v1, labels: { version: v1 } }
- { name: v2, labels: { version: v2 } }
两张单子配合:VS 说比例,DR 定义 v1/v2 是谁。缺了 DR,VS 里的 subset: v2 就是空指令。💬 常见误解
👶 小白:我只写了 VirtualService 分流,为什么 v2 一直没流量?
👨🏫 老师:因为你没写 DestinationRule 定义
👨🏫 老师:因为你没写 DestinationRule 定义
subset: v2。VS 里的 subset 只是个"代号",代号对应哪些 Pod 是 DR 说了算。少了 DR,istiod 找不到 v2 是谁,那条路由等于废单。口诀:VS 定去向、DR 定人选,成对出现。L03
Spec 只是数据
// istio.io/client-go .../networking/v1/types.gen.go:255
type VirtualService struct {
metav1.TypeMeta // apiVersion + kind
metav1.ObjectMeta // metadata(name/namespace/labels…)
Spec v1alpha3.VirtualService // ★ proto 结构体只是 .spec
Status v1alpha1.IstioStatus
}
读法:关键心智模型:
istio.io/api 里的 proto 结构体(如 v1alpha3.VirtualService)只是 CRD 的 .spec 部分。带 metadata/kind/status 的完整对象在 client-go 里。CRD 数据结构由 proto 生成(.pb.go + deepcopy + json shim 四文件模式)。L04
config.Config 统一单元
// pkg/config/model.go:103
type Config struct {
Meta // GVK/Name/Namespace/Labels/ResourceVersion…(:49)
Spec Spec // 具体的 proto message(:107)
Status Status
}
config.Config = "所有配置的统一信封"
istiod 内部不想为每种 CRD 写一套处理逻辑。所以它把任意一条配置(不管是 VirtualService 还是 Gateway)都归一成一个
config.Config——统一的信封:Meta 装元信息(谁、在哪、什么类型),Spec 装具体内容(一个 proto)。于是 ConfigStore、事件处理、PushContext 都只跟 config.Config 打交道,用 GVK(类型标识)区分具体是什么。这是"统一抽象"——把千变万化的 CRD 收敛成一个类型,大大简化了内部代码。(和 OpenClaw 的 MsgContext、Envoy 的统一接口一个思想。)千种 CRD 装进同一个信封 config.Config,用 GVK 区分类型;下游 ConfigStore/PushContext 只认这一个信封。
L05
ConfigStore 接口
// pilot/pkg/model/config.go:137
type ConfigStore interface {
Get(typ, name, ns) *config.Config // :144
List(typ, ns) []config.Config // :148
Create/Update/Delete(...) // :153+
}
// :188 ConfigStoreController = ConfigStore + RegisterEventHandler + Run
// (带 informer 缓存 + 事件回调的 ConfigStore)
读法:
ConfigStore 是"配置的 CRUD 抽象"——平台无关(不绑定 K8s)。ConfigStoreController 加了"watch 变更 + 回调"(Day 09 的事件源)。Istio 从 K8s CRD 读配置的实现(Day 04 会提到 krt/crdclient)就实现这个接口。旧版的 IstioConfigStore(带便捷方法)已被 PushContext 索引取代。L06
schema 类型系统
pkg/config/schema/ 是"类型总登记处"——每种 CRD 类型的元信息(GVK、复数名、Go 类型、校验器)都在这里注册(由 metadata.yaml 代码生成)。
// collections/collections.gen.go 以 VirtualService 为例:
// ReflectType: reflect.TypeOf(&v1alpha3.VirtualService{}).Elem() // 绑到 api 结构体
// ValidateProto: validation.ValidateVirtualService // 挂校验器
// 聚合 bundle:All、Kube、Pilot(istiod 监听的集合)
读法:schema 系统让 istiod 能"根据 GVK 动态处理任意类型"——查到 schema 就知道"这个类型的 Go 结构、怎么校验、复数资源名是什么"。
collections.Pilot 是"istiod 关心的所有类型"的集合。这是"元编程"——用数据描述类型,避免为每种类型硬编码。L07
GVK 三坐标
schema 系统在三套坐标间映射:Kind(快枚举,热路径用)、GVK(GroupVersionKind,如 networking.istio.io/v1alpha3/VirtualService)、GVR(GroupVersionResource,复数资源名,K8s client 用)。
为什么要三套坐标?
同一个类型有三种"叫法",各有用途:Kind(一个 uint8 枚举)——热路径上比较快(比字符串快);GVK——人类可读的完整标识,事件处理用;GVR——K8s API 认的复数资源名(virtualservices),调 K8s client 用。schema 系统提供
ToGVR/ToKind/FromGVR 等转换函数在三者间切换。理解这三套坐标,读 istiod 代码时看到 gvk.VirtualService、kind.VirtualService 就不会晕——它们是同一类型的不同表示。L08
今日小结 + 动手
🧠 今天你应该能回答
- CRD 是什么?它是用户和 Istio 的什么?
- VirtualService 和 DestinationRule 各管什么?
- proto 结构体和完整 CRD 对象什么关系?
- config.Config 为什么是"统一信封"?
- schema 系统解决什么?GVK 三坐标各是什么用途?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/istio
grep -n 'type Config struct\|type Meta struct' pkg/config/model.go
grep -n 'type ConfigStore interface\|ConfigStoreController' pilot/pkg/model/config.go
ls pkg/config/schema/
明天预告 · Day 04:服务注册——istiod 怎么知道集群里有哪些服务?
ServiceDiscovery 抽象、Service/Endpoint 模型、Kubernetes 注册表、聚合注册表。