宣告式 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
中的一個。不設定任何一個也是有效的。