声明式 API 验证

特性状态: Kubernetes v1.33 [beta]

Kubernetes 1.34 包含了对 API 的可选*声明式验证*。启用后,Kubernetes API 服务器可以使用此机制,而不是依赖手动编写的 Go 代码(validation.go 文件)来确保对 API 的请求有效。Kubernetes 开发者以及扩展 Kubernetes API 的人员可以直接在 API 类型定义(types.go 文件)旁边定义验证规则。代码作者定义特殊的注释标签(例如,+k8s:minimum=0)。然后,代码生成器(validation-gen)使用这些标签生成用于 API 验证的优化 Go 代码。

虽然这主要是一个影响 Kubernetes 贡献者和扩展 API 服务器开发者的特性,但集群管理员应了解其行为,尤其是在其推出阶段。

声明式验证正在逐步推出。在 Kubernetes 1.34 中,使用声明式验证的 API 包括:

  • DeclarativeValidation:(Beta,默认值:true)启用后,API 服务器会针对已迁移的类型/字段同时运行新的声明式验证和旧的手写验证。内部会比较结果。
  • DeclarativeValidationTakeover:(Beta,默认值:false)此门控决定哪个验证结果是*权威的*(即返回给用户并用于准入决策)。

默认行为 (Kubernetes 1.34)

  • DeclarativeValidation=trueDeclarativeValidationTakeover=false(门控的默认值)时,两个验证系统都会运行。
  • 使用*手写*验证的结果。声明式验证以不匹配模式运行以进行比较。
  • API 服务器会记录两个验证系统之间的不匹配情况,并增加 declarative_validation_mismatch_total 指标。这有助于开发者在 Beta 阶段识别并修复差异。
  • 此特性的集群升级应该是安全的,因为权威验证逻辑默认不会改变。

管理员可以选择明确启用 DeclarativeValidationTakeover=true,以使*声明式*验证对已迁移的字段具有权威性,通常在验证其环境中的稳定性后(例如,通过监控不匹配指标)。

禁用声明式验证

作为集群管理员,在某些特定情况下,你可能会考虑在声明式验证仍处于 Beta 阶段时禁用它:

  • 意外的验证行为: 如果启用 DeclarativeValidationTakeover 导致意外的验证错误或允许以前无效的对象。
  • 性能下降: 如果监控显示延迟显著增加(例如,在 apiserver_request_duration_seconds 中)且与该特性的启用相关。
  • 高不匹配率: 如果 declarative_validation_mismatch_total 指标显示频繁不匹配,表明声明式规则可能存在影响集群工作负载的错误,即使 DeclarativeValidationTakeover 为 false 也是如此。

要恢复到仅使用手写验证(在 Kubernetes v1.33 之前使用的方式),请禁用 DeclarativeValidation 功能门,例如通过命令行参数:(--feature-gates=DeclarativeValidation=false)。这也隐式禁用了 DeclarativeValidationTakeover 的影响。

降级和回滚的注意事项

禁用此功能可作为一种安全机制。但是,请注意一个潜在的极端情况(由于广泛测试,这被认为不太可能发生):如果声明式验证中的一个错误(当 DeclarativeValidationTakeover=true 时)*错误地允许*了一个无效对象被持久化,那么禁用功能门可能会导致后续对该特定对象的更新被现在具有权威性(且正确)的手写验证阻止。解决此问题可能需要手动更正存储的对象,在极少数情况下可能需要通过直接修改 etcd 来实现。

有关管理功能门的详细信息,请参阅功能门

声明式验证标签参考

本文档提供了所有可用声明式验证标签的全面参考。

标签目录

标签描述
+k8s:eachKey声明对映射中每个键的验证。
+k8s:eachVal声明对映射或列表中每个值的验证。
+k8s:enum指示字符串类型为枚举。
+k8s:forbidden指示字段不得指定。
+k8s:format指示字符串字段具有特定格式。
+k8s:ifDisabled声明仅在选项被禁用时才应用的验证。
+k8s:ifEnabled声明仅在选项被启用时才应用的验证。
+k8s:isSubresource指定包中的验证仅适用于特定的子资源。
+k8s:item声明对被声明为 +k8s:listType=map 的切片的项的验证。
+k8s:listMapKey声明列表值类型的一个命名子字段为列表映射键的一部分。
+k8s:listType声明列表字段的语义类型。
+k8s:maxItems指示列表字段的大小有限制。
+k8s:maxLength指示字符串字段的长度有限制。
+k8s:minimum指示数字字段具有最小值。
+k8s:neq验证字段值不等于特定的不允许值。
+k8s:opaqueType指示对引用类型声明的任何验证都将被忽略。
+k8s:optional指示字段对客户端是可选的。
+k8s:required指示客户端必须指定字段。
+k8s:subfield声明对结构体子字段的验证。
+k8s:supportsSubresource声明包中类型支持的子资源。
+k8s:unionDiscriminator指示此字段是联合体的判别器。
+k8s:unionMember指示此字段是联合组的成员。
+k8s:zeroOrOneOfMember指示此字段是零或一组成员。

标签参考

+k8s:eachKey

描述

声明对映射中每个键的验证。

负载

  • <validation-tag>:要评估每个键的标签。

使用示例

type MyStruct struct {
    // +k8s:eachKey=+k8s:minimum=1
    MyMap map[int]string `json:"myMap"`
}

在此示例中,eachKey 用于指定 +k8s:minimum 标签应应用于 MyMap 中的每个 int 键。这意味着映射中的所有键必须 >= 1。

+k8s:eachVal

描述

声明对映射或列表中每个值的验证。

负载

  • <validation-tag>:要评估每个值的标签。

使用示例

type MyStruct struct {
    // +k8s:eachVal=+k8s:minimum=1
    MyMap map[string]int `json:"myMap"`
}

在此示例中,eachVal 用于指定 +k8s:minimum 标签应应用于 MyList 中的每个元素。这意味着 MyStruct 中的所有字段必须 >= 1。

+k8s:enum

描述

指示字符串类型为枚举。此类型的所有常量值都被视为枚举中的值。

使用示例

首先,定义一个新的字符串类型和该类型的几个常量

// +k8s:enum
type MyEnum string

const (
    MyEnumA MyEnum = "A"
    MyEnumB MyEnum = "B"
)

然后,在另一个结构体中使用此类型

type MyStruct struct {
    MyField MyEnum `json:"myField"`
}

验证逻辑将确保 MyField 是定义的枚举值之一("A""B")。

+k8s:forbidden

描述

指示字段不得指定。

使用示例

type MyStruct struct {
    // +k8s:forbidden
    MyField string `json:"myField"`
}

在此示例中,创建或更新 MyStruct 时不能提供 MyField(它是被禁止的)。

+k8s:format

描述

指示字符串字段具有特定格式。

负载

  • k8s-ip:此字段包含 IPv4 或 IPv6 地址值。IPv4 八位字节可以有前导零。
  • k8s-long-name:此字段包含 Kubernetes “长名称”,也称为 “DNS 子域” 值。
  • k8s-short-name:此字段包含 Kubernetes “短名称”,也称为 “DNS 标签” 值。

使用示例

type MyStruct struct {
    // +k8s:format=k8s-ip
    IPAddress string `json:"ipAddress"`

    // +k8s:format=k8s-long-name
    Subdomain string `json:"subdomain"`

    // +k8s:format=k8s-short-name
    Label string `json:"label"`
}

在此示例中:

  • IPAddress 必须是有效的 IP 地址。
  • Subdomain 必须是有效的 DNS 子域。
  • Label 必须是有效的 DNS 标签。

+k8s:ifDisabled

描述

声明仅在选项被禁用时才应用的验证。

参数

  • <option> (字符串,必填):选项的名称。

负载

  • <validation-tag>:此验证标签仅在验证选项被禁用时进行评估。

使用示例

type MyStruct struct {
    // +k8s:ifDisabled("my-feature")=+k8s:required
    MyField string `json:"myField"`
}

在此示例中,仅当“my-feature”选项被禁用时才需要 MyField

+k8s:ifEnabled

描述

声明仅在选项被启用时才应用的验证。

参数

  • <option> (字符串,必填):选项的名称。

负载

  • <validation-tag>:此验证标签仅在验证选项被启用时进行评估。

使用示例

type MyStruct struct {
    // +k8s:ifEnabled("my-feature")=+k8s:required
    MyField string `json:"myField"`
}

在此示例中,仅当“my-feature”选项被启用时才需要 MyField

+k8s:isSubresource

描述

+k8s:isSubresource 标签是一个包级注释,它**将该包内的验证规则限定到特定的子资源**。它本质上告诉代码生成器:“这里定义的验证逻辑是此子资源的特定实现,不应应用于根对象或任何其他子资源。”

关键依赖

此标签**依赖于**在定义主 API 类型的包中存在相应的 +k8s:supportsSubresource 标签。

  • +k8s:supportsSubresource 通过告诉调度器子资源有效来打开大门。
  • +k8s:isSubresource 提供专门的验证逻辑,当请求通过该门时运行。

如果您在主类型上没有相应的 +k8s:supportsSubresource 声明而使用 +k8s:isSubresource,则会生成专门的验证代码,但它将**无法访问**。主调度器将无法识别子资源路径,并会在请求路由到您的特定验证逻辑之前拒绝该请求。

这种依赖关系允许强大的组织,例如将您的主 API 类型放在一个包中,并在单独的专用包中定义它们的子资源特定验证。

范围:

负载

  • <subresource-path>:此包中的验证应应用的子资源路径(例如,"/status""/scale")。

使用示例

这个两部分示例演示了分离关注点的预期用例。

1. 在主 API 包中声明支持: 首先,声明 Deployment 类型在其主包中支持 /scale 验证。

文件:staging/src/k8s.io/api/apps/v1/doc.go

// This enables the validation dispatcher to handle requests for "/scale".
// +k8s:supportsSubresource="/scale"
package v1

// ... includes the definition for the Deployment type

2. 在单独的包中范围化验证逻辑: 接下来,为仅限于 /scale 子资源的验证规则创建一个单独的包。

文件:staging/src/k8s.io/api/apps/v1/validations/scale/doc.go

// This ensures the rules in this package ONLY run for the "/scale" subresource.
// +k8s:isSubresource="/scale"
package scale

import "k8s.io/api/apps/v1"

// Validation code in this package would reference types from package v1 (e.g., v1.Scale).
// The generated validation function will only be invoked for requests to the "/scale"
// subresource of a type defined in a package that supports it.

+k8s:item

描述

声明对被声明为 +k8s:listType=map 的切片项的验证。要匹配的项通过提供字段值对参数来声明,其中字段是 listMapKey。必须指定所有 listMapKey 键字段。

用途

+k8s:item(<listMapKey-JSON-field-name>: <value>,...)=<validation-tag>

+k8s:item(stringKey: "value", intKey: 42, boolKey: true)=<validation-tag>

参数必须使用列表-映射键字段的 JSON 名称命名。值可以是字符串、整数或布尔值。

负载

  • <validation-tag>:用于匹配列表项的标签。

使用示例

type MyStruct struct {
	// +k8s:listType=map
	// +k8s:listMapKey=type
	// +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember
	// +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember
	MyConditions []MyCondition `json:"conditions"`
}

type MyCondition struct {
    Type string `json:"type"`
    Status string `json:"status"`
}

在此示例中:

  • 类型为“Approved”的条件是零或一组成员。
  • 类型为“Denied”的条件是零或一组成员。

+k8s:listMapKey

描述

声明列表值类型的一个命名子字段为列表映射键的一部分。当使用 +k8s:listType=map 时,此标签是必需的。可以在列表映射上使用多个 +k8s:listMapKey 标签来指定它由多个字段作为键。

负载

  • <field-json-name>:用作键的字段的 JSON 名称。

使用示例

// +k8s:listType=map
// +k8s:listMapKey=keyFieldOne
// +k8s:listMapKey=keyFieldTwo
type MyList []MyStruct

type MyStruct struct {
    keyFieldOne string `json:"keyFieldOne"`
    keyFieldTwo string `json:"keyFieldTwo"`
    valueField string `json:"valueField"`
}

在此示例中,listMapKey 用于指定 MyStructkeyField 应用作列表映射的键。

+k8s:listType

描述

声明列表字段的语义类型。此标签用于指定如何处理列表,例如作为映射或集合。

负载

  • atomic:列表被视为单个原子值。
  • map:列表被视为映射,其中每个元素都有一个唯一的键。需要使用 +k8s:listMapKey
  • set:列表被视为集合,其中每个元素都是唯一的。

使用示例

// +k8s:listType=map
// +k8s:listMapKey=keyField
type MyList []MyStruct

type MyStruct struct {
    keyField string `json:"keyField"`
    valueField string `json:"valueField"`
}

在此示例中,MyList 被声明为类型为 map 的列表,其中 keyField 为键。这意味着验证逻辑将确保列表中的每个元素都有一个唯一的 keyField

+k8s:maxItems

描述

指示列表字段的大小有限制。

负载

  • <非负整数>:此字段最多包含 X 项。

使用示例

type MyStruct struct {
    // +k8s:maxItems=5
    MyList []string `json:"myList"`
}

在此示例中,MyList 不能包含超过 5 个项。

+k8s:maxLength

描述

指示字符串字段的长度有限制。

负载

  • <非负整数>:此字段的长度不能超过 X 个字符。

使用示例

type MyStruct struct {
    // +k8s:maxLength=10
    MyString string `json:"myString"`
}

在此示例中,MyString 的长度不能超过 10 个字符。

+k8s:minimum

描述

指示数字字段具有最小值。

负载

  • <整数>:此字段必须大于或等于 x。

使用示例

type MyStruct struct {
    // +k8s:minimum=0
    MyInt int `json:"myInt"`
}

在此示例中,MyInt 必须大于或等于 0。

+k8s:neq

描述

验证字段的值不等于特定的不允许值。支持字符串、整数和布尔类型。

负载

  • <value>:不允许的值。解析器将推断类型(字符串、int、布尔)。

使用示例

type MyStruct struct {
    // +k8s:neq="disallowed"
    MyString string `json:"myString"`

    // +k8s:neq=0
    MyInt int `json:"myInt"`

    // +k8s:neq=true
    MyBool bool `json:"myBool"`
}

在此示例中:

  • MyString 不能等于 "disallowed"
  • MyInt 不能等于 0
  • MyBool 不能等于 true

+k8s:opaqueType

描述

指示将忽略在引用类型上声明的任何验证。如果引用类型的包未包含在生成器的当前标志中,则必须设置此标签,否则代码生成将失败(防止无声错误)。如果验证不应被忽略,请使用 --readonly-pkg 标志将类型包添加到生成器中。

使用示例

import "some/external/package"

type MyStruct struct {
    // +k8s:opaqueType
    ExternalField package.ExternalType `json:"externalField"`
}

在此示例中,将忽略 package.ExternalType 上的任何验证标签。

+k8s:optional

描述

指示字段对客户端是可选的。

使用示例

type MyStruct struct {
    // +k8s:optional
    MyField string `json:"myField"`
}

在此示例中,创建或更新 MyStruct 时不需要提供 MyField

+k8s:required

描述

指示客户端必须指定字段。

使用示例

type MyStruct struct {
    // +k8s:required
    MyField string `json:"myField"`
}

在此示例中,创建或更新 MyStruct 时必须提供 MyField

+k8s:subfield

描述

声明对结构体子字段的验证。

参数

  • <field-json-name> (字符串,必填):子字段的 JSON 名称。

负载

  • <validation-tag>:用于评估子字段的标签。

使用示例

type MyStruct struct {
    // +k8s:subfield("mySubfield")=+k8s:required
    MyStruct MyStruct `json:"MyStruct"`
}

type MyStruct struct {
    MySubfield string `json:"mySubfield"`
}

在此示例中,MyStruct 中的 MySubfield 是必需的。

+k8s:supportsSubresource

描述

+k8s:supportsSubresource 标签是一个包级注释标签,它**声明了哪些子资源是包中类型的有效验证目标**。将此标签视为注册一个端点;它告诉验证框架特定的子资源路径被识别,并且不应立即被拒绝。

生成验证代码时,此标签会将指定的子资源路径添加到类型的主调度函数中。这允许将传入的该子资源的请求路由到验证实现。

可以使用多个标签来声明对多个子资源的支持。如果包中不存在 +k8s:supportsSubresource 标签,则仅对根资源(例如 .../myresources/myobject)启用验证,任何对子资源的请求都将失败并出现“未找到验证”错误。

独立使用

如果使用 +k8s:supportsSubresource 而没有相应的 +k8s:isSubresource 标签进行特定验证,则根对象的验证规则将默认应用于子资源。

范围:

负载

  • <subresource-path>:要支持的子资源路径(例如,"/status""/scale")。

使用示例

通过添加这些标签,你正在启用验证系统来处理包 v1 中定义的类型的 /status/scale 子资源的请求。

文件:staging/src/k8s.io/api/core/v1/doc.go

// +k8s:supportsSubresource="/status"
// +k8s:supportsSubresource="/scale"
package v1

+k8s:unionDiscriminator

描述

指示此字段是联合体的判别器。

参数

  • union (字符串,可选):如果存在多个联合体,则为联合体的名称。

使用示例

type MyStruct struct {
	TypeMeta int

	// +k8s:unionDiscriminator
	D D `json:"d"`

	// +k8s:unionMember
	// +k8s:optional
	M1 *M1 `json:"m1"`

	// +k8s:unionMember
	// +k8s:optional
	M2 *M2 `json:"m2"`
}

type D string

const (
	DM1 D = "M1"
	DM2 D = "M2"
)

type M1 struct{}

type M2 struct{}

在此示例中,Type 字段是联合体的判别器。Type 的值将决定预期存在哪个联合体成员(M1M2)。

+k8s:unionMember

描述

指示此字段是联合体的成员。

参数

  • union (字符串,可选):如果存在多个联合体,则为联合体的名称。
  • memberName (字符串,可选):此成员的判别器值。默认为字段的名称。

使用示例

type MyStruct struct {
	// +k8s:unionMember(union: "union1")
	// +k8s:optional
	M1 *M1 `json:"u1m1"`

	// +k8s:unionMember(union: "union1")
	// +k8s:optional
	M2 *M2 `json:"u1m2"`
}

type M1 struct{}

type M2 struct{}

在此示例中,M1M2 是命名联合体 union1 的成员。

+k8s:zeroOrOneOfMember

描述

指示此字段是零或一联合的成员。零或一联合最多允许设置一个成员。与常规联合不同,没有成员设置是有效的。

参数

  • union (字符串,可选):如果存在多个联合体,则为联合体的名称。
  • memberName (字符串,可选):此成员的自定义成员名称。默认为字段的名称。

使用示例

type MyStruct struct {
	// +k8s:zeroOrOneOfMember
	// +k8s:optional
	M1 *M1 `json:"m1"`

	// +k8s:zeroOrOneOfMember
	// +k8s:optional
	M2 *M2 `json:"m2"`
}

type M1 struct{}

type M2 struct{}

在此示例中,最多可以设置 AB 中的一个。不设置任何一个也是有效的。

最后修改时间:2025 年 8 月 24 日太平洋标准时间晚上 11:25:小幅文档改进 (f34c970f03)