本文已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
CRD 的未來:結構描述
大約兩年前推出了 CustomResourceDefinitions,作為使用自訂資源擴展 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
特權欄位未由運營團隊指定。他們的控制器不知道它,他們的驗證 admission webhook 也不知道它。儘管如此,kube-apiserver 仍持續保存此可疑但未知的欄位,而從未驗證它。
在晚上執行時,此工作永遠不會失敗,但由於服務使用者無法寫入 /etc/passwd
,因此也不會造成任何危害。
維護團隊需要支援特權工作。它增加了 privileged
支援,但超級謹慎地實施特權工作的授權,僅允許公司中極少數人建立這些工作。然而,惡意工作早已被持久化到 etcd。下一個晚上到來,惡意工作被執行。
朝向資料結構的完整知識
此範例顯示我們無法信任 etcd 中的 CustomResource 資料。在沒有關於 JSON 結構的完整知識的情況下,kube-apsierver 無法採取任何措施來防止未知資料的持久性。
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
- 提到的每個 properties 欄位也必須出現在核心中(不包含藍色值驗證)。
如您所見,也允許使用 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)在轉換和 admission 請求之後
(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 版本的文件中進行了描述。