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

配置模型与 CRD

用户写 VirtualService/DestinationRule 这些 YAML(CRD)来配置 Istio。它们在 istiod 内部怎么表示?今天看统一配置单元 config.ConfigConfigStore 接口、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 定义 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 的统一接口一个思想。)
VirtualService DestinationRule Gateway / … config.Config(统一信封) Meta: GVK/Name/NS/Labels… Spec: 任意 proto message ConfigStore 按 GVK 存取
千种 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.VirtualServicekind.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 注册表、聚合注册表。
← Day 02 istiod 启动 Day 04 · 服务注册 →