声明式 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 包括:
注意
对于此特性的 Beta 版本,Kubernetes 有意使用一个已被取代的 API 作为此更改的测试平台。未来的 Kubernetes 版本可能会将其推广到更多 API。DeclarativeValidation
:(Beta,默认值:true
)启用后,API 服务器会针对已迁移的类型/字段同时运行新的声明式验证和旧的手写验证。内部会比较结果。DeclarativeValidationTakeover
:(Beta,默认值:false
)此门控决定哪个验证结果是*权威的*(即返回给用户并用于准入决策)。
默认行为 (Kubernetes 1.34)
- 当
DeclarativeValidation=true
和DeclarativeValidationTakeover=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
用于指定 MyStruct
的 keyField
应用作列表映射的键。
+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
的值将决定预期存在哪个联合体成员(M1
或 M2
)。
+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{}
在此示例中,M1
和 M2
是命名联合体 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{}
在此示例中,最多可以设置 A
或 B
中的一个。不设置任何一个也是有效的。