Kubernetes Visitor 模式
關 注 微 信 公 眾 號 《 雲 原 生 C T O 》 更 多 雲 原 生 幹 貨 等 你 來 探 索
專
注
於
雲原生技術
分
享
提
供
優
質
雲原生開發
視
頻
技
術
培
訓
面試技巧
,
及
技術疑難問題
解答
雲
原
生
技
術
分
享
不
僅
僅
局
限
於
Go
、
Rust
、
Python
、
Istio
、
containerd
、
CoreDNS
、
Envoy
、
etcd
、
Fluentd
、
Harbor
、
Helm
、
Jaeger
、
Kubernetes
、
Open Policy Agent
、
Prometheus
、
Rook
、
TiKV
、
TUF
、
Vitess
、
Argo
、
Buildpacks
、
CloudEvents
、
CNI
、
Contour
、
Cortex
、
CRI-O
、
Falco
、
Flux
、
gRPC
、
KubeEdge
、
Linkerd
、
NATS
、
Notary
、
OpenTracing
、
Operator Framework
、
SPIFFE
、
SPIRE
和
Thanos
等
Kubernetes Visitor 模式
說到那些為我打開了高效程式設計大門的人,我想說的是四人組設計模式(Design Pattern by Gang of four)是第一個推動我的人
http://www.gofpatterns.com/index.php
幫助我更好地理解各種程式碼結構,更合理地編碼。當然是基於 Java
的,還有很多其他設計模式的文章,因為設計模式是很多 Java
開源框架所奉行的原則,比如 springframework
中常見的 factory
模式、 proxy
模式和 visitor
模式。
幸運的是,您所學到的和牢記於心的東西將會得到回報——我從 Java
得到的“ key
”似乎也適用於 Kubernetes
。讀完 Kubectl
和 k8s
的原始碼後,您會發現它們擁有相似的設計模式,儘管實現方式不同。
好了,讓我們回到主題,深入探究 Visitor
模式,看看“ key
”在 Kubectl
和 Kubernetes
中是如何工作的,以促進日常的編碼和開發。
什麼是Visitor模式
用 Visitor
模式編碼的工作流是最好的答案。
在 Gof
中,還解釋了為什麼引入 Visitor
模式。
在設計跨類層次結構的異構物件集合的操作時, Visitor
模式非常有用。 Visitor
模式允許在不更改集合中任何物件的類的情況下定義操作。為了實現這一點, Visitor
模式建議在一個單獨的類(稱為 Visitor
類)中定義操作。這將操作與它所操作的物件集合分開。對於每一個要定義的新操作,都會建立一個新的 Visitor
類。因為操作要跨一組物件執行,所以 Visitor
需要一種訪問這些物件的公共成員的方法。這個需求可以通過實現以下兩個設計思想來解決。
在實際場景中解釋它:基於介面特性,動態地解耦物件和它們的動作,以實現更穩定的程式碼、更少的維護和更快的新增新函式(新增一個新的 ConcreteVisitor
)的迭代。
在 Go
中, Visitor
模式的應用程式可以做同樣的改進,因為介面是它的主要特性之一。
Kubectl和Kubernetes中的訪客模式
Kubernetes
是一個容器編排平臺,上面有各種不同的資源。 kubectl
是一個命令列工具,它使用以下命令格式操作資源。
kubectl get {resourcetype} {resource_name}
kubectl edit {resourcetype} {resource_name}
…
kubectl
將這些命令組合到 APIServer
接收到的資料中,發起一個請求,然後返回結果,實際執行一個構建器方法,該方法封裝了各種 Visitor
來處理請求的引數和結果,最後得到我們在命令列上看到的結果。
構建器方法: http://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/staging/src/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go#L94
func (f *factoryImpl) NewBuilder() *resource.Builder {
return resource.NewBuilder(f.clientGetter)
}
這裡的大多數 Visitor
都是在 Visitor
中定義的。 Go
,原始檔的名稱不言自明。
這段程式碼有 700
多行,它使用了 builder
模式( builder.go
)和 visitor
模式來連線 Visitor
,並通過呼叫它們各自的 VisitorFunc
方法來實現函式,同時在 builder.go
中封裝了 VisitorFunc
的具體實現。
visitor.go: http://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go
builder.go: http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/builder.go
VisitorFunc: http://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/staging/src/k8s.io/cli-runtime/pkg/resource/interfaces.go#L103
type VisitorFunc func(*Info, error) error
type Visitor interface {
Visit(VisitorFunc) error
}
type Info struct {
Namespace string
Name string
OtherThings string
}
func (info *Info) Visit(fn VisitorFunc) error {
return fn(info, nil)
}
那些執行 Visit
方法的 visitor
被認為是合格的 visitor
。讓我們來看看一些典型的 visitor
。
Selector
在 kubectl
中,我們預設訪問預設名稱空間。但是有 -n/ -namespace
選項來指定我們想要訪問的名稱空間,還有 -l/ -label
用於選擇合格的資源。命令如下所示。
kubectl get pod pod1 -n test -l abc=true
通過 Selector
訪問器檢視實現。
http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/selector.go#L27
type Selector struct {
Client RESTClient
Mapping *meta.RESTMapping
Namespace string
LabelSelector string
FieldSelector string
LimitChunks int64
}
當然,我們實現了 Visit
方法,最終為 api
訪問構造了合理的 Info
物件。
http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/selector.go#L67
list, err := helper.List(
r.Namespace,
r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
&options,
)
if err != nil {
return nil, EnhanceListError(err, options, r.Mapping.Resource.String())
}
resourceVersion, _ := metadataAccessor.ResourceVersion(list)
info := &Info{
Client: r.Client,
Mapping: r.Mapping,
Namespace: r.Namespace,
ResourceVersion: resourceVersion,
Object: list,
}
if err := fn(info, nil); err != nil {
return nil, err
}
DecoratedVisitor
DecoratedVisitor
包含一個 Visitor
和一組裝飾器( VisitorFunc
),在執行 Visit
方法時按順序執行所有的裝飾器。
http://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go#L309
// Visit implements Visitor
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
return v.visitor.Visit(func(info *Info, err error) error {
if err != nil {
return err
}
for i := range v.decorators {
if err := v.decorators[i](info, nil); err != nil {
return err
}
}
return fn(info, nil)
})
}
當在生成器中初始化它時, Visitor
將被新增到由結果處理的 Visitor
列表中。你可以在名稱空間處理後呼叫 get
方法。
http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/builder.go#L1119
if b.latest {
// must set namespace prior to fetching
if b.defaultNamespace {
visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace))
}
visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
}
func RetrieveLatest(info *Info, err error) error {
if err != nil {
return err
}
if meta.IsListType(info.Object) {
return fmt.Errorf("watch is only supported on individual resources and resource collections, but a list of resources is found")
}
if len(info.Name) == 0 {
return nil
}
if info.Namespaced() && len(info.Namespace) == 0 {
return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
}
return info.Get()
}
在程式碼中,也有一些類似的 Visitor
處理不同的邏輯。這種設計模式的一個明顯優點是操作簡單。基本上,所有的資源都符合這個基於 gkv
的操作。所以當新增 Visitor
時,不修改 Visitor
。去是必要的。相反,只要實現了 VisitorFunc
介面,就可以直接新增新的 go
檔案。然後在構建器構建過程中新增相關邏輯。
實際的Visitor模式
回顧我最近寫的程式碼,我已經不知不覺地發現了 Visitor
模式的蹤跡。我和我的同事定製了許多 crd
,編寫了 operator
,並在叢集中執行它們,提供不同的服務,如安全、 RBAC
自動新增、 SA
自動建立等。
這些 cd
都有不同的欄位屬性,例如, groupprbac
包含組名、電子郵件和使用者列表。標識,包含組名和關聯的 Rolebindings
的狀態。由於厭倦了重複的 kubectl get groupbac xxx
和 kubectl get identity xxx
,我決定建立一個 kubectl
外掛來使用 kubectl groupget {groupName}
獲取它們。那麼如何?我首先想到的是 Bash
,但如果新增更多的資源,它將很難維護和擴充套件。
回到 Visitor
模式本身。在處理資源訪問時,我定義了一組 Visitor
,可以用來訪問不同的資源。程式碼結構如下所示。
type VisitorFunc func(*Info, error) error
type GroupRbacVisitor struct {
visitor Visitor
results map[string]GroupResult
}
func (v GroupRbacVisitor) Visit(fn VisitorFunc) error {
return v.visitor.Visit(func(info *Info, err error) error {
// ...
}
}
type IdentityVisitor struct {
visitor Visitor
results map[string]IdentityResult
}
func (v IdentityVisitor) Visit(fn VisitorFunc) error {
return v.visitor.Visit(func(info *Info, err error) error {
// ...
}
}
每次獲得的結果儲存在各自的結果中,並最終被收集和處理。每當新增新資源時,我只需要定義一個新的 Visitor
,編寫相應的 visit
方法,並可能調整最終的顯示邏輯。超級方便!
func FetchAll(c *cobra.Command, visitors []Visitor) error {
// ...
for _, visitor := range visitors {
v.Visit(func(*Info, error) error {
//...
})
}
// ...
}
結束
我們從未停止尋找編寫更易於閱讀、維護和擴充套件的程式碼的方法。我相信學習、理解和實踐設計模式是讓我們更接近目標的途徑之一。
直到我研究了原始碼,我才意識到我已經在實踐中使用了設計模式。這就像你終於認識了一個每天都在見面的人一個外表變了的老朋友。這種認知讓我們感覺更親近——現在,我知道如何更好地使用 Go
中的設計模式。感謝你的閱讀!
更多文件
請關注微信公眾號 雲原生CTO [1]
參考資料
參考地址: http://medium.com/geekculture/visitor-pattern-in-kubernetes-d1b58c6d5cd5
- Go 中的構建器模式
- 讓我們使用 Go 實現基本的服務發現
- 雲原生下一步的發展方向是什麼?
- 用更雲原生的方式做診斷|大規模 K8s 叢集診斷利器深度解析
- 多個維度分析k8s多叢集管理工具,到底哪個才真正適合你
- 使用 Kube-capacity CLI 檢視 Kubernetes 資源請求、限制和利用率
- 使用 Go 在 Kubernetes 中構建自己的准入控制器
- 雲原生數倉如何破解大規模叢集的關聯查詢效能問題?
- 雲原生趨勢下的遷移與災備思考
- 2022 年不容錯過的六大雲原生趨勢!
- 使用 Prometheus 監控 Golang 應用程式
- 雲原生時代下的機遇與挑戰 DevOps如何破局
- 如何在雲原生格局中理解Kubernetes合規性和安全框架
- 設計雲原生應用程式的15條基本原則
- 使用 Operator SDK 為 Pod 標籤編寫Controller
- Kubernetes Visitor 模式
- 為什麼雲原生是第二次雲革命
- 構建雲原生安全的六個重要能力
- 擴充套件雲原生策略的步驟有哪些?
- 七個值得關注的開源雲原生工具