本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
介紹 client-go 版本 6
Kubernetes API 伺服器公開了一個可供任何客戶端使用的 REST 介面。client-go 是 Go 程式語言的官方客戶端庫。它既被 Kubernetes 內部使用(例如,在 kubectl 中),也被眾多外部消費者使用:如 etcd-operator 或 prometheus-operator 等運算子;如 KubeLess 和 OpenShift 等更高階的框架;以及更多。
client-go 的版本 6 更新增加了對 Kubernetes 1.9 的支援,允許訪問最新的 Kubernetes 功能。雖然更新日誌包含了所有詳細資訊,但這篇博文將重點介紹最突出的變化,並旨在指導如何從版本 5 升級。
這篇博文是使 client-go 更易於第三方消費者使用的一系列努力之一。更便捷的訪問是許多來自不同公司的人員共同努力的成果,他們都在 Kubernetes Slack 的 #client-go-docs 頻道中開會。我們很高興聽到改進的反饋和想法,當然也感謝所有願意貢獻的人。
API 組變更
以下 API 組的提升是 Kubernetes 1.9 的一部分
- 工作負載物件(Deployment、DaemonSet、ReplicaSet 和 StatefulSet)已在 Kubernetes 1.9 中提升到 apps/v1 API 組。client-go 遵循此轉換,允許開發者透過匯入 k8s.io/api/apps/v1 包而不是 k8s.io/api/apps/v1beta1 並使用 Clientset.AppsV1() 來使用最新版本。
- 准入 Webhook 註冊已在 Kubernetes 1.9 中提升到 admissionregistration.k8s.io/v1beta1 API 組。舊的 ExternalAdmissionHookConfiguration 型別已被不相容的 ValidatingWebhookConfiguration 和 MutatingWebhookConfiguration 型別取代。此外,admission.k8s.io 中的 webhook 准入負載型別 AdmissionReview 已提升到 v1beta1。請注意,版本化物件現在傳遞給 webhook。有關詳細資訊,請參閱准入 webhook 文件。
自定義資源的驗證
在 Kubernetes 1.8 中,我們引入了 CustomResourceDefinitions (CRD) 持久化前模式驗證作為 Alpha 功能。在 1.9 中,該功能提升為 Beta 版,並將預設啟用。作為 client-go 使用者,您將在 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1 中找到 API 型別。
OpenAPI v3 模式可以在 CRD 規範中定義,如下所示:
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata: ...
spec:
...
validation:
openAPIV3Schema:
properties:
spec:
properties:
version:
type: string
enum:
- "v1.0.0"
- "v1.0.1"
replicas:
type: integer
minimum: 1
maximum: 10
上述 CRD 中的 schema 對例項應用以下驗證:
- spec.version 必須是字串,且必須是“v1.0.0”或“v1.0.1”。
- spec.replicas 必須是整數,且最小值必須為 1,最大值必須為 10。具有無效 spec.version (v1.0.2) 和 spec.replicas (15) 值的 CustomResource 將被拒絕。
apiVersion: mygroup.example.com/v1
kind: App
metadata:
name: example-app
spec:
version: "v1.0.2"
replicas: 15
$ kubectl create -f app.yaml
The App "example-app" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"mygroup.example.com/v1", "kind":"App", "metadata":map[string]interface {}{"creationTimestamp":"2017-08-31T20:52:54Z", "uid":"5c674651-8e8e-11e7-86ad-f0761cb232d1", "clusterName":"", "name":"example-app", "namespace":"default", "deletionTimestamp":interface {}(nil), "deletionGracePeriodSeconds":(\*int64)(nil)}, "spec":map[string]interface {}{"replicas":15, "version":"v1.0.2"}}:
validation failure list:
spec.replicas in body should be less than or equal to 10
spec.version in body should be one of [v1.0.0 v1.0.1]
請注意,透過 Admission Webhooks,Kubernetes 1.9 提供了另一個 Beta 功能,用於在建立或更新物件之前對其進行驗證。從 1.9 開始,這些 webhook 還允許修改物件(例如,設定預設值或注入值)。當然,webhook 也適用於 CRD。此外,webhook 可用於實現 CRD 驗證不易表達的驗證。請注意,webhook 比 CRD 驗證更難實現,因此對於許多目的來說,CRD 驗證是正確的工具。
建立名稱空間內 Informer
通常,一個名稱空間中的物件或者只具有某些標籤的物件需要在控制器中進行處理。Informer 現在允許您調整用於查詢 API 伺服器以列出和觀察物件的 ListOptions。未初始化的物件(供初始化器使用)可以透過將 IncludeUninitialized 設定為 true 來使其可見。所有這些都可以透過新的 NewFilteredSharedInformerFactory 建構函式實現共享 informer。
import “k8s.io/client-go/informers”
...
sharedInformers := informers.NewFilteredSharedInformerFactory(
client,
30\*time.Minute,
“some-namespace”,
func(opt \*metav1.ListOptions) {
opt.LabelSelector = “foo=bar”
},
)
請注意,相應的 lister 將只知道與名稱空間和給定 ListOptions 匹配的物件。請注意,相同的限制適用於客戶端上的 List 或 Watch 呼叫。
這個來自 cert-manager 的生產程式碼示例展示瞭如何在實際程式碼中使用名稱空間 informer。
多型伸縮客戶端
歷史上,只有 extensions API 組中的型別才能與自動生成的 Scale 客戶端一起使用。此外,不同的 API 組為其 /scale 子資源使用不同的 Scale 型別。為了解決這些問題,k8s.io/client-go/scale 提供了一個多型伸縮客戶端,以一致的方式伸縮不同 API 組中的不同資源。
import (
apimeta "k8s.io/apimachinery/pkg/api/meta"
discocache "k8s.io/client-go/discovery/cached"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
“k8s.io/client-go/scale”
)
...
cachedDiscovery := discocache.NewMemCacheClient(client.Discovery())
restMapper := discovery.NewDeferredDiscoveryRESTMapper(
cachedDiscovery,
apimeta.InterfacesForUnstructured,
)
scaleKindResolver := scale.NewDiscoveryScaleKindResolver(
client.Discovery(),
)
scaleClient, err := scale.NewForConfig(
client, restMapper,
dynamic.LegacyAPIPathResolverFunc,
scaleKindResolver,
)
scale, err := scaleClient.Scales("default").Get(groupResource, "foo")
返回的伸縮物件是通用的,並作為 autoscaling/v1.Scale 物件公開。它由一個內部 Scale 型別支援,其中定義了所有支援伸縮的 API 組中特殊 Scale 型別之間的轉換。我們計劃在 1.10 中將其擴充套件到 CustomResources。
如果您正在實現對 scale 子資源的支援,我們建議您公開 autoscaling/v1.Scale 物件。
型別安全的深複製
以前,深度複製物件需要呼叫 Scheme.Copy(Object),其顯著缺點是會失去型別安全性。client-go 版本 5 中的典型程式碼片段需要型別轉換:
newObj, err := runtime.NewScheme().Copy(node)
if err != nil {
return fmt.Errorf("failed to copy node %v: %s”, node, err)
}
newNode, ok := newObj.(\*v1.Node)
if !ok {
return fmt.Errorf("failed to type-assert node %v", newObj)
}
感謝 k8s.io/code-generator,Copy 現在已被每個物件上的型別安全 DeepCopy 方法取代,允許您在程式碼量和 API 錯誤表面方面顯著簡化程式碼。
newNode := node.DeepCopy()
無需錯誤處理:此呼叫絕不會失敗。當且僅當節點為 nil 時,DeepCopy() 返回 nil。
為了複製 runtime.Objects,runtime.Object 介面中還有一個額外的 DeepCopyObject() 方法。
由於舊方法已被刪除,客戶端需要相應地更新其複製呼叫。
程式碼生成和自定義資源
不鼓勵使用 client-go 的動態客戶端訪問自定義資源,而是由使用 k8s.io/code-generator 中生成器的型別安全程式碼取代。請檢視 Open Shift 部落格上的深度解析,瞭解如何將程式碼生成與 client-go 結合使用。
註釋塊
您現在可以將標籤放置在型別或函式正上方的註釋塊中,或在其上方的第二個塊中。這兩個註釋塊之間不再有任何區別。這曾是使用生成器時出現細微錯誤的原因。
// second block above
// +k8s:some-tag
// first block above
// +k8s:another-tag
type Foo struct {}
自定義客戶端方法
您現在可以使用擴充套件標籤定義來建立自定義動詞。這使您能夠超越 HTTP 定義的動詞。這為更高級別的自定義打開了大門。
例如,此塊導致生成方法 UpdateScale(s *autoscaling.Scale) (*autoscaling.Scale, error)。
// genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/autoscaling.Scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale
解決 Golang 命名衝突
在更復雜的 API 組中,Kind、組名、Go 包名和 Go 組別名可能存在衝突。在 1.9 之前,這並未正確處理。以下標籤解決了命名衝突並使生成的程式碼更美觀:
// +groupName=example2.example.com
// +groupGoName=SecondExample
這些通常位於 API 包的 doc.go 檔案中。第一個用作 CustomResource 組名,當使用 HTTP 以 RESTful 方式與 API 伺服器通訊時使用。第二個用於生成的 Golang 程式碼(例如,在 clientset 中)以訪問組版本。
clientset.SecondExampleV1()
最後,Go 包名中可以包含點。在本節的示例中,您需要將 groupName 片段放入專案的 pkg/apis/example2.example.com 目錄中。
示例專案
Kubernetes 1.9 包含了一些示例專案,可以作為您自己專案的藍圖。
- k8s.io/sample-apiserver 是一個簡單的使用者提供的 API 伺服器,透過API 聚合整合到叢集中。
- k8s.io/sample-controller 是一個功能齊全的控制器(也稱為運算子),帶有共享 informer 和工作佇列,用於處理已建立、已更改或已刪除的物件。它基於 CustomResourceDefinitions,並使用 k8s.io/code-generator 生成深複製函式、型別化客戶端集、informer 和 lister。
依賴管理
為了從 client-go 的上一個版本 5 更新到版本 6,庫本身以及某些第三方依賴項都必須更新。以前,這個過程很繁瑣,因為很多程式碼在現有包佈局中跨版本進行了重構或重新定位。幸運的是,最新版本中需要移動的程式碼少得多,這應該會簡化大多數使用者的升級過程。
已釋出倉庫的狀態
過去,k8s.io/client-go、k8s.io/api 和 k8s.io/apimachinery 更新不頻繁。標籤(例如 v4.0.0)在 Kubernetes 釋出後很久才建立。隨著 1.9 版本的釋出,我們恢復了執行夜間機器人,該機器人會自動更新所有倉庫供公眾使用,甚至在手動標記之前。這包括以下分支:
- master
- release-1.8 / release-5.0
- release-1.9 / release-6.0 Kubernetes 標籤(例如 v1.9.1-beta1)也會自動應用到已釋出的倉庫,字首為 kubernetes-(例如 kubernetes-1.9.1-beta1)。
這些標籤的測試覆蓋率有限,但可以供 client-go 和其他庫的早期採用者使用。此外,它們有助於管理 k8s.io/api 和 k8s.io/apimachinery 的正確版本。請注意,我們只在 k8s.io/client-go 上建立了 v6.0.3 類似的語義版本標籤。k8s.io/api 和 k8s.io/apimachinery 的相應標籤是 kubernetes-1.9.3。
另請注意,只有這些標籤才對應經過測試的 Kubernetes 版本。如果您依賴於釋出分支,例如 release-1.9,您的客戶端正在執行未釋出的 Kubernetes 程式碼。
client-go 的依賴管理現狀
一般來說,要管理的依賴項列表是自動生成的,並寫入檔案 Godeps/Godeps.json。只有其中列出的修訂版經過測試。這意味著我們不且無法針對依賴項的主分支測試程式碼庫。根據使用的依賴管理工具,我們處於以下情況:
- godep 透過在您的 GOPATH 中從 k8s.io/client-go 執行 godep restore 來讀取 Godeps/Godeps.json。然後使用 godep save 在您的專案中進行依賴管理。godep 將從您的 GOPATH 中選擇正確的版本。
- glide 會在初始化和更新時自動從其依賴項(包括 k8s.io/client-go)中讀取 Godeps/Godeps.json。因此,只要沒有衝突,glide 大部分應該是自動的。
- dep 目前無法以一致的方式遵守 Godeps/Godeps.json,尤其是在更新時。至關重要的是手動將 client-go 依賴項指定為約束或覆蓋,也適用於非 k8s.io/* 依賴項。如果沒有這些,dep 只會選擇依賴項的主分支,這可能會導致問題,因為它們會頻繁更新。
- Kubernetes 和 golang/dep 社群已經意識到這些問題 [問題 #1124, 問題 #1236],並且正在共同努力尋找解決方案。在此之前,必須特別小心。有關詳細資訊,請參閱 client-go 的 INSTALL.md。
更新依賴項 – golang/dep
即使 golang/dep 目前存在不足,dep 仍在逐漸成為 Go 生態系統中事實上的標準。只要小心謹慎並意識到其缺失的功能,dep 就可以(而且已經!)成功使用。以下是演示如何使用 dep 將 client-go 5 的專案更新到最新版本 6 的方法:
(如果您仍在執行 client-go 版本 4,並且希望透過不跳過釋出來確保安全,那麼現在是檢視這篇由 Heptio 的朋友們整理的優秀博文的好時機,該博文介紹瞭如何升級到版本 5。)
在開始之前,重要的是要了解 client-go 依賴於另外兩個 Kubernetes 專案:k8s.io/apimachinery 和 k8s.io/api。此外,如果您正在使用 CRD,您可能還依賴 k8s.io/apiextensions-apiserver 來獲取 CRD 客戶端。第一個公開了較低級別的 API 機制(例如方案、序列化和型別轉換),第二個持有 API 定義,第三個提供與 CustomResourceDefinitions 相關的 API。為了使 client-go 正確執行,它需要將其配套庫以相應匹配的版本進行 vendoring。每個庫倉庫都提供一個名為 release-<version> 的分支,其中 <version> 指的是特定的 Kubernetes 版本;對於 client-go 版本 6,必須引用每個倉庫上的 release-1.9 分支。
假設 client-go 的最新版本 5 補丁版本是透過 dep 託管的,Gopkg.toml 清單檔案應該如下所示(可能使用分支而不是版本):
[[constraint]]
name = "k8s.io/api"
version = "kubernetes-1.8.1"
[[constraint]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.8.1"
[[constraint]]
name = "k8s.io/apiextensions-apiserver"
version = "kubernetes-1.8.1"
[[constraint]]
name = "k8s.io/client-go"
version = "5.0.1"
請注意,如果客戶端實際不需要某些庫,則這些庫可能會缺失。
升級到 client-go 版本 6 意味著按照以下方式增加版本和標籤識別符號(已**加粗**顯示):
[constraint]]
name = "k8s.io/api"
version = "kubernetes-1.9.0"
[[constraint]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.9.0"
[[constraint]]
name = "k8s.io/apiextensions-apiserver"
version = "kubernetes-1.9.0"
[[constraint]]
name = "k8s.io/client-go"
version = "6.0.0"
升級結果可在此處找到。
需要注意:如上所述,dep 無法以可靠和可重現的方式捕獲完整的依賴項集。這意味著對於一個 100% 具備未來適應性的專案,您必須為 client-go 的 Godeps/Godeps.json 中列出的許多其他包新增約束(甚至覆蓋)。如果出現問題,請準備好新增它們。我們正在與 golang/dep 社群合作,以使這一體驗更輕鬆、更順暢。
最後,我們需要透過執行 dep ensure 命令來告訴 dep 升級到指定的版本。如果一切順利,命令呼叫的輸出應該為空,唯一表明成功的是 vendor 資料夾中更新了一些檔案。
如果您正在使用 CRD,您可能也會使用程式碼生成。以下 Gopkg.toml 程式碼塊將向您的專案新增所需的程式碼生成包:
required = [
"k8s.io/code-generator/cmd/client-gen",
"k8s.io/code-generator/cmd/conversion-gen",
"k8s.io/code-generator/cmd/deepcopy-gen",
"k8s.io/code-generator/cmd/defaulter-gen",
"k8s.io/code-generator/cmd/informer-gen",
"k8s.io/code-generator/cmd/lister-gen",
]
[[constraint]]
branch = "kubernetes-1.9.0"
name = "k8s.io/code-generator"
此時,您是否希望透過 dep 修剪不需要的包(例如測試檔案)或將更改提交到版本控制系統,這取決於您——但從升級的角度來看,您現在應該已經準備好透過 client-go 利用 Kubernetes 1.9 帶來的所有酷炫新功能。