Part 3: kube-apiserver Admission Control 与 CRD 超深度分析


一、模块定位

1.1 Admission Control 业务职责

Admission Control(准入控制)是 kube-apiserver 请求处理管线中的核心守门人,位于认证(Authentication)与授权(Authorization)之后、持久化存储之前。其业务职责可归纳为:

职责维度 描述
变更(Mutation) 在对象持久化前对其进行自动修改——注入默认值、补全标签、设置初始值
验证(Validation) 在对象持久化前对其进行策略校验——拒绝不符合集群策略的请求
审计(Audit) 在准入过程中记录关键决策信息,供后续审计追踪
副作用协调 如 ResourceQuota 的配额扣减、NamespaceLifecycle 的终止保护

Admission Control 的核心设计原则是双阶段执行:先 Mutate 再 Validate,且二者严格分离——Validation 阶段不得修改对象。这保证了验证逻辑的确定性,避免了变更与校验交织导致的不可预测行为。

1.2 CRD(CustomResourceDefinition)业务职责

CRD 是 Kubernetes 扩展 API 的核心机制,允许用户在不修改 apiserver 源码的前提下定义新的资源类型。其业务职责包括:

职责维度 描述
类型定义 声明自定义资源的 Group/Version/Kind、Scope(命名空间级/集群级)
Schema 校验 基于 OpenAPI v3 Schema 对 CR 实例进行结构验证
多版本管理 支持 CRD 的多版本定义及版本间转换(通过 Webhook 或 None 策略)
子资源支持 为 CR 提供 /status 和 /scale 子资源端点
OpenAPI 发布 将 CRD 的 Schema 动态注册到 OpenAPI / Discovery 端点
存储编排 为每个 CRD 动态创建 ETCD 存储层,处理 Unstructured 对象的序列化/反序列化

CRD 与 Admission Control 的交汇点在于:CRD 定义的自定义资源同样需要经过完整的 Admission Chain,包括内置插件和动态 Webhook 的变更与验证。


二、模块整体结构

2.1 Admission Plugin 接口层次

从源码 interfaces.go 可见,Admission 的接口设计呈三层结构:

Interface(基础接口)
  └── Handles(operation Operation) bool  —— 声明本插件处理的操作类型

MutationInterface(变更接口)
  └── Interface + Admit(ctx, Attributes, ObjectInterfaces) error

ValidationInterface(验证接口)
  └── Interface + Validate(ctx, Attributes, ObjectInterfaces) error

一个 Plugin 可以同时实现 MutationInterfaceValidationInterface,也可以只实现其中之一。例如:

  • NamespaceLifecycle 只实现 MutationInterface(其 Admit 方法既做变更也做验证,但在接口语义上归类为 Admit)
  • MutatingAdmissionWebhook 只实现 MutationInterface
  • ValidatingAdmissionWebhook 只实现 ValidationInterface
  • ResourceQuota 同时实现两个接口

Attributes 接口是 Admission 系统的"请求上下文",承载了全部准入决策所需信息:

type Attributes interface {
    GetName() string                          // 对象名称
    GetNamespace() string                     // 命名空间
    GetResource() schema.GroupVersionResource // 资源类型(GVR)
    GetSubresource() string                   // 子资源(status/scale等)
    GetOperation() Operation                  // 操作类型(CREATE/UPDATE/DELETE/CONNECT)
    GetOperationOptions() runtime.Object      // 操作选项
    IsDryRun() bool                           // 是否为 DryRun
    GetObject() runtime.Object                // 新对象
    GetOldObject() runtime.Object             // 旧对象(仅UPDATE)
    GetKind() schema.GroupVersionKind         // 类型(GVK)
    GetUserInfo() user.Info                   // 请求用户信息
    AddAnnotation(key, value string) error    // 审计标注
    GetReinvocationContext() ReinvocationContext // 重调用上下文
}

ObjectInterfaces 接口提供与对象交互的基础设施:

type ObjectInterfaces interface {
    GetObjectCreater() runtime.ObjectCreater
    GetObjectTyper() runtime.ObjectTyper
    GetObjectDefaulter() runtime.ObjectDefaulter
    GetObjectConvertor() runtime.ObjectConvertor
    GetEquivalentResourceMapper() runtime.EquivalentResourceMapper
}

2.2 AdmissionChain 执行顺序

从源码 chain.go 可见,chainAdmissionHandler 本质是一个 []Interface 切片,Admit 和 Validate 分别顺序遍历:

func (admissionHandler chainAdmissionHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
    for _, handler := range admissionHandler {
        if !handler.Handles(a.GetOperation()) {
            continue  // 跳过不处理该操作的插件
        }
        if mutator, ok := handler.(MutationInterface); ok {
            err := mutator.Admit(ctx, a, o)
            if err != nil {
                return err  // 首个错误即返回
            }
        }
    }
    return nil
}

kube-apiserver 默认启用的 Admission Plugin 及其推荐执行顺序如下(在 plugin/pkg/admission/order.go 中定义):

1.  NamespaceLifecycle          — 保护 immortal 命名空间、阻止在终止命名空间中创建对象
2.  LimitRanger                 — 为 Pod/Container 注入默认 limit/request
3.  ServiceAccount              — 自动注入 ServiceAccount 和 ImagePullSecrets
4.  NodeRestriction             — 限制 kubelet 只能修改自身 Node 和绑定到自身的 Pod
5.  DefaultTolerationSeconds    — 为 Pod 注入默认容忍度
6.  PodSecurity                 — Pod 安全标准 enforcement(替代原 PodSecurityPolicy)
7.  DefaultStorageClass         — 为 PVC 注入默认 StorageClass
8.  MutatingAdmissionWebhook    — 调用外部变更 Webhook(按 webhook 配置顺序串行)
9.  ValidatingAdmissionPolicy   — 基于CEL的策略验证(1.28+)
10. ValidatingAdmissionWebhook  — 调用外部验证 Webhook(所有 webhook 并行调用)
11. ResourceQuota               — 配额检查与扣减
12. DenyEscalatingExec          — 阻止特权容器的 exec/attach
13. OwnerReferencesPermissionEnforcement — 检查 OwnerReference 修改权限

关键顺序约束

  • Mutating 必须在 Validating 之前执行,否则验证的是未变更的对象
  • MutatingAdmissionWebhook 放在内置 Mutate 插件之后,使其能修改已被默认值填充的对象
  • ValidatingAdmissionWebhook 放在 ResourceQuota 之前,使得被拒绝的请求不会浪费配额评估
  • ResourceQuota 放在链末端,确保配额计算基于最终(已变更)的对象状态

2.3 CRD APIExtensions Server 类结构

从源码 apiserver.go 可见,CRD 子系统的核心类结构为:

type ExtraConfig struct {
    CRDRESTOptionsGetter   genericregistry.RESTOptionsGetter  // ETCD 存储选项
    MasterCount            int                                  // HA Master 数量
    ServiceResolver        webhook.ServiceResolver              // Webhook 服务名解析
    AuthResolverWrapper    webhook.AuthenticationInfoResolverWrapper // Webhook 认证
}

type Config struct {
    GenericConfig *genericapiserver.RecommendedConfig  // 通用服务器配置
    ExtraConfig   ExtraConfig                          // CRD 专有配置
}

type completedConfig struct {
    GenericConfig genericapiserver.CompletedConfig
    ExtraConfig   *ExtraConfig
}

type CustomResourceDefinitions struct {
    GenericAPIServer *genericapiserver.GenericAPIServer  // 内嵌通用 API Server
    Informers        externalinformers.SharedInformerFactory // CRD Informer 工厂
}

CRD APIExtensions Server 作为独立 API Server 运行,通过 delegation 机制委托给 kube-apiserver 的主 handler。其在 New() 方法中的初始化流程为:

  1. 创建 GenericAPIServer 实例
  2. 注册 CRD 自身的 API(customresourcedefinitions + customresourcedefinitions/status
  3. 创建 CustomResourceDefinitionHandler,拦截 /apis/ 路径下的所有 CR 请求
  4. 启动多个控制器:Establishing、Naming、NonStructuralSchema、APIApproval、Finalizer、OpenAPI、Discovery

2.4 CustomResourceDefinition 类型体系

CRD 的类型定义涉及多个层次:

apiextensions.GroupName(apiextensions.k8s.io)
  └── CustomResourceDefinition
        ├── Spec
        │     ├── Group string                      // API Group
        │     ├── Versions []CustomResourceDefinitionVersion
        │     │     ├── Name string                  // 版本名(v1, v1beta1等)
        │     │     ├── Served bool                  // 是否提供服务
        │     │     ├── Storage bool                 // 是否为存储版本(仅一个)
        │     │     ├── Schema *CustomResourceValidation  // OpenAPI v3 Schema
        │     │     ├── Subresources *CustomResourceSubresources
        │     │     │     ├── Status *bool           // /status 子资源
        │     │     │     └── Scale *CustomResourceSubresourceScale  // /scale 子资源
        │     │     └── AdditionalPrinterColumns []CustomResourceColumnDefinition
        │     ├── Scope ResourceScope                // Namespaced 或 Cluster
        │     ├── Conversion *CustomResourceConversion
        │     │     ├── Strategy None/Webhook        // 转换策略
        │     │     └── Webhook *WebhookConversion
        │     │           ├── ClientConfig *WebhookClientConfig
        │     │           └── ConversionReviewVersions []string
        │     └── PreserveUnknownFields *bool
        └── Status
              ├── Conditions []CustomResourceDefinitionCondition
              │     ├── Type Established/NamesAccepted/NonStructuralSchema/...
              │     ├── Status True/False/Unknown
              │     └── Reason/Message
              └── AcceptedNames CustomResourceDefinitionNames  // 可被 API Approval 修改的名称

三、核心业务逻辑深度解析

3.1 Admission Chain 执行流程(Validate/Mutate 双阶段)

kube-apiserver 的请求处理管线中,Admission 在 Create/Update/Delete/Connect 操作时被触发。完整流程如下:

No

Yes

Yes

No

No

Yes

No

Yes

Yes

No

No

Yes

HTTP Request 到达

Authentication 认证

Authorization 授权

Admission: Mutate 阶段

Admission: Validate 阶段

持久化到 ETCD

遍历 MutationInterface 链

handler.Handles
操作匹配?

跳过该插件

调用 handler.Admit

返回错误?

❌ 拒绝请求

继续下一个插件

链结束?

遍历 ValidationInterface 链

handler.Handles
操作匹配?

跳过该插件

调用 handler.Validate

返回错误?

❌ 拒绝请求

继续下一个插件

链结束?

Mutating Webhook 重调用机制:MutatingAdmissionWebhook 支持重调用策略(ReinvocationPolicy)。当一个 Webhook 的变更可能影响后续 Webhook 的匹配条件时,设置 IfNeeded 策略,系统会在所有 Webhook 执行完毕后重新调用之前被标记为可重调用的 Webhook。从源码 reinvocationcontext.go 可见:

  • webhookReinvokeContext.previouslyInvokedReinvocableWebhooks:记录已执行且支持重调用的 Webhook UID 集合
  • webhookReinvokeContext.reinvokeWebhooks:需要重调用的 Webhook UID 集合
  • 当某个 Webhook 修改了对象(changed=true),则触发 RequireReinvokingPreviouslyInvokedPlugins()
  • 重调用时只调用被标记的 Webhook,不会首次调用之前被跳过的 Webhook

3.2 内置 Admission Plugin 逐个解析

3.2.1 NamespaceLifecycle

源码路径pkg/admission/plugin/namespace/lifecycle/admission.go

核心职责:阻止在不合法的命名空间中进行操作。

从源码分析,Admit() 方法的执行逻辑如下:

Yes

No

Yes

No

Yes

Yes

No

No

Yes

No

Yes

No

No

Yes

Yes + Create

Yes

No

No + Create

Yes

No

No

Yes

No + 非Create

Admit 入口

操作=Delete
且目标=Namespace
且为 immortal?

❌ Forbidden:
immortal 命名空间不可删除

非命名空间级资源
且非 Namespace 类型?

✅ 放行

操作对象=Namespace 类型?

操作=Delete?

加入 forceLiveLookupCache
TTL=30s

✅ 放行所有 Namespace 操作

操作=Delete?

✅ 放行删除

操作=AccessReview?

✅ 放行

Informer 已同步?

❌ Forbidden:
not yet ready

从 Lister 查询 Namespace

Namespace 存在?

Phase=Terminating?

❌ Forbidden:
namespace is being terminated

✅ 放行

等待 50ms
重新查询缓存

缓存已更新?

继续到 Phase 检查

直接查询 ETCD

ETCD 中存在?

❌ NotFound

关键设计细节

  1. Immortal 命名空间defaultkube-systemkube-public 三个命名空间永远不可删除
  2. ForceLiveLookupCache:LRU 缓存(容量100),TTL 30s。当命名空间被 Delete 时加入此缓存,后续对该命名空间内对象的操作会跳过本地缓存,直接查询 ETCD,防止缓存的延迟导致在正在终止的命名空间中创建对象
  3. missingNamespaceWait:50ms 等待。创建对象时如果缓存中找不到命名空间,先等待 50ms 再查一次,为 Informer 的 Watch 事件传播留出时间
  4. AccessReview 放行localsubjectaccessreviews 始终放行,避免通过返回 “namespace not found” 泄露命名空间存在性信息
3.2.2 ResourceQuota

核心职责:在 Mutate 阶段计算配额使用量,在 Validate 阶段检查配额是否超限。

执行流程

  1. Admit 阶段:枚举请求对象消耗的所有资源类型(CPU/Memory/Storage/Pods/Services 等),通过 ListResourceQuotasByNamespace 获取所有匹配的 Quota 对象,计算配额使用量
  2. Validate 阶段:检查配额使用量是否超出硬性限制,如果超出则返回 Forbidden 错误
  3. DryRun 处理:DryRun 请求不实际扣减配额
3.2.3 LimitRanger

核心职责:为未设置 limit/request 的 Pod/Container 注入默认值。

执行逻辑

  1. 遍历命名空间中的所有 LimitRange 对象
  2. 对请求对象中的每个 Container,检查其资源声明
  3. 如果未设置 request,从 LimitRange 的默认值注入
  4. 如果未设置 limit,从 LimitRange 的默认值注入
  5. 如果 limit/request 比例不符合 LimitRange 的比例约束,则注入调整后的值
3.2.4 DefaultStorageClass

核心职责:为未指定 StorageClassName 的 PVC 注入默认 StorageClass。

执行逻辑

  1. 仅处理 CREATE 操作且对象类型为 PersistentVolumeClaim
  2. 如果 PVC 已指定 StorageClassName,放行
  3. 查找集群中被标记为默认的 StorageClass(注解 storageclass.kubernetes.io/is-default-class=true
  4. 如果找到恰好一个默认 StorageClass,将其名称注入到 PVC 的 StorageClassName 字段
  5. 如果有多个默认 StorageClass,不注入(避免歧义)
3.2.5 DefaultTolerationSeconds

核心职责:为未设置容忍度的 Pod 自动注入 node.kubernetes.io/not-readynode.kubernetes.io/unreachable 的默认容忍度(300s),确保 Pod 在节点故障时有合理的优雅驱逐时间。

3.2.6 MutatingAdmissionWebhook

详见 3.3 节。

3.2.7 ValidatingAdmissionWebhook

详见 3.4 节。

3.2.8 OwnerReferencesPermissionEnforcement

核心职责:阻止用户创建/修改 OwnerReference 到其没有权限操作的对象类型。

执行逻辑

  1. 仅处理 CREATE/UPDATE 操作
  2. 比较新旧对象的 OwnerReferences 列表
  3. 对于新增或修改的 OwnerReference,检查用户是否对其 apiVersion/kindnamespace 有 GET 权限
  4. 如果无权限,返回 Forbidden
3.2.9 PodNodeSelector

核心职责:通过 Namespace 的注解 scheduler.alpha.kubernetes.io/default-node-selector 为 Pod 注入默认的 Node Selector,限制 Pod 可调度的节点范围。

3.2.10 Priority(PrioritySort)

核心职责:在创建 Pod 时根据 PriorityClass 设置 Pod 的优先级值,并处理抢占逻辑。在 Admission 阶段主要是注入 Priority 值到 Pod 对象。

3.3 Webhook Admission — Mutating 远程调用机制

从源码分析,MutatingAdmissionWebhook 的架构层次为:

Plugin(实现 MutationInterface)
  └── generic.Webhook(基础设施:匹配、分发、客户端管理)
        └── mutatingDispatcher(串行调用 + JSON Patch 应用 + 重调用)

核心源码解析

// plugin.go - Plugin 只做两件事:注册 + Admit 委托
type Plugin struct {
    *generic.Webhook
}

func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
    return a.Webhook.Dispatch(ctx, attr, o)  // 完全委托给 generic.Webhook.Dispatch
}
// generic/webhook.go - Dispatch 做三件事
func (a *Webhook) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
    // 1. 跳过 webhook 配置资源自身的请求(避免递归)
    if rules.IsWebhookConfigurationResource(attr) { return nil }
    // 2. 等待 Informer 同步
    if !a.WaitForReady() { return Forbidden }
    // 3. 获取所有 webhook 配置,委托给 dispatcher
    hooks := a.hookSource.Webhooks()
    return a.dispatcher.Dispatch(ctx, attr, o, hooks)
}

ShouldCallHook 匹配逻辑(决定某个 Webhook 是否应该被调用):

No + 无err

Yes 或 有err

No + 无err

Yes 或 有err

Yes

No

No

Yes

Yes

No

Yes

No

Yes

No

ShouldCallHook

NamespaceSelector 匹配

匹配?

不调用

ObjectSelector 匹配

匹配?

遍历 Rules

任一 Rule 匹配
attr 的 GVR/Operation?

返回 invocation

MatchPolicy=Equivalent?

遍历等效资源

任一 Rule 匹配
等效 GVR?

返回 invocation
含等效 GVK

NamespaceSelector
有 err?

返回 StatusError

ObjectSelector
有 err?

返回 invocation + nil

mutatingDispatcher.Dispatch 串行调用流程

Webhook-3 Webhook-2 Webhook-1 mutatingDispatcher Webhook-3 Webhook-2 Webhook-1 mutatingDispatcher loop [对每个 hook 串行执行] 初始化 webhookReinvokeContext 检查是否重调用 + 对象是否变化 ShouldCallHook 匹配检查 创建/转换 VersionedAttributes HTTP POST /admit AdmissionReview Response VerifyAdmissionResponse 解码 JSON Patch Apply Patch → 新 VersionedObject 比较 old vs new → changed? changed? → 标记需重调用 ReinvocationPolicy=IfNeeded → 记录可重调用 转换 VersionedObject 到内部版本 返回

callAttrMutatingHook 的核心逻辑

  1. DryRun 检查:如果请求是 DryRun,Webhook 必须声明 SideEffects=NoneSideEffects=NoneOnDryRun,否则返回 DryRunUnsupportedErr
  2. 创建 AdmissionReview:通过 CreateAdmissionObjects() 构造请求对象,包含 UID、Kind、Resource、UserInfo、Object、OldObject、DryRun 等完整信息
  3. HTTP 调用:通过 RESTClient POST 到 Webhook 端点,支持自定义超时(TimeoutSeconds
  4. 验证响应VerifyAdmissionResponse(uid, true, response) 严格校验:
    • Response 不能为空
    • UID 必须匹配请求 UID
    • v1 版本还要校验 GVK
    • Mutating 模式:patch 和 patchType 必须同时提供或同时缺失
  5. 应用 Patch:仅支持 PatchTypeJSONPatch
    patchObj, _ := jsonpatch.DecodePatch(result.Patch)
    objJS, _ := runtime.Encode(jsonSerializer, attr.VersionedObject)
    patchedJS, _ := patchObj.Apply(objJS)
    newVersionedObject, _ = jsonSerializer.Decode(patchedJS, nil, newVersionedObject)
    
  6. 变更检测:使用 apiequality.Semantic.DeepEqual 比较变更前后对象,只有真正变化才标记 changed=true
  7. 审计标注:记录 Mutation 和 Patch 的审计信息
  8. Default 应用:对变更后的对象调用 Default() 方法

3.4 Webhook Admission — Validating 远程调用机制

从源码分析,ValidatingAdmissionWebhook 的关键区别在于并行调用所有匹配的 Webhook:

// validating/dispatcher.go - 核心并行调度逻辑
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, 
    o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
    
    // 1. 预筛选:收集所有匹配的 hook 及其 VersionedAttributes
    var relevantHooks []*generic.WebhookInvocation
    versionedAttrs := map[schema.GroupVersionKind]*generic.VersionedAttributes{}
    for _, hook := range hooks {
        invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o)
        if statusError != nil { return statusError }
        if invocation == nil { continue }
        relevantHooks = append(relevantHooks, invocation)
        // 缓存 VersionedAttributes,相同 GVK 只创建一次
        if _, ok := versionedAttrs[invocation.Kind]; !ok {
            versionedAttr, _ := generic.NewVersionedAttributes(attr, invocation.Kind, o)
            versionedAttrs[invocation.Kind] = versionedAttr
        }
    }
    if len(relevantHooks) == 0 { return nil }
    
    // 2. 检查上下文是否已超时
    select {
    case <-ctx.Done():
        return apierrors.NewTimeoutError(...)
    default:
    }

    // 3. 并行调用所有 webhook
    wg := sync.WaitGroup{}
    errCh := make(chan error, len(relevantHooks))
    wg.Add(len(relevantHooks))
    for i := range relevantHooks {
        go func(invocation *generic.WebhookInvocation) {
            defer wg.Done()
            // 调用单个 webhook
            err := d.callHook(ctx, hook, invocation, versionedAttr)
            // ... 错误处理与指标记录 ...
            if err != nil { errCh <- err }
        }(relevantHooks[i])
    }
    wg.Wait()
    close(errCh)

    // 4. 汇总错误
    var errs []error
    for e := range errCh { errs = append(errs, e) }
    if len(errs) == 0 { return nil }
    // 目前只返回第一个错误
    return errs[0]
}

Validating vs Mutating 的核心差异

维度 Mutating Validating
调用方式 串行(顺序调用,前一个的输出是后一个的输入) 并行(goroutine 并发调用)
对象修改 允许返回 JSON Patch 不允许返回 Patch/PatchType
响应校验 VerifyAdmissionResponse(uid, true, ...) VerifyAdmissionResponse(uid, false, ...)
重调用 支持(ReinvocationPolicy) 不支持
版本转换 每次调用可能需要转换到不同版本 预计算所有版本,缓存复用
审计标注 Mutation + Patch 双重标注 仅 AuditAnnotations
失败策略 FailurePolicy=Ignore 时继续下一个 同样支持,但并行中各自独立处理

Validating Webhook 的并行调用时序图

Webhook-C Webhook-B Webhook-A 预筛选 validatingDispatcher Webhook-C Webhook-B Webhook-A 预筛选 validatingDispatcher par [并行调用] 遍历所有 hooks → ShouldCallHook relevantHooks=[A,B,C] + versionedAttrs goroutine: POST /validate goroutine: POST /validate goroutine: POST /validate allowed=true allowed=false (reject) allowed=true WaitGroup.Wait() 收集错误 → 返回首个拒绝错误

3.5 AdmissionReview 请求/响应构造

从源码 request/admissionreview.go 分析,AdmissionReview 的构造支持 v1 和 v1beta1 两个版本:

CreateAdmissionObjects 根据Webhook声明的 admissionReviewVersions 选择版本:

func CreateAdmissionObjects(versionedAttributes *generic.VersionedAttributes, 
    invocation *generic.WebhookInvocation) (uid types.UID, request, response runtime.Object, err error) {
    for _, version := range invocation.Webhook.GetAdmissionReviewVersions() {
        switch version {
        case admissionv1.SchemeGroupVersion.Version:  // "v1"
            uid := types.UID(uuid.NewUUID())
            request := CreateV1AdmissionReview(uid, versionedAttributes, invocation)
            response := &admissionv1.AdmissionReview{}
            return uid, request, response, nil
        case admissionv1beta1.SchemeGroupVersion.Version:  // "v1beta1"
            // ...
        }
    }
    return "", nil, nil, fmt.Errorf("webhook does not accept known AdmissionReview versions")
}

v1 AdmissionReview Request 包含的字段

type AdmissionRequest struct {
    UID                types.UID                    // 唯一请求ID
    Kind               metav1.GroupVersionKind      // Webhook 匹配的 GVK
    Resource           metav1.GroupVersionResource  // Webhook 匹配的 GVR
    SubResource        string                       // 子资源
    RequestKind        *metav1.GroupVersionKind     // 原始请求的 GVK
    RequestResource    *metav1.GroupVersionResource // 原始请求的 GVR
    RequestSubResource string                       // 原始子资源
    Name               string                       // 对象名
    Namespace          string                       // 命名空间
    Operation          Operation                    // CREATE/UPDATE/DELETE/CONNECT
    UserInfo           UserInfo                     // 请求用户
    Object             runtime.RawExtension         // 新对象(版本化)
    OldObject          runtime.RawExtension         // 旧对象(仅UPDATE)
    DryRun             *bool                        // DryRun 标志
    Options            runtime.RawExtension         // 操作选项
}

VerifyAdmissionResponse 响应校验的关键差异:

校验项 v1 Mutating v1 Validating v1beta1
Response 非空
UID 匹配 ❌ 不校验
GVK 匹配 ❌ 不校验
Patch+PatchType 一致性 ✅ 同时提供/缺失 ❌ 都不允许 ✅ 有Patch时自动Pin为JSONPatch
PatchType 为空检查 N/A

3.6 CRD 创建 → Schema 验证 → ETCD 存储完整流程

3.6.1 CRD APIExtensions 架构

从源码 apiserver.goNew() 方法可见,CRD 系统的初始化流程为:

completedConfig.New

创建 GenericAPIServer

注册 CRD 自身 API

v1beta1: customresourcedefinitions + status

v1: customresourcedefinitions + status

创建 CRD InformerFactory

创建 CustomResourceDefinitionHandler

versionDiscoveryHandler

groupDiscoveryHandler

CRD Informer

delegateHandler

CRDRESTOptionsGetter

AdmissionControl

EstablishingController

ServiceResolver/AuthResolver

注册 /apis 和 /apis/ 路由到 crdHandler

创建多个 Controller

DiscoveryController

NamingConditionController

NonStructuralSchemaController

APIApprovalController

CRDFinalizer

OpenAPIController

注册 PostStartHook

start-apiextensions-informers

start-apiextensions-controllers

crd-informer-synced

3.6.2 CRD 创建流程

当用户通过 kubectl apply 创建一个 CRD 时:

kubectl apply -f crd.yaml

HTTP POST /apis/apiextensions.k8s.io/v1/customresourcedefinitions

GenericAPIServer 路由匹配

Admission Chain 执行

Mutate: 注入默认值

Validate: CRD Schema 自身验证

customresourcedefinition REST Storage.Create

CRD Strategy.Validate

验证 Group 名合法性

验证 Version 列表非空

验证仅一个 Storage 版本

验证 Schema 结构合法性

验证 Conversion 配置

ETCD 持久化 CRD 对象

Informer Watch 感知新 CRD

EstablishingController 检查 CRD 状态

CRD Condition → Established

DiscoveryController 更新 API 发现

versionDiscoveryHandler 注册 GV

groupDiscoveryHandler 注册 Group

OpenAPIController 发布 Schema

crdHandler 为新 CRD 动态创建 Storage

3.6.3 CR 实例的 Schema 验证

从源码 validation/validation.go 分析,CR 的 Schema 验证流程为:

Yes

No

Validation

RequiredFailCode

EnumFailCode

其他

其他

CR Create/Update 请求

从 CRD 获取 Schema

NewSchemaValidator

ConvertJSONSchemaPropsWithPostProcess
CRD Schema → OpenAPI spec.Schema

StripUnsupportedFormatsPostProcess
去除不支持的 format

validate.NewSchemaValidator
创建 Schema 验证器

ValidateCustomResource

validator.Validate customResource

结果有效?

✅ 通过验证

遍历 result.Errors

Error 类型?

Code?

field.Required

field.NotSupported

field.Invalid

field.Invalid

ConvertJSONSchemaPropsWithPostProcess 的核心转换逻辑

这个函数将 apiextensions.JSONSchemaProps(CRD 内部类型)递归转换为 spec.Schema(kube-openapi 类型),转换过程包括:

  1. 基础属性映射:type, format, title, description, nullable, default, example, enum
  2. 数值约束:maximum, minimum, exclusiveMaximum, exclusiveMinimum, multipleOf
  3. 字符串约束:maxLength, minLength, pattern
  4. 数组约束:maxItems, minItems, uniqueItems, items
  5. 对象约束:maxProperties, minProperties, required, properties, additionalProperties, patternProperties
  6. 组合约束:allOf, oneOf, anyOf, not
  7. Kubernetes 扩展
    • x-kubernetes-int-or-stringXIntOrString=true → type=[“integer”,“string”]
    • x-kubernetes-preserve-unknown-fields
    • x-kubernetes-embedded-resource
    • x-kubernetes-list-map-keys
    • x-kubernetes-list-type
    • x-kubernetes-map-type

StripUnsupportedFormatsPostProcess 作为后处理步骤,去除 kube-openapi 验证器不支持的 format 值(如 date-time 以外的某些自定义 format),避免验证失败。

ValidateCustomResource 的错误映射

OpenAPI 错误码 Kubernetes 字段错误
RequiredFailCode field.Required(errPath, "")
EnumFailCode field.NotSupported(errPath, err.Value, values)
其他 Validation 错误 field.Invalid(errPath, value, err.Error())
非 Validation 类型 field.Invalid(fldPath, "", err.Error())
3.6.4 CR 的 ETCD 存储

从源码 customresource/etcd.go 分析,CR 的存储架构为:

type CustomResourceStorage struct {
    CustomResource *REST           // 主资源存储
    Status         *StatusREST     // /status 子资源存储
    Scale          *ScaleREST      // /scale 子资源存储
}

REST 存储的创建

func newREST(resource schema.GroupResource, kind, listKind schema.GroupVersionKind, 
    strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, ...) (*REST, *StatusREST) {
    
    store := &genericregistry.Store{
        NewFunc: func() runtime.Object {
            ret := &unstructured.Unstructured{}
            ret.SetGroupVersionKind(kind)  // 设置 GVK 作为版本信号
            return ret
        },
        NewListFunc: func() runtime.Object {
            ret := &unstructured.UnstructuredList{}
            ret.SetGroupVersionKind(listKind)
            return ret
        },
        CreateStrategy:      strategy,
        UpdateStrategy:      strategy,
        DeleteStrategy:      strategy,
        ResetFieldsStrategy: strategy,
        TableConvertor:      tableConvertor,
    }
    // 通过 CompleteWithOptions 完成 ETCD 存储配置
    store.CompleteWithOptions(&generic.StoreOptions{RESTOptions: optsGetter})
    
    // Status 子资源共享底层 Store,但使用独立的 UpdateStrategy
    statusStore := *store
    statusStore.UpdateStrategy = NewStatusStrategy(strategy)
    statusStore.ResetFieldsStrategy = statusStrategy
    
    return &REST{store, categories}, &StatusREST{store: &statusStore}
}

关键设计点

  1. Unstructured 对象:CR 在存储层使用 unstructured.Unstructured,而非编译时的 Go struct。这使得 CRD 可以在运行时定义新类型
  2. GVK 信号NewFunc 创建的对象预设了 GVK,帮助版本化解码器正确识别对象类型
  3. Strategy 模式:Create/Update/Delete 各自有独立的 Strategy,处理验证、默认值、允许的更新字段等
  4. Status 子资源:使用共享的 Store 实例,但 Override 了 UpdateStrategy,限制只能修改 status 字段。forceAllowCreate=false 确保不能通过 Update 创建 Status
  5. Scale 子资源:通过 JSON Path 从 Unstructured 对象中提取 specReplicas/statusReplicas/labelSelector 字段,映射为 autoscaling/v1.Scale 类型

Scale 子资源的数据提取

func scaleFromCustomResource(cr *unstructured.Unstructured, specReplicasPath, statusReplicasPath, 
    labelSelectorPath string) (*autoscalingv1.Scale, bool, error) {
    
    specReplicas, foundSpecReplicas, _ := unstructured.NestedInt64(cr.UnstructuredContent(), 
        strings.Split(specReplicasPath, ".")...)
    statusReplicas, _, _ := unstructured.NestedInt64(cr.UnstructuredContent(), 
        strings.Split(statusReplicasPath, ".")...)
    // labelSelectorPath 可选
    
    scale := &autoscalingv1.Scale{
        ObjectMeta: metav1.ObjectMeta{...},  // 从 CR 的 metadata 复制
        Spec: ScaleSpec{Replicas: int32(specReplicas)},
        Status: ScaleStatus{Replicas: int32(statusReplicas), Selector: labelSelector},
    }
    return scale, foundSpecReplicas, nil
}

shallowCopyObjectMeta:CR 的 List/Get 操作返回对象时会浅拷贝 ObjectMeta,因为 generic store 会设置 self-link,避免修改缓存中的原始对象。

3.7 CRD 版本转换(Convert)机制

从源码 conversion/converter.goconversion/webhook_converter.go 分析,CRD 的版本转换体系如下:

3.7.1 转换器工厂
type CRConverterFactory struct {
    webhookConverterFactory *webhookConverterFactory
}

func (m *CRConverterFactory) NewConverter(crd *apiextensionsv1.CustomResourceDefinition) 
    (safe, unsafe runtime.ObjectConvertor, err error) {
    
    switch crd.Spec.Conversion.Strategy {
    case apiextensionsv1.NoneConverter:
        converter = &nopConverter{}  // 无转换
    case apiextensionsv1.WebhookConverter:
        converter, err = m.webhookConverterFactory.NewWebhookConverter(crd)
    }
    
    unsafe = &crConverter{
        convertScale:  convertScale,  // 是否需要转换 Scale 子资源
        validVersions: validVersions,  // CRD 声明的合法版本集合
        clusterScoped: crd.Spec.Scope == ClusterScoped,
        converter:     converter,      // 底层转换器
    }
    return &safeConverterWrapper{unsafe}, unsafe, nil
}
3.7.2 crConverter 的 Convert 逻辑

crConverter 封装了通用的 CR 转换行为:

  1. 如果输入对象已经是目标 GVK 版本,无需转换
  2. 检查目标 GVK 是否在 CRD 的合法版本列表中
  3. 如果是 Scale 类型,使用 Scale 转换路径
  4. 调用底层 converter(nopConverter 或 webhookConverter)
  5. 转换完成后设置目标的 apiVersion/kind
3.7.3 Webhook 转换器
Conversion Webhook webhookConverter API Server Conversion Webhook webhookConverter API Server alt [空对象] [列表对象] [单一对象] 校验UID/GVK/Status 检查返回数量一致 验证Kind/GroupVersion匹配 验证ObjectMeta一致 restoreObjectMeta alt [无对象需要转换] [有对象需转换] Convert(inObj, targetGV) nopConverter.Convert (跳过Webhook调用) getObjectsToConvert (过滤已为目标版本的对象) getObjectsToConvert (检查是否需要转换) createConversionReviewObjects (选择v1/v1beta1版本) 返回原对象/设置版本 POST /convert ConversionReview Request ConversionReview Response getConvertedObjectsFromResponse 转换后的对象

Webhook 转换的核心验证步骤(v1 版本):

  1. GVK 校验response.GroupVersionKind() != v1GVK → 错误
  2. UID 校验response.Response.UID != expectedUID → 错误
  3. 状态校验response.Response.Result.Status != "Success" → 错误
  4. 数量校验len(convertedObjects) != len(objectsToConvert) → 错误
  5. GVK 匹配:每个转换后的对象必须为目标 GroupVersion 和原始 Kind
  6. ObjectMeta 一致性validateConvertedObject 检查 name/namespace/uid 必须匹配
  7. 元数据恢复restoreObjectMeta 深拷贝原始 metadata,但保留 Webhook 返回的 labels/annotations

ConversionReview 数据结构

// Request
type ConversionRequest struct {
    UID               types.UID              // 请求唯一ID
    Objects           []runtime.RawExtension // 待转换对象列表
    DesiredAPIVersion string                 // 目标 apiVersion(如 "example.com/v1")
}

// Response
type ConversionResponse struct {
    UID              types.UID              // 必须匹配 Request.UID
    ConvertedObjects []runtime.RawExtension // 转换后的对象列表
    Result           metav1.Status          // 必须为 StatusSuccess
}

性能优化:Webhook 转换器的 trace 阈值动态计算:50ms + 8ms × objCount,基于 SLO(~4ms/对象 + 序列化开销 ~4ms/对象)。

3.8 CustomResource OpenAPI 发布

从源码 apiserver.go 中的 OpenAPI 控制器注册可见:

openapiController := openapicontroller.NewController(
    s.Informers.Apiextensions().V1().CustomResourceDefinitions())

// 在 PostStartHook 中运行
if s.GenericAPIServer.OpenAPIVersionedService != nil && 
   s.GenericAPIServer.StaticOpenAPISpec != nil {
    go openapiController.Run(
        s.GenericAPIServer.StaticOpenAPISpec,     // 静态 OpenAPI Spec 的更新接口
        s.GenericAPIServer.OpenAPIVersionedService, // /openapi/v2 端点服务
        context.StopCh)
}

OpenAPI 发布流程

CRD Informer 感知 CRD 变化

OpenAPIController.OnUpdate

从 CRD 提取 Schema

ConvertJSONSchemaProps
→ spec.Schema

StripUnsupportedFormats
后处理

合并到 StaticOpenAPISpec

更新 OpenAPIVersionedService
/openapi/v2 端点

kubectl 解释/补全
可使用新 CRD

NonStructuralSchema 控制器:当 CRD 的 Schema 不满足结构化要求时(如使用 x-kubernetes-preserve-unknown-fields 但未在顶层设置 type: object),该控制器会在 CRD Status 中设置 NonStructuralSchema Condition 为 False,提示用户修复 Schema。


四、Mermaid 图集合

图1:Admission Chain 整体架构图

Validate_Plugins

Mutate_Plugins

Admission_Chain

Request_Pipeline

HTTP Handler

Authn

Authz

Admission

ETCD Storage

Mutate Phase

Validate Phase

NamespaceLifecycle

LimitRanger

ServiceAccount

DefaultTolerationSeconds

DefaultStorageClass

MutatingAdmissionWebhook

ValidatingAdmissionPolicy

ValidatingAdmissionWebhook

ResourceQuota

OwnerReferencesPermEnforcement

图2:Admission 双阶段执行图

CREATE/UPDATE/DELETE/CONNECT

其他

Yes

No

Yes

No

请求进入 Admission

操作类型?

Phase 1: Mutate

跳过 Admission

Plugin-1.Admit

Plugin-2.Admit

...

Plugin-N.Admit

Mutate 错误?

❌ 返回错误

Phase 2: Validate

Plugin-1.Validate

Plugin-2.Validate

...

Plugin-M.Validate

Validate 错误?

❌ 返回错误

✅ 放行,进入存储

图3:NamespaceLifecycle 过滤流程图

Yes

No

Yes

No

Yes + Delete

Yes + 其他

No

Yes

No

Yes

No

No

Yes

Yes

Yes

No

No + Create

No

Yes

No

Yes

Admit 入口

Delete + Namespace + Immortal?

Forbidden: 不可删除

非NS级资源 + 非Namespace Kind?

Allow

Kind = Namespace?

forceLiveLookupCache.Add
TTL=30s

Allow

Delete 操作?

Allow

AccessReview?

Allow

Informer Ready?

Forbidden: not ready

Lister.Get namespace

NS 存在?

Create + Terminating?

Forbidden: NS terminating

Allow

Sleep 50ms → 重查

缓存更新?

Live Lookup ETCD

ETCD 存在?

NotFound

图4:MutatingAdmissionWebhook 时序图

Webhook Server mutatingDispatcher generic.Webhook.Dispatch Plugin.Admit Chain Handler Webhook Server mutatingDispatcher generic.Webhook.Dispatch Plugin.Admit Chain Handler loop [对每个匹配的 Webhook 串行] Admit(ctx, attr, o) Dispatch(ctx, attr, o) IsWebhookConfigResource? → No WaitForReady() hookSource.Webhooks() Dispatch(ctx, attr, o, hooks) ShouldCallHook 匹配 NewVersionedAttributes / ConvertVersionedAttributes POST /mutate (AdmissionReview) AdmissionReview Response VerifyAdmissionResponse(uid, true, ...) Decode JSON Patch Apply Patch to VersionedObject DeepEqual 检查变更 如果 changed → RequireReinvoke Convert VersionedObject → internal nil (允许) / error (拒绝)

图5:ValidatingAdmissionWebhook 时序图

Webhook-B Webhook-A validatingDispatcher generic.Webhook.Dispatch Plugin.Validate Chain Handler Webhook-B Webhook-A validatingDispatcher generic.Webhook.Dispatch Plugin.Validate Chain Handler par [并行调用] Validate(ctx, attr, o) Dispatch(ctx, attr, o) Dispatch(ctx, attr, o, hooks) 预筛选: 收集 relevantHooks 预创建 versionedAttrs (缓存) goroutine: POST /validate goroutine: POST /validate allowed=true allowed=false (ErrWebhookRejection) WaitGroup.Wait() 收集错误 → 返回首个 error (拒绝) / nil (允许)

图6:CRD APIExtensions 架构图

CRD_Dynamic_Resources

APIExtensions_Server

GenericAPIServer

CRD REST Storage
customresourcedefinitions

CRDHandler
处理 /apis/ 路由

versionDiscoveryHandler

groupDiscoveryHandler

CRD Informer

delegateHandler

Controllers

EstablishingController

DiscoveryController

NamingConditionController

NonStructuralSchemaController

APIApprovalController

CRDFinalizer

OpenAPIController

CustomResourceStorage

REST - 主资源

StatusREST - /status

ScaleREST - /scale

genericregistry.Store → ETCD

图7:CRD 创建流程图

用户提交 CRD YAML

kubectl apply

POST /apis/apiextensions.k8s.io/v1/customresourcedefinitions

Admission Chain

Mutate: 注入默认值

Validate: CRD Schema 校验

customresourcedefinition.REST.Create

Strategy.Validate

ETCD 写入 CRD 对象

Watch 事件触发

EstablishingController
→ Condition: Established

DiscoveryController
→ 注册 API Group/Version

OpenAPIController
→ 发布 Schema

NamingConditionController
→ 检查名称冲突

NonStructuralSchemaController
→ 检查 Schema 结构化

CRD 可用
kubectl get MyResource

图8:CRD Schema 验证图

Yes

No

CR 实例请求

从 CRD 提取 OpenAPIV3Schema

NewSchemaValidator

ConvertJSONSchemaPropsWithPostProcess

映射基础属性
type/format/title/description

映射约束
max*/min*/pattern/required

映射组合
allOf/oneOf/anyOf/not

映射 K8s 扩展
x-kubernetes-*

StripUnsupportedFormats
后处理

validate.NewSchemaValidator
创建验证器实例

ValidateCustomResource

validator.Validate customResource

IsValid?

✅ 通过

遍历 Errors

RequiredFail → field.Required

EnumFail → field.NotSupported

其他 → field.Invalid

图9:CRD ETCD 存储图

Create

Update

Get

List

Delete

Update Status

Get/Update Scale

CR 请求

操作类型

REST.Create

REST.Update

REST.Get

REST.List

REST.Delete

StatusREST.Update

ScaleREST.Get/Update

genericregistry.Store.Create

genericregistry.Store.Update

genericregistry.Store.Get

genericregistry.Store.List

genericregistry.Store.Delete

Strategy.Validate

Unstructured → JSON 编码

ETCD Put

ETCD Get

JSON → Unstructured 解码

shallowCopyObjectMeta
避免修改缓存

scaleFromCustomResource

提取 specReplicasPath

提取 statusReplicasPath

提取 labelSelectorPath

映射为 autoscaling/v1.Scale

图10:CRD 版本转换图

None

Webhook

Yes

No

Yes

No

Yes

No

No

Yes

API 请求需版本转换

crConverter.Convert

Conversion Strategy?

nopConverter.Convert
直接设置 apiVersion/kind

webhookConverter.Convert

空对象?

getObjectsToConvert

列表对象?

过滤已为目标版本的对象

已是目标版本?

返回原对象

加入转换列表

有对象需转换?

返回原对象/设版本

createConversionReviewObjects
选择 v1/v1beta1

POST /convert → Webhook

getConvertedObjectsFromResponse

校验 UID 匹配

校验 GVK 匹配

校验 Status=Success

校验 数量一致

校验 Kind/GV 匹配

validateConvertedObject
name/ns/uid 一致

restoreObjectMeta
保留 labels/annotations

返回转换后对象

图11:OpenAPI 发布流程图

CRD 创建/更新

Informer Watch 事件

OpenAPIController.OnUpdate

从 CRD 提取各版本 Schema

ConvertJSONSchemaProps
→ spec.Schema

应用 StripUnsupportedFormats

构建完整 OpenAPI Spec

更新 StaticOpenAPISpec

触发 OpenAPIVersionedService 刷新

/openapi/v2 端点更新

kubectl api-resources 可见

kubectl explain 可用

kubectl 补全生效

图12:Admission Plugin 注册图

apiserver 启动

admission.Plugins.Register
注册所有 Plugin 工厂函数

NamespaceLifecycle.Register

LimitRanger.Register

MutatingAdmissionWebhook.Register

ValidatingAdmissionWebhook.Register

ResourceQuota.Register

根据 --enable-admission-plugins
创建 Plugin 实例

PluginInitializer.Initialize
注入依赖

SetExternalKubeClientSet

SetExternalKubeInformerFactory

SetAuthorizer

SetDrainNotification

ValidateInitialization
校验依赖完整性

按顺序组成 chainAdmissionHandler

注册到 GenericAPIServer
AdmissionControl

图13:Webhook 配置图

Webhook_定义

ClientConfig

URL 或 Service

CABundle

Rules

APIGroups

APIVersions

Resources

Operations

FailurePolicy

Fail - 拒绝

Ignore - 放行

MatchPolicy

Exact

Equivalent

NamespaceSelector

matchLabels

matchExpressions

ObjectSelector

matchLabels

matchExpressions

SideEffects

None

NoneOnDryRun

TimeoutSeconds

1-30s

ReinvocationPolicy

Never

IfNeeded

admissionReviewVersions

v1

v1beta1

MutatingWebhookConfiguration

Webhook-1

Webhook-2

ValidatingWebhookConfiguration

Webhook-1

Webhook-2

图14:CRD Status 子资源图

CRD 对象

Spec

Status

Conditions

Type: Established
Status: True/False
Reason: InitialNamesAccepted

Type: NamesAccepted
Status: True/False
Reason: NoConflicts

Type: NonStructuralSchema
Status: True/False

Type: KubernetesAPIApprovalPolicyConformant
Status: True/False

Type: Terminating
Status: True

StatusREST.Update

共享 genericregistry.Store

statusStrategy.Validate
只允许修改 status 子集

forceAllowCreate=false
不允许 create-on-update

ETCD 更新

ScaleREST.Update

scaleUpdatedObjectInfo

从 Scale 提取 replicas

SetNestedField 到 CR

底层 Store.Update

图15:Admission 拒绝流程图

Yes - Forbidden

Yes - InternalError

Yes - Webhook ErrCallingWebhook

Fail

Ignore

Yes - ErrWebhookRejection

No

Yes

No

错误类型

errors.NewForbidden

errors.NewInvalid

errors.NewInternalError

errors.NewTimeoutError

ErrWebhookRejection

ErrCallingWebhook

请求进入 Admission

Mutate Phase

Plugin 返回错误?

构建 StatusError

包装为 StatusError

FailurePolicy?

InternalError

继续下一个 Plugin

提取 Status
返回拒绝原因

Validate Phase

Plugin 返回错误?

同上错误处理

✅ 请求放行

HTTP 403 Forbidden
携带 Status 详情

HTTP 500 InternalError

HTTP 403/422
Webhook 拒绝消息


五、总结

5.1 Admission Control 设计哲学

  1. 关注点分离:Mutation 和 Validation 严格分阶段执行,避免交叉依赖
  2. 插件化:所有准入逻辑通过插件接口实现,可独立启用/禁用
  3. 渐进增强:内置插件处理核心策略,Webhook 提供用户可扩展的动态准入能力
  4. 串行 Mutate vs 并行 Validate:Mutate 因为有数据依赖(前一个的输出是后一个的输入)必须串行;Validate 因为无副作用可以并行提升性能
  5. 重调用保障:Mutating Webhook 的 ReinvocationPolicy 解决了多个 Webhook 之间的交互问题
  6. DryRun 安全:DryRun 请求跳过有副作用的操作,Webhook 必须声明 SideEffects=None

5.2 CRD 设计哲学

  1. Unstructured 优先:CR 不编译为 Go struct,使用 Unstructured 保持运行时灵活性
  2. 声明式 Schema:通过 OpenAPI v3 JSONSchema 声明 CR 结构,支持自动验证和 OpenAPI 发布
  3. 存储版本唯一性:ETCD 只存储一个版本(storage: true),其他版本通过 Convert 转换
  4. Webhook 转换解耦:版本间转换逻辑委托给外部 Webhook,CRD 本身只声明策略
  5. 子资源复用:Status 和 Scale 子资源共享底层 Store 实例,通过 Strategy Override 限制可修改字段
  6. 控制器协同:Establishing、Discovery、Naming、NonStructuralSchema、APIApproval、Finalizer、OpenAPI 七个控制器协同确保 CRD 的完整生命周期管理

5.3 关键交互点

Admission Control 与 CRD 的关键交互点在于:

  1. CR 实例经过完整 Admission Chain:所有 CR 的 Create/Update/Delete 操作都会经过内置插件和动态 Webhook 的变更与验证
  2. CRD 自身也是 API 资源:CRD 的创建/更新也经过 Admission Chain,可以被 Webhook 拦截和修改
  3. Webhook 的 CRD 匹配:Webhook 的 Rules 可以匹配 CRD 定义的 GVR,等效资源匹配(MatchPolicy=Equivalent)支持跨版本匹配
  4. CRD Webhook 的服务发现:Admission Webhook 和 CRD Conversion Webhook 共享 ServiceResolver 和 AuthResolverWrapper 基础设施
  5. 版本转换与准入的时序:Admission 操作基于请求版本的 Schema 进行验证,版本转换发生在存储层和读取层

本文基于 Kubernetes 源码(staging/src/k8s.io/apiserver、staging/src/k8s.io/apiextensions-apiserver)深度分析,覆盖了 Admission Control 的接口体系、双阶段执行、内置插件、Webhook 远程调用机制,以及 CRD 的创建流程、Schema 验证、ETCD 存储、版本转换和 OpenAPI 发布等核心业务逻辑。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐