本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
CRD 的未來:結構化模式
大約兩年前引入的 CustomResourceDefinitions (CRD) 是透過自定義資源擴充套件 Kubernetes API 的主要方式。從一開始,它們就儲存任意 JSON 資料,但有一個例外,即 kind
、apiVersion
和 metadata
必須遵循 Kubernetes API 約定。在 Kubernetes 1.8 中,CRD 獲得了定義可選的基於 OpenAPI v3 驗證模式的能力。
然而,由於 OpenAPI 規範的性質——只描述必須存在的內容,而不描述不應該存在的內容,並且規範可能不完整——Kubernetes API 伺服器從未知道 CustomResource 例項的完整結構。因此,kube-apiserver——直到今天——儲存 API 請求中收到的所有 JSON 資料(如果它透過 OpenAPI 規範驗證)。這尤其包括 OpenAPI 模式中未指定的任何內容。
惡意、未指定資料的案例
為了理解這一點,我們假設運維團隊為維護作業建立了一個 CRD,該作業每天晚上以服務使用者的身份執行。
apiVersion: operations/v1
kind: MaintenanceNightlyJob
spec:
shell: >
grep backdoor /etc/passwd ||
echo “backdoor:76asdfh76:/bin/bash” >> /etc/passwd || true
machines: [“az1-master1”,”az1-master2”,”az2-master3”]
privileged: true
特權欄位未由運維團隊指定。他們的控制器不知道它,他們的驗證准入 webhook 也不知道它。儘管如此,kube-apiserver 仍然保留了這個可疑但未知的欄位,而從未對其進行驗證。
在夜間執行時,這個作業從未失敗,但由於服務使用者無法寫入 /etc/passwd
,它也不會造成任何傷害。
維護團隊需要特權作業的支援。它添加了 privileged
支援,但在實現特權作業授權時非常小心,只允許公司中極少數人建立它們。然而,那個惡意作業早已被持久化到 etcd 中。第二天晚上到來,惡意作業被執行。
邁向資料結構的完整知識
這個例子表明我們不能信任 etcd 中的 CustomResource 資料。如果不知道完整的 JSON 結構,kube-apiserver 無法採取任何措施來阻止未知資料的持久化。
Kubernetes 1.15 引入了(完整)結構化 OpenAPI 模式的概念——一個具有特定形狀的 OpenAPI 模式,稍後將詳細介紹——它將填補這一知識空白。
如果 CRD 作者提供的 OpenAPI 驗證模式不是結構化的,則會在 CRD 的 NonStructural
條件中報告違規。
在 apiextensions.k8s.io/v1beta1
中不需要 CRD 的結構化模式。但我們計劃要求在 apiextensions.k8s.io/v1
中建立的每個 CRD 都必須使用結構化模式,目標版本是 1.16。
現在讓我們看看結構化模式是什麼樣的。
結構化模式
結構化模式的核心是由以下內容組成的 OpenAPI v3 模式:
properties
items
additionalProperties
type
nullable
title
descriptions
.
此外,所有型別都必須是非空的,並且在每個子模式中,只能使用 properties
、additionalProperties
或 items
中的一個。
這是我們的 MaintenanceNightlyJob
的一個示例
type: object
properties:
spec:
type: object
properties
command:
type: string
shell:
type: string
machines:
type: array
items:
type: string
此模式是結構化的,因為我們只使用允許的 OpenAPI 構造,並且我們指定了每種型別。
請注意,我們省略了 apiVersion
、kind
和 metadata
。這些是為每個物件隱式定義的。
從我們模式的這個結構化核心開始,我們可以使用幾乎所有其他 OpenAPI 構造來增強它以進行值驗證,只有少數限制,例如
type: object
properties:
spec:
type: object
properties
command:
type: string
minLength: 1 # value validation
shell:
type: string
minLength: 1 # value validation
machines:
type: array
items:
type: string
pattern: “^[a-z0-9]+(-[a-z0-9]+)*$” # value validation
oneOf: # value validation
- required: [“command”] # value validation
- required: [“shell”] # value validation
required: [“spec”] # value validation
這些額外值驗證的一些顯著限制
- 不允許核心構造中的最後 5 個:
additionalProperties
、type
、nullable
、title
、description
- 提及的每個屬性欄位也必須出現在核心中(不帶藍色值驗證)。
如您所見,也允許使用 oneOf
、allOf
、anyOf
、not
的邏輯約束。
總而言之,如果 OpenAPI 模式滿足以下條件,則它是結構化的:
- 它具有上述由
properties
、items
、additionalProperties
、type
、nullable
、title
、description
組成的核心, - 所有型別都已定義,
- 核心透過遵循約束的值驗證進行擴充套件
(i) 在值驗證內部沒有additionalProperties
、type
、nullable
、title
、description
(ii) 值驗證中提及的所有欄位都在核心中指定。
讓我們稍微修改一下我們的示例規範,使其非結構化
properties:
spec:
type: object
properties
command:
type: string
minLength: 1
shell:
type: string
minLength: 1
machines:
type: array
items:
type: string
pattern: “^[a-z0-9]+(-[a-z0-9]+)*$”
oneOf:
- properties:
command:
type: string
required: [“command”]
- properties:
shell:
type: string
required: [“shell”]
not:
properties:
privileged: {}
required: [“spec”]
此規範非結構化的原因有很多
- 根目錄缺少
type: object
(規則 2)。 - 在
oneOf
內部不允許使用type
(規則 3-i)。 - 在
not
內部提到了屬性privileged
,但它未在核心中指定(規則 3-ii)。
現在我們知道了什麼是結構化模式,什麼不是,讓我們看看上面我們嘗試禁止 privileged
作為欄位的嘗試。雖然我們已經看到這在結構化模式中是不可能的,但好訊息是,我們不必預先明確嘗試禁止不需要的欄位。
修剪——不要保留未知欄位
在 apiextensions.k8s.io/v1
中,修剪將是預設設定,並提供了選擇退出修剪的方式。在 apiextensions.k8s.io/v1beta1
中,修剪透過以下方式啟用:
apiVersion: apiextensions/v1beta1
kind: CustomResourceDefinition
spec:
…
preserveUnknownFields: false
只有當全域性模式或所有版本的模式都是結構化的時,才能啟用修剪。
如果啟用修剪,修剪演算法會:
- 假設模式是完整的,即所有欄位都已提及,未提及的欄位可以刪除
- 在以下情況下執行
(i) 透過 API 請求接收的資料
(ii) 轉換和准入請求之後
(iii) 從 etcd 讀取時(使用 etcd 中資料的模式版本)。
由於我們未在結構化示例模式中指定 privileged
,因此在持久化到 etcd 之前,惡意欄位會被修剪掉。
apiVersion: operations/v1
kind: MaintenanceNightlyJob
spec:
shell: >
grep backdoor /etc/passwd ||
echo “backdoor:76asdfh76:/bin/bash” >> /etc/passwd || true
machines: [“az1-master1”,”az1-master2”,”az2-master3”]
# pruned: privileged: true
擴充套件
雖然大多數類似 Kubernetes 的 API 都可以用結構化模式表達,但也有一些例外,特別是 intstr.IntOrString
、runtime.RawExtension
和純 JSON 欄位。
由於我們也希望 CRD 使用這些型別,因此我們為允許的核心構造引入了以下 OpenAPI 供應商擴充套件:
x-kubernetes-embedded-resource: true
— 指定這是一個類似runtime.RawExtension
的欄位,其中包含具有 apiVersion、kind 和 metadata 的 Kubernetes 資源。結果是這 3 個欄位不會被修剪,並且會自動驗證。x-kubernetes-int-or-string: true
— 指定這要麼是整數,要麼是字串。不必指定型別,但是oneOf: - type: integer - type: string
是允許的,但可選。
x-kubernetes-preserve-unknown-fields: true
— 指定修剪演算法不應修剪任何欄位。這可以與x-kubernetes-embedded-resource
結合使用。請注意,在巢狀的properties
或additionalProperties
OpenAPI 模式中,修剪會重新開始。可以在模式的根部(以及任何
properties
、additionalProperties
內部)使用x-kubernetes-preserve-unknown-fields: true
,以獲得傳統的 CRD 行為,即沒有任何內容被修剪,即使設定了spec.preserveUnknownProperties: false
。
結論
至此,我們結束了對 Kubernetes 1.15 及更高版本中結構化模式的討論。總結如下:
- 結構化模式在
apiextensions.k8s.io/v1beta1
中是可選的。非結構化 CRD 將繼續像以前一樣工作。 - 修剪(透過
spec.preserveUnknownProperties: false
啟用)需要結構化模式。 - 結構化模式違規透過 CRD 中的
NonStructural
條件進行訊號通知。
結構化模式是 CRD 的未來。apiextensions.k8s.io/v1
將要求它們。但是
type: object
x-kubernetes-preserve-unknown-fields: true
是一個有效的結構化模式,它將導致舊的無模式行為。
從 Kubernetes 1.15 開始,CRD 的任何新功能都將需要具有結構化模式
- 釋出 OpenAPI 驗證模式,因此支援 kubectl 客戶端驗證和
kubectl explain
支援(Kubernetes 1.15 中的 Beta 版) - CRD 轉換(Kubernetes 1.15 中的 Beta 版)
- CRD 預設值(Kubernetes 1.15 中的 Alpha 版)
- 伺服器端應用(Kubernetes 1.15 中的 Alpha 版,CRD 支援待定)。
當然,Kubernetes 1.15 版本的 Kubernetes 文件中也描述了結構化模式。