Kubernetes Visitor設(shè)計(jì)模式及發(fā)送pod創(chuàng)建請求解析
確立目標(biāo)
- 理解kubectl的核心實(shí)現(xiàn)之一:
Visitor Design Pattern
訪問者模式 - 理解發(fā)送pod創(chuàng)建請求的細(xì)節(jié)
visitor design pattern
在設(shè)計(jì)模式中,訪問者模式的定義為:
允許一個(gè)或者多個(gè)操作應(yīng)用到對象上,解耦操作和對象本身
那么,對一個(gè)程序來說,具體的表現(xiàn)就是:
- 表面:某個(gè)對象執(zhí)行了一個(gè)方法
- 內(nèi)部:對象內(nèi)部調(diào)用了多個(gè)方法,最后統(tǒng)一返回結(jié)果
舉個(gè)例子,
- 表面:調(diào)用一個(gè)查詢訂單的接口
- 內(nèi)部:先從緩存中查詢,沒查到再去熱點(diǎn)數(shù)據(jù)庫查詢,還沒查到則去歸檔數(shù)據(jù)庫里查詢
Visitor
我們來看看kubeadm中的訪問者模式的定義:
// Visitor 即為訪問者這個(gè)對象 type Visitor interface { Visit(VisitorFunc) error } // VisitorFunc對應(yīng)這個(gè)對象的方法,也就是定義中的“操作” type VisitorFunc func(*Info, error) error
基本的數(shù)據(jù)結(jié)構(gòu)很簡單,但從當(dāng)前的數(shù)據(jù)結(jié)構(gòu)來看,有兩個(gè)問題:
- 單個(gè)操作 可以直接調(diào)用
Visit
方法,那多個(gè)操作如何實(shí)現(xiàn)呢? - 在應(yīng)用多個(gè)操作時(shí),如果出現(xiàn)了error,該退出還是繼續(xù)應(yīng)用下一個(gè)操作呢?
Chained
以下內(nèi)容在staging/src/k8s.io/cli-runtime/pkg/resource
VisitorList和EagerVisitorList是將多個(gè)對象聚合為一個(gè)對象
DecoratedVisitor和ContinueOnErrorVisitor是將多個(gè)方法聚合為一個(gè)方法
FlattenListVisitor和FilteredVisitor是將對象抽象為多個(gè)底層對象,逐個(gè)調(diào)用方法
VisitorList
封裝多個(gè)Visitor為一個(gè),出現(xiàn)錯(cuò)誤就立刻中止并返回
// VisitorList定義為[]Visitor,又實(shí)現(xiàn)了Visit方法,也就是將多個(gè)[]Visitor封裝為一個(gè)Visitor type VisitorList []Visitor // 發(fā)生error就立刻返回,不繼續(xù)遍歷 func (l VisitorList) Visit(fn VisitorFunc) error { for i := range l { if err := l[i].Visit(fn); err != nil { return err } } return nil }
EagerVisitorList
封裝多個(gè)Visitor為一個(gè),出現(xiàn)錯(cuò)誤暫存下來,全部遍歷完再聚合所有的錯(cuò)誤并返回
// EagerVisitorList 也是將多個(gè)[]Visitor封裝為一個(gè)Visitor type EagerVisitorList []Visitor // 返回的錯(cuò)誤暫存到[]error中,統(tǒng)一聚合 func (l EagerVisitorList) Visit(fn VisitorFunc) error { errs := []error(nil) for i := range l { if err := l[i].Visit(func(info *Info, err error) error { if err != nil { errs = append(errs, err) return nil } if err := fn(info, nil); err != nil { errs = append(errs, err) } return nil }); err != nil { errs = append(errs, err) } } return utilerrors.NewAggregate(errs) }
DecoratedVisitor
這里借鑒了裝飾器的設(shè)計(jì)模式,將一個(gè)Visitor調(diào)用多個(gè)VisitorFunc方法,封裝為調(diào)用一個(gè)VisitorFunc
// 裝飾器Visitor type DecoratedVisitor struct { visitor Visitor decorators []VisitorFunc } // visitor遍歷調(diào)用decorators中所有函數(shù),有失敗立即返回 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) }) }
ContinueOnErrorVisitor
// 報(bào)錯(cuò)依舊繼續(xù) type ContinueOnErrorVisitor struct { Visitor } // 報(bào)錯(cuò)不立即返回,聚合所有錯(cuò)誤后返回 func (v ContinueOnErrorVisitor) Visit(fn VisitorFunc) error { errs := []error{} err := v.Visitor.Visit(func(info *Info, err error) error { if err != nil { errs = append(errs, err) return nil } if err := fn(info, nil); err != nil { errs = append(errs, err) } return nil }) if err != nil { errs = append(errs, err) } if len(errs) == 1 { return errs[0] } return utilerrors.NewAggregate(errs) }
FlattenListVisitor
將runtime.ObjectTyper解析成多個(gè)runtime.Object,再轉(zhuǎn)換為多個(gè)Info,逐個(gè)調(diào)用VisitorFunc
type FlattenListVisitor struct { visitor Visitor typer runtime.ObjectTyper mapper *mapper }
FilteredVisitor
對Info資源的檢驗(yàn)
// 過濾的Info type FilteredVisitor struct { visitor Visitor filters []FilterFunc } func (v FilteredVisitor) Visit(fn VisitorFunc) error { return v.visitor.Visit(func(info *Info, err error) error { if err != nil { return err } for _, filter := range v.filters { // 檢驗(yàn)Info是否滿足條件,出錯(cuò)則退出 ok, err := filter(info, nil) if err != nil { return err } if !ok { return nil } } return fn(info, nil) }) }
Implements
StreamVisitor
最基礎(chǔ)的Visitor
type StreamVisitor struct { // 讀取信息的來源,實(shí)現(xiàn)了Read這個(gè)接口,這個(gè)"流式"的概念,包括了常見的HTTP、文件、標(biāo)準(zhǔn)輸入等各類輸入 io.Reader *mapper Source string Schema ContentValidator }
FileVisitor
文件的訪問,包括標(biāo)準(zhǔn)輸入,底層調(diào)用StreamVisitor來訪問
type FileVisitor struct { // 表示文件路徑或者STDIN Path string *StreamVisitor }
URLVisitor
HTTP用GET方法獲取數(shù)據(jù),底層也是復(fù)用StreamVisitor
type URLVisitor struct { URL *url.URL *StreamVisitor // 提供錯(cuò)誤重試次數(shù) HttpAttemptCount int }
KustomizeVisitor
自定義的Visitor,針對自定義的文件系統(tǒng),Customize 定制,是將C轉(zhuǎn)成了K
type KustomizeVisitor struct { Path string *StreamVisitor }
發(fā)送創(chuàng)建Pod請求的實(shí)現(xiàn)細(xì)節(jié)
kubectl是怎么向kube-apiserver發(fā)送請求的呢?
send request
// 在RunCreate函數(shù)中,關(guān)鍵的發(fā)送函數(shù) obj, err := resource. NewHelper(info.Client, info.Mapping). DryRun(o.DryRunStrategy == cmdutil.DryRunServer). WithFieldManager(o.fieldManager). Create(info.Namespace, true, info.Object) // 進(jìn)入create函數(shù),查看到 m.createResource(m.RESTClient, m.Resource, namespace, obj, options) // 對應(yīng)的實(shí)現(xiàn)為 func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) { return c.Post(). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(resource). VersionedParams(options, metav1.ParameterCodec). Body(obj). Do(context.TODO()). Get() } /* 到這里,我們發(fā)現(xiàn)了2個(gè)關(guān)鍵性的定義: 1. RESTClient 與kube-apiserver交互的RESTful風(fēng)格的客戶端 這個(gè)RESTClient是來自于Builder時(shí)的傳入,生成的Result,底層是一個(gè)NewClientWithOptions生成的 2. runtime.Object 資源對象的抽象,包括Pod/Deployment/Service等各類資源 3. 我們是傳入的文件,是FileVisitor來執(zhí)行的,底層Builder.mapper調(diào)用Decode來生成obj(Unstructured()) */
RESTful Client
我們先來看看,與kube-apiserver交互的Client是怎么創(chuàng)建的
// 從傳入?yún)?shù)來看,數(shù)據(jù)來源于Info這個(gè)結(jié)構(gòu) r.Visit(func(info *resource.Info, err error) error{}) // 而info來源于前面的Builder,前面部分都是將Builder參數(shù)化,核心的生成為Do函數(shù) r := f.NewBuilder(). Unstructured(). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). LabelSelectorParam(o.Selector). Flatten(). Do() // 大致看一下這些函數(shù),我們可以在Unstructured()中看到getClient函數(shù),其實(shí)這就是我們要找的函數(shù) func (b *Builder) getClient(gv schema.GroupVersion) (RESTClient, error) // 從返回值來看,client包括默認(rèn)的REST client和配置選項(xiàng) NewClientWithOptions(client, b.requestTransforms...) // 這個(gè)Client會(huì)在kubernetes項(xiàng)目中大量出現(xiàn),它是與kube-apiserver交互的核心組件,以后再深入。
Object
Object
這個(gè)對象是怎么獲取到的呢?因?yàn)槲覀兊臄?shù)據(jù)源是來自文件的,那么我們最直觀的想法就是FileVisitor
func (v *FileVisitor) Visit(fn VisitorFunc) error { // 省略讀取這塊的代碼,底層調(diào)用的是StreamVisitor的邏輯 return v.StreamVisitor.Visit(fn) } func (v *StreamVisitor) Visit(fn VisitorFunc) error { d := yaml.NewYAMLOrJSONDecoder(v.Reader, 4096) for { // 這里就是返回info的地方 info, err := v.infoForData(ext.Raw, v.Source) } } // 再往下一層看,來到mapper層,也就是kubernetes的資源對象映射關(guān)系 func (m *mapper) infoForData(data []byte, source string) (*Info, error){ // 這里就是我們返回Object的地方,其中GVK是Group/Version/Kind的縮寫,后續(xù)我們會(huì)涉及 obj, gvk, err := m.decoder.Decode(data, nil, nil) }
這時(shí),我們想回頭去看,這個(gè)mapper是在什么時(shí)候被定義的?
// 在Builder初始化中,我們就找到了 func (b *Builder) Unstructured() *Builder { b.mapper = &mapper{ localFn: b.isLocal, restMapperFn: b.restMapperFn, clientFn: b.getClient, // 我們查找資源用到的是這個(gè)decoder decoder: &metadataValidatingDecoder{unstructured.UnstructuredJSONScheme}, } return b } // 逐層往下找,對應(yīng)的Decode方法的實(shí)現(xiàn),就是對應(yīng)的數(shù)據(jù)解析成data: func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) { // 細(xì)節(jié)暫時(shí)忽略 }
Post
了解了REST Client
和Object
的大致產(chǎn)生邏輯后,我們再回過頭來看發(fā)送的方法
// RESTful接口風(fēng)格中,POST請求對應(yīng)的就是CREATE方法 c.Post(). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(resource). VersionedParams(options, metav1.ParameterCodec). Body(obj). Do(context.TODO()). Get() // Do方法,發(fā)送請求 err := r.request(ctx, func(req *http.Request, resp *http.Response) { result = r.transformResponse(resp, req) }) // Get方法,獲取請求的返回結(jié)果,用來打印狀態(tài) switch t := out.(type) { case *metav1.Status: if t.Status != metav1.StatusSuccess { return nil, errors.FromObject(t) } }
站在前人的肩膀上,向前輩致敬,Respect!
Summary
通過Visitor的設(shè)計(jì)模式,從傳入的參數(shù)中解析出內(nèi)容,然后在Factory進(jìn)行NewBuilder的時(shí)候進(jìn)行配置實(shí)現(xiàn)RESTClient,mapper,obj的生成,Do()拿到Result,組裝好POST請求發(fā)送到ApiServer。
到這里我們對kubectl的功能有了初步的了解,以下是關(guān)鍵內(nèi)容所在:
命令行采用了cobra
庫,主要支持7個(gè)大類的命令;
掌握Visitor設(shè)計(jì)模式,這個(gè)是kubectl實(shí)現(xiàn)各類資源對象的解析和校驗(yàn)的核心;
初步了解RESTClient
和Object
這兩個(gè)對象,它們是貫穿kubernetes的核心概念;
調(diào)用邏輯
- cobra匹配子命令
- 用Visitor模式構(gòu)建Builder
- 用RESTClient將Object發(fā)送到kube-apiserver
以上就是Kubernetes Visitor設(shè)計(jì)模式及發(fā)送pod創(chuàng)建請求解析的詳細(xì)內(nèi)容,更多關(guān)于Kubernetes Visitor發(fā)送pod請求的資料請關(guān)注腳本之家其它相關(guān)文章!
- kubernetes數(shù)據(jù)持久化PV?PVC深入分析詳解
- kubernetes數(shù)據(jù)持久化StorageClass動(dòng)態(tài)供給實(shí)現(xiàn)詳解
- Kubernetes?controller?manager運(yùn)行機(jī)制源碼解析
- Kubernetes Informer數(shù)據(jù)存儲(chǔ)Index與Pod分配流程解析
- Kubernetes scheduler啟動(dòng)監(jiān)控資源變化解析
- Kubernetes ApiServer三大server權(quán)限與數(shù)據(jù)存儲(chǔ)解析
- Kubernetes kubectl中Pod創(chuàng)建流程源碼解析
- 一文詳解基于Kubescape進(jìn)行Kubernetes安全加固
相關(guān)文章
Kubernetes(K8S)入門基礎(chǔ)內(nèi)容介紹
這篇文章介紹了Kubernetes(K8S)的入門基礎(chǔ)內(nèi)容,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03KubeSphere接入外部Elasticsearch實(shí)戰(zhàn)示例
這篇文章主要為大家介紹了KubeSphere接入外部Elasticsearch實(shí)戰(zhàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Kubernetes關(guān)鍵組件與結(jié)構(gòu)組成介紹
這篇文章介紹了Kubernetes的關(guān)鍵組件與結(jié)構(gòu)組成,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03Kubernetes scheduler啟動(dòng)監(jiān)控資源變化解析
這篇文章主要為大家介紹了Kubernetes scheduler啟動(dòng)監(jiān)控資源變化解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Kubernetes(K8S)容器集群管理環(huán)境完整部署詳細(xì)教程-中篇
本系列文章主要介紹了Kubernetes(K8S)容器集群管理環(huán)境完整部署的詳細(xì)教程,分為上中下三篇文章,此為中篇,需要的朋友可以參考下2022-01-01kubernetes數(shù)據(jù)持久化StorageClass動(dòng)態(tài)供給實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了kubernetes數(shù)據(jù)持久化StorageClass動(dòng)態(tài)供給實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Kubernetes Visitor設(shè)計(jì)模式及發(fā)送pod創(chuàng)建請求解析
這篇文章主要為大家介紹了Kubernetes Visitor設(shè)計(jì)模式及發(fā)送pod創(chuàng)建請求解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11