<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Kubectl 源碼分析

    共 47097字,需瀏覽 95分鐘

     ·

    2021-03-23 21:14

    kubectl子命令很多,估計(jì)很少有人可以全部記住,作為開發(fā),不知道大家有沒(méi)有試著思考下,怎么樣才可以比較靈活的實(shí)現(xiàn)這么豐富的功能,此外,命令行工具的用戶友好也是很重要的。

    1 Kubectl創(chuàng)建

    一起看看Kubectl的啟動(dòng):

    // kubernetes\cmd\kubectl\kubectl.go
    func main() {
        rand.Seed(time.Now().UnixNano())

        command := cmd.NewDefaultKubectlCommand()

        if err := command.Execute(); err != nil {
            os.Exit(1)
        }
    }
    COPY

    去掉了一些無(wú)關(guān)代碼,主要操作就是兩個(gè):

    1. NewDefaultKubectlCommand(): 創(chuàng)建kubectl命令
    2. command.Execute() : 執(zhí)行command命令COPY

    重點(diǎn)看下創(chuàng)建的過(guò)程:

    // 使用默認(rèn)的初始化參數(shù)調(diào)用NewDefaultKubectlCommandWithArgs創(chuàng)建kubectl
    func NewDefaultKubectlCommand() *cobra.Command {
        return NewDefaultKubectlCommandWithArgs(NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes), os.Args, os.Stdin, os.Stdout, os.Stderr)
    }

    // 字面意思
    func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string, in io.Reader, out, errout io.Writer) *cobra.Command {
        // 實(shí)際創(chuàng)建kubectl
        cmd := NewKubectlCommand(in, out, errout)

        // 檢測(cè)是否存在插件處理器
        if pluginHandler == nil {
            return cmd
        }

        if len(args) &gt; 1 {
            cmdPathPieces := args[1:]

            // 根據(jù)傳入的參數(shù)判斷是否存在這樣的子命令,如果不存在,則判斷是否存在對(duì)應(yīng)的插件可以調(diào)用,有的話就調(diào)用插件,調(diào)用完隨即推出。
            if _, _, err := cmd.Find(cmdPathPieces); err != nil {
                if err := HandlePluginCommand(pluginHandler, cmdPathPieces); err != nil {
                    fmt.Fprintf(errout, &quot;Error: %v\n&quot;, err)
                    os.Exit(1)
                }
            }
        }

        return cmd
    }COPY

    NewDefaultKubectlCommandWithArgs方法里只有第一步是創(chuàng)建命令的,后續(xù)都在處理插件,插件的處理后續(xù)單獨(dú)看,繼續(xù)往調(diào)用棧里走,看內(nèi)部的創(chuàng)建邏輯:

    // NewKubectlCommand 創(chuàng)建kubcel命令,并添加相應(yīng)的子命令
    func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
        warningHandler := rest.NewWarningWriter(err, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(err)})
        warningsAsErrors := false
        // 所有子命令的父命令
        // 這里可以看到kubectl默認(rèn)的說(shuō)明
        cmds := &amp;cobra.Command{
            Use:   &quot;kubectl&quot;,
            Short: i18n.T(&quot;kubectl controls the Kubernetes cluster manager&quot;),
            Long: templates.LongDesc(&#x60;
          kubectl controls the Kubernetes cluster manager.

          Find more information at:
                https://kubernetes.io/docs/reference/kubectl/overview/`),
            // 默認(rèn)的方法是打印出help信息
            Run: runHelp,
            // 調(diào)用時(shí),各個(gè)階段的 Hook 方法
            // 命令運(yùn)行前回調(diào)
            PersistentPreRunE: func(*cobra.Command, []string) error {
                rest.SetDefaultWarningHandler(warningHandler)
                return initProfiling()
            },
            // 運(yùn)行后回調(diào)
            PersistentPostRunE: func(*cobra.Command, []string) error {
                if err := flushProfiling(); err != nil {
                    return err
                }
                if warningsAsErrors {
                    count := warningHandler.WarningCount()
                    switch count {
                    case 0:
                        // no warnings
                    case 1:
                        return fmt.Errorf(&quot;%d warning received&quot;, count)
                    default:
                        return fmt.Errorf(&quot;%d warnings received&quot;, count)
                    }
                }
                return nil
            },
            BashCompletionFunction: bashCompletionFunc,
        }

        flags := cmds.PersistentFlags()
        flags.SetNormalizeFunc(cliflag.WarnWordSepNormalizeFunc)

        // 參數(shù)的歸一化方法,主要作用是將所有 &quot;_&quot; 轉(zhuǎn)化成 &quot;-&quot;
        flags.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)

        // 為kubectl添加默認(rèn)的性能測(cè)算參數(shù),默認(rèn)是關(guān)閉的
        // 如果在參數(shù)中設(shè)置為開啟,則會(huì)進(jìn)行測(cè)算,具體可見命令調(diào)用前后的回調(diào)
        addProfilingFlags(flags)

        flags.BoolVar(&amp;warningsAsErrors, &quot;warnings-as-errors&quot;, warningsAsErrors, &quot;Treat warnings received from the server as errors and exit with a non-zero exit code&quot;)

        kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
        kubeConfigFlags.AddFlags(flags)
        matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
        matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())

        cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)

        f := cmdutil.NewFactory(matchVersionKubeConfigFlags)

        i18n.LoadTranslations(&quot;kubectl&quot;, nil)

        cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)

        ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}

        // 添加當(dāng)前的子命令組
        groups := templates.CommandGroups{
            {
                Message: &quot;Basic Commands (Beginner):&quot;,
                Commands: []*cobra.Command{
                    create.NewCmdCreate(f, ioStreams),
                    expose.NewCmdExposeService(f, ioStreams),
                    run.NewCmdRun(f, ioStreams),
                    set.NewCmdSet(f, ioStreams),
                },
            },
            {
                Message: &quot;Basic Commands (Intermediate):&quot;,
                Commands: []*cobra.Command{
                    explain.NewCmdExplain(&quot;kubectl&quot;, f, ioStreams),
                    get.NewCmdGet(&quot;kubectl&quot;, f, ioStreams),
                    edit.NewCmdEdit(f, ioStreams),
                    delete.NewCmdDelete(f, ioStreams),
                },
            },
            {
                Message: &quot;Deploy Commands:&quot;,
                Commands: []*cobra.Command{
                    rollout.NewCmdRollout(f, ioStreams),
                    scale.NewCmdScale(f, ioStreams),
                    autoscale.NewCmdAutoscale(f, ioStreams),
                },
            },
            {
                Message: &quot;Cluster Management Commands:&quot;,
                Commands: []*cobra.Command{
                    certificates.NewCmdCertificate(f, ioStreams),
                    clusterinfo.NewCmdClusterInfo(f, ioStreams),
                    top.NewCmdTop(f, ioStreams),
                    drain.NewCmdCordon(f, ioStreams),
                    drain.NewCmdUncordon(f, ioStreams),
                    drain.NewCmdDrain(f, ioStreams),
                    taint.NewCmdTaint(f, ioStreams),
                },
            },
            {
                Message: &quot;Troubleshooting and Debugging Commands:&quot;,
                Commands: []*cobra.Command{
                    describe.NewCmdDescribe(&quot;kubectl&quot;, f, ioStreams),
                    logs.NewCmdLogs(f, ioStreams),
                    attach.NewCmdAttach(f, ioStreams),
                    cmdexec.NewCmdExec(f, ioStreams),
                    portforward.NewCmdPortForward(f, ioStreams),
                    proxy.NewCmdProxy(f, ioStreams),
                    cp.NewCmdCp(f, ioStreams),
                    auth.NewCmdAuth(f, ioStreams),
                    debug.NewCmdDebug(f, ioStreams),
                },
            },
            {
                Message: &quot;Advanced Commands:&quot;,
                Commands: []*cobra.Command{
                    diff.NewCmdDiff(f, ioStreams),
                    apply.NewCmdApply(&quot;kubectl&quot;, f, ioStreams),
                    patch.NewCmdPatch(f, ioStreams),
                    replace.NewCmdReplace(f, ioStreams),
                    wait.NewCmdWait(f, ioStreams),
                    kustomize.NewCmdKustomize(ioStreams),
                },
            },
            {
                Message: &quot;Settings Commands:&quot;,
                Commands: []*cobra.Command{
                    label.NewCmdLabel(f, ioStreams),
                    annotate.NewCmdAnnotate(&quot;kubectl&quot;, f, ioStreams),
                    completion.NewCmdCompletion(ioStreams.Out, &quot;&quot;),
                },
            },
        }
        groups.Add(cmds)

        filters := []string{&quot;options&quot;}

        alpha := NewCmdAlpha(f, ioStreams)
        if !alpha.HasSubCommands() {
            filters = append(filters, alpha.Name())
        }

        templates.ActsAsRootCommand(cmds, filters, groups...)

        for name, completion := range bashCompletionFlags {
            if cmds.Flag(name) != nil {
                if cmds.Flag(name).Annotations == nil {
                    cmds.Flag(name).Annotations = map[string][]string{}
                }
                cmds.Flag(name).Annotations[cobra.BashCompCustom] = append(
                    cmds.Flag(name).Annotations[cobra.BashCompCustom],
                    completion,
                )
            }
        }

        // 添加一些不在默認(rèn)分組內(nèi)的方法
        cmds.AddCommand(alpha)
        cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams))
        cmds.AddCommand(plugin.NewCmdPlugin(f, ioStreams))
        cmds.AddCommand(version.NewCmdVersion(f, ioStreams))
        cmds.AddCommand(apiresources.NewCmdAPIVersions(f, ioStreams))
        cmds.AddCommand(apiresources.NewCmdAPIResources(f, ioStreams))
        cmds.AddCommand(options.NewCmdOptions(ioStreams.Out))

        return cmds
    }COPY

    kubectl命令的創(chuàng)建過(guò)程就是初始化Kubectl命令,明確命令使用前后的回調(diào)函數(shù),然后將幾組子命令添加到根命令下。

    2 Kubectl執(zhí)行

    kubectl大部分命令的執(zhí)行其實(shí)是交給子命令模塊去做的。Cobra規(guī)定了,子命令與Root命令的基本代碼結(jié)構(gòu),如果要添加子命令,符合一般約束的前提下,在cmd目錄下新建一個(gè)文件即可。

      ? appName/
        ? cmd/
            add.go
            your.go
            commands.go
            here.go
          main.goCOPY

    可以看一下目前Kubectl下的子命令:

    目前kubectl支持的子命令肯定都會(huì)在這里,還記得之前kubectl的構(gòu)造函數(shù)么?K8s的代碼里,將這些子命令分為了幾個(gè)種類:

    種類命令
    Basic Commands (Beginner)create\expose\run\set
    Basic Commands (Intermediate)explain\get\edit\delete
    Deploy Commandsrollout\scale\autoscale
    Cluster Management Commandscertificate\clusterinfo\top\cordon\uncordon\drain\taint
    Troubleshooting and Debugging Commandsdescribe\logs\attach\exec\portforward\proxy\cp\auth\debug
    Advanced Commandsdiff\apply\patch\replace\wait\kustomize
    Settings Commandslabel\annotate\completion
    其他version\options\config\apiversions\apiresources

    里面有一些命令比較常用,有一些我也是第一次知道,比如debug\wait。

    這些子命令都通過(guò)AddCommand方法,添加到Command結(jié)構(gòu)體的成員變量commands中了。commands作為一個(gè)數(shù)組,主要用于存儲(chǔ)程序支持的子命令。

    // kubectl的執(zhí)行邏輯
    func (c *Command) ExecuteC() (cmd *Command, err error) {
        if c.ctx == nil {
            c.ctx = context.Background()
        }

        // 以根命令的形式運(yùn)行
        if c.HasParent() {
            return c.Root().ExecuteC()
        }

        // 窗口檢測(cè)回調(diào)函數(shù),主要作用是檢測(cè)當(dāng)前程序是否是被鼠標(biāo)點(diǎn)擊觸發(fā)的,如果不是命令行模式,報(bào)錯(cuò)然后退出
        if preExecHookFn != nil {
            preExecHookFn(c)
        }

        // 初始化幫助信息,覆蓋默認(rèn)的幫助信息
        c.InitDefaultHelpCmd()

        args := c.args

        // 一些異常case的檢測(cè)
        if c.args == nil &amp;&amp; filepath.Base(os.Args[0]) != &quot;cobra.test&quot; {
            args = os.Args[1:]
        }

        // 命令補(bǔ)全初始化
        c.initCompleteCmd(args)

        var flags []string
        // 這一步很關(guān)鍵
        // 這一步會(huì)明確實(shí)際執(zhí)行的子命令,然后賦值給cmd
        if c.TraverseChildren {
            cmd, flags, err = c.Traverse(args)
        } else {
            cmd, flags, err = c.Find(args)
        }
        if err != nil {
            // 如果沒(méi)有找到對(duì)應(yīng)的子命令,提示出錯(cuò)
            if cmd != nil {
                c = cmd
            }
            if !c.SilenceErrors {
                c.PrintErrln(&quot;Error:&quot;, err.Error())
                c.PrintErrf(&quot;Run &#039;%v --help&#039for usage.\n&quot;, c.CommandPath())
            }
            return c, err
        }

        // 子命令執(zhí)行前的準(zhǔn)備
        cmd.commandCalledAs.called = true
        if cmd.commandCalledAs.name == &quot;&quot; {
            cmd.commandCalledAs.name = cmd.Name()
        }

        // context傳輸
        if cmd.ctx == nil {
            cmd.ctx = c.ctx
        }

        // 執(zhí)行
        err = cmd.execute(flags)
        if err != nil {
            // Always show help if requested, even if SilenceErrors is in
            // effect
            if err == flag.ErrHelp {
                cmd.HelpFunc()(cmd, args)
                return cmd, nil
            }

            // If root command has SilentErrors flagged,
            // all subcommands should respect it
            if !cmd.SilenceErrors &amp;&amp; !c.SilenceErrors {
                c.PrintErrln(&quot;Error:&quot;, err.Error())
            }

            // If root command has SilentUsage flagged,
            // all subcommands should respect it
            if !cmd.SilenceUsage &amp;&amp; !c.SilenceUsage {
                c.Println(cmd.UsageString())
            }
        }
        return cmd, err
    }

    // command的執(zhí)行框架
    // 按照
    // 1. 解析命令行參數(shù)
    // 2. help參數(shù)檢測(cè)
    // 3. 運(yùn)行前檢測(cè)
    // 4. preRun
    // 5. p.PersistentPreRun | p.PersistentPreRunE  p是父命令
    // 6. PreRunE | PreRun
    // 7. RunE | Run
    // 8. PostRunE | PostRun
    // 9. p.PersistentPostRunE | p.PersistentPostRun
    func (c *Command) execute(a []string) (err error) {
        if c == nil {
            return fmt.Errorf(&quot;Called Execute() on a nil Command&quot;)
        }

        if len(c.Deprecated) &gt; 0 {
            c.Printf(&quot;Command %q is deprecated, %s\n&quot;, c.Name(), c.Deprecated)
        }

        // initialize help and version flag at the last point possible to allow for user
        // overriding
        c.InitDefaultHelpFlag()
        c.InitDefaultVersionFlag()

        err = c.ParseFlags(a)
        if err != nil {
            return c.FlagErrorFunc()(c, err)
        }

        // If help is called, regardless of other flags, return we want help.
        // Also say we need help if the command isn&#039;t runnable.
        helpVal, err := c.Flags().GetBool(&quot;help&quot;)
        if err != nil {
            // should be impossible to get here as we always declare a help
            // flag in InitDefaultHelpFlag()
            c.Println(&quot;\&quot;help\&quot; flag declared as non-bool. Please correct your code&quot;)
            return err
        }

        if helpVal {
            return flag.ErrHelp
        }

        // for back-compat, only add version flag behavior if version is defined
        if c.Version != &quot;&quot; {
            versionVal, err := c.Flags().GetBool(&quot;version&quot;)
            if err != nil {
                c.Println(&quot;\&quot;version\&quot; flag declared as non-bool. Please correct your code&quot;)
                return err
            }
            if versionVal {
                err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c)
                if err != nil {
                    c.Println(err)
                }
                return err
            }
        }

        if !c.Runnable() {
            return flag.ErrHelp
        }

        c.preRun()

        argWoFlags := c.Flags().Args()
        if c.DisableFlagParsing {
            argWoFlags = a
        }

        if err := c.ValidateArgs(argWoFlags); err != nil {
            return err
        }

        for p := c; p != nil; p = p.Parent() {
            if p.PersistentPreRunE != nil {
                if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
                    return err
                }
                break
            } else if p.PersistentPreRun != nil {
                p.PersistentPreRun(c, argWoFlags)
                break
            }
        }
        if c.PreRunE != nil {
            if err := c.PreRunE(c, argWoFlags); err != nil {
                return err
            }
        } else if c.PreRun != nil {
            c.PreRun(c, argWoFlags)
        }

        if err := c.validateRequiredFlags(); err != nil {
            return err
        }
        if c.RunE != nil {
            if err := c.RunE(c, argWoFlags); err != nil {
                return err
            }
        } else {
            c.Run(c, argWoFlags)
        }
        if c.PostRunE != nil {
            if err := c.PostRunE(c, argWoFlags); err != nil {
                return err
            }
        } else if c.PostRun != nil {
            c.PostRun(c, argWoFlags)
        }
        for p := c; p != nil; p = p.Parent() {
            if p.PersistentPostRunE != nil {
                if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
                    return err
                }
                break
            } else if p.PersistentPostRun != nil {
                p.PersistentPostRun(c, argWoFlags)
                break
            }
        }

        return nil
    }COPY

    就是這么簡(jiǎn)單,kubectl的初始化和子命令調(diào)用看起來(lái)沒(méi)有什么太大的復(fù)雜性。

    3 子命令結(jié)構(gòu)設(shè)計(jì)

    Kubectl的子命令,主要基于三種設(shè)計(jì)模式:建造者模式、訪問(wèn)者模式、裝飾器模式。

    建造者模式(Builder Pattern)比較簡(jiǎn)單,就是使用多個(gè)簡(jiǎn)單的對(duì)象一步一步構(gòu)建成一個(gè)復(fù)雜的對(duì)象。

    訪問(wèn)者模式(Visitor Pattern)是一種行為型設(shè)計(jì)模式,可以將算法和操作對(duì)象的結(jié)構(gòu)進(jìn)行分離,遵循開放、封閉原則的一種方法。我們需要重點(diǎn)看下這個(gè)模式是如何工作的。

    3.1 訪問(wèn)者模式示例

    寫一個(gè)簡(jiǎn)單的訪問(wèn)者模式的應(yīng)用,主要元素有訪問(wèn)對(duì)象、訪問(wèn)者、調(diào)用方

    type Visitor func(person Person)

    // 基類
    // 被訪問(wèn)的對(duì)象,通過(guò)accept方法接受訪問(wèn)
    type Person interface {
        accept(visitor Visitor)
    }

    // 存儲(chǔ)學(xué)生信息的類型
    // 實(shí)現(xiàn)了Person接口
    type Student struct {
        Name  string &#x60;json:&quot;name&quot;&#x60;
        Age   int    &#x60;json:&quot;age&quot;&#x60;
        Score int    &#x60;json:&quot;score&quot;&#x60;
    }

    func (s Student) accept(visitor Visitor) {
        visitor(s)
    }

    // 存儲(chǔ)教師信息
    type Teacher struct {
        Name   string &#x60;json:&quot;name&quot;&#x60;
        Age    int    &#x60;json:&quot;age&quot;&#x60;
        Course string &#x60;json:&quot;course&quot;&#x60;
    }

    func (t Teacher) accept(visitor Visitor) {
        visitor(t)
    }COPY

    定義兩個(gè)簡(jiǎn)單的訪問(wèn)器

    // 導(dǎo)出json格式數(shù)據(jù)的訪問(wèn)器
    func JsonVisitor(person Person) {
        bytes, err := json.Marshal(person)
        if err != nil {
            panic(err)
        }
        fmt.Println(string(bytes))
    }

    // 導(dǎo)出yaml格式信息的訪問(wèn)器
    func YamlVisitor(person Person) {
        bytes, err := yaml.Marshal(person)
        if err != nil {
            panic(err)
        }
        fmt.Println(string(bytes))
    }COPY

    調(diào)用一下

    func main() {
        s := Student{Age: 10, Name: &quot;小明&quot;, Score: 90}
        t := Teacher{Name: &quot;李&quot;, Age: 35, Course: &quot;數(shù)學(xué)&quot;}
        persons := []Person{s, t}

        for _, person := range persons {
            person.accept(JsonVisitor)
            person.accept(YamlVisitor)
        }
    }COPY

    上面是一個(gè)簡(jiǎn)單的示例,看起來(lái)有一點(diǎn)像策略模式。兩者沒(méi)有本質(zhì)區(qū)別,都是針對(duì)多態(tài)的一種處理。不過(guò)訪問(wèn)者模式更側(cè)重對(duì)于被訪問(wèn)者的狀態(tài)的修改,而策略模式更側(cè)重的是處理邏輯的擴(kuò)展。實(shí)際用的時(shí)候不用考慮那么多,怎么方便怎么來(lái)就好了。上面的例子比較簡(jiǎn)單,復(fù)雜的情況下,一個(gè)結(jié)構(gòu)體里會(huì)有很多不同的狀態(tài),每個(gè)訪問(wèn)器負(fù)責(zé)修改一部分狀態(tài)。kubectl中就是這樣的場(chǎng)景。

    3.2 Kubectl對(duì)于訪問(wèn)者模式的應(yīng)用

    在k8s中,存在各種各樣的資源類型,每個(gè)類型都包含復(fù)雜的狀態(tài)信息,有些是公用的,有的是獨(dú)有的。kubectl的子命令,需要對(duì)不同的資源類型做出處理, 處理流程上:1. 讀取命令行參數(shù)、或者讀取指定文件、或者讀取url,構(gòu)建命令 2. 調(diào)用k8s的client,向API Server發(fā)起請(qǐng)求 3. 完成處理

    處理邏輯上,可以抽象為:1. 獲取參數(shù) 2. Builder模式構(gòu)建一個(gè)資源的集合 3. 使用visitor模式處理這些資源狀態(tài)的集合,包括本地資源的修改操作、向Api Server的請(qǐng)求操作 3. 完成處理

    這是一個(gè)比較抽象的過(guò)程。下面可以具體看一下實(shí)際實(shí)現(xiàn)的代碼

    // Info 封裝了一些client調(diào)用時(shí)所需要的基本信息
    type Info struct {
        // 只有需要遠(yuǎn)端調(diào)用的時(shí)候才會(huì)初始化client和mapping
        Client RESTClient
        Mapping *meta.RESTMapping

        // 指定namespace的時(shí)候才會(huì)設(shè)置這個(gè)參數(shù)
        Namespace string
        Name      string

        // 可選參數(shù) url或者文件路徑
        Source string

        Object runtime.Object
        ResourceVersion string
    }

    // 訪問(wèn)器基類,用于修改資源類型的操作
    type Visitor interface {
        Visit(VisitorFunc) error
    }

    type VisitorFunc func(*Info, error) errorCOPY

    可以看到,Visitor類型里包含方法Visit,Visit的參數(shù)是一個(gè)VisitorFunc。

    為了方便理解Visitor的使用,先舉個(gè)例子,我們定義幾個(gè)不同類型的Visitor

    // name visitor
    // 假設(shè)這個(gè)visitor主要用于訪問(wèn) Info 結(jié)構(gòu)中的 Name 和 NameSpace 成員
    type NameVisitor struct {
      visitor Visitor
    }

    func (v NameVisitor) Visit(fn VisitorFunc) error {
      return v.visitor.Visit(func(info *Info, err error) error {
        fmt.Println(&quot;NameVisitor() before call function&quot;)
        err = fn(info, err)
        if err == nil {
          fmt.Printf(&quot;==&gt; Name=%s, NameSpace=%s\n&quot;, info.Name, info.Namespace)
        }
        fmt.Println(&quot;NameVisitor() after call function&quot;)
        return err
      })
    }

    // Other Visitor
    // 這個(gè)Visitor主要用來(lái)訪問(wèn) Info 結(jié)構(gòu)中的 OtherThings 成員
    type OtherThingsVisitor struct {
      visitor Visitor
    }

    func (v OtherThingsVisitor) Visit(fn VisitorFunc) error {
      return v.visitor.Visit(func(info *Info, err error) error {
        fmt.Println(&quot;OtherThingsVisitor() before call function&quot;)
        err = fn(info, err)
        if err == nil {
          fmt.Printf(&quot;==&gt; OtherThings=%s\n&quot;, info.OtherThings)
        }
        fmt.Println(&quot;OtherThingsVisitor() after call function&quot;)
        return err
      })
    }

    // Log Visitor
    type LogVisitor struct {
      visitor Visitor
    }
    func (v LogVisitor) Visit(fn VisitorFunc) error {
      return v.visitor.Visit(func(info *Info, err error) error {
        fmt.Println(&quot;LogVisitor() before call function&quot;)
        err = fn(info, err)
        fmt.Println(&quot;LogVisitor() after call function&quot;)
        return err
      })
    }
    // 調(diào)用邏輯
    func main() {
      info := Info{}
      var v Visitor = &amp;info
      v = LogVisitor{v}
      v = NameVisitor{v}
      v = OtherThingsVisitor{v}

      loadFile := func(info *Info, err error) error {
        info.Name = &quot;Hao Chen&quot;
        info.Namespace = &quot;MegaEase&quot;
        info.OtherThings = &quot;We are running as remote team.&quot;
        return nil
      }
      v.Visit(loadFile)
    }
    COPY

    上面的代碼,每個(gè)visitor里

    • 有一個(gè) Visitor 接口成員,這里意味著多態(tài)。
    • 在實(shí)現(xiàn) Visit() 方法時(shí),其調(diào)用了自己結(jié)構(gòu)體內(nèi)的那個(gè) VisitorVisitor() 方法,這其實(shí)是一種修飾器的模式,用另一個(gè)Visitor修飾了自己

    調(diào)用后,輸出如下信息

    LogVisitor() before call function
    NameVisitor() before call function
    OtherThingsVisitor() before call function
    ==&gt; OtherThings=We are running as remote team.
    OtherThingsVisitor() after call function
    ==&gt; Name=Hao Chen, NameSpace=MegaEase
    NameVisitor() after call function
    LogVisitor() after call functionCOPY

    顯而易見,上面的代碼有以下幾種功效:

    • 解耦了數(shù)據(jù)和程序。
    • 使用了修飾器模式
    • 還做出來(lái)pipeline的模式

    搞清楚上述邏輯后,在回過(guò)來(lái)看k8s的實(shí)現(xiàn)就會(huì)一目了然

    // Decorate 就是裝飾的意思,顯而易見,裝飾過(guò)的訪問(wèn)器
    type DecoratedVisitor struct {
        visitor    Visitor
        // 一個(gè)裝飾器集合
        decorators []VisitorFunc
    }

    // Visit implements Visitor
    // 1. 下潛一層,調(diào)用自己的Visitor內(nèi)的訪問(wèn)器方法
    // 2. 調(diào)用自己 所有裝飾者方法,即自身的訪問(wèn)器
    // 3. 最后再調(diào)用傳入的fn
    // 達(dá)到一種嵌套調(diào)用、鏈?zhǔn)秸{(diào)用的效果
    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)
        })
    }COPY

    上面的代碼并不復(fù)雜,

    • 用一個(gè) DecoratedVisitor 的結(jié)構(gòu)來(lái)存放所有的VistorFunc函數(shù)
    • NewDecoratedVisitor 可以把所有的 VisitorFunc轉(zhuǎn)給它,構(gòu)造 DecoratedVisitor 對(duì)象。
    • DecoratedVisitor實(shí)現(xiàn)了 Visit() 方法,里面就是來(lái)做一個(gè)for-loop,順著調(diào)用所有的 VisitorFunc

    用一個(gè)非常巧妙的方法,把裝飾器模式和訪問(wèn)者模式結(jié)合在了一起,又把操作與數(shù)據(jù)解耦、操作與操作解耦,嘆為觀止!

    那怎么調(diào)用呢?

    info := Info{}
    var v Visitor = &amp;info
    v = NewDecoratedVisitor(v, NameVisitor, OtherVisitor)

    v.Visit(LoadFile)COPY

    舉個(gè)例子,kubectl的apply方法的實(shí)現(xiàn):

    // 路徑 vendor/k8s.io/kubectl/pkg/cmd/apply/apply.go
    func (o *ApplyOptions) GetObjects() ([]*resource.Info, error) {
        var err error = nil
        if !o.objectsCached {
            // 通過(guò)builder模式,逐步構(gòu)建一個(gè)完整的資源對(duì)象
            r := o.Builder.
                Unstructured().
                Schema(o.Validator).
                ContinueOnError().
                NamespaceParam(o.Namespace).DefaultNamespace().
                FilenameParam(o.EnforceNamespace, &amp;o.DeleteOptions.FilenameOptions).
                LabelSelectorParam(o.Selector).
                Flatten().
                Do()
            o.objects, err = r.Infos()
            o.objectsCached = true
        }
        return o.objects, err
    }

    // 路徑 k8s.io/cli-runtime/pkg/resource/builder.go
    func (b *Builder) Do() *Result {
        r := b.visitorResult()
        r.mapper = b.Mapper()
        if r.err != nil {
            return r
        }
        if b.flatten {
            r.visitor = NewFlattenListVisitor(r.visitor, b.objectTyper, b.mapper)
        }
        // 訪問(wèn)器方法集合
        helpers := []VisitorFunc{}
        if b.defaultNamespace {
            // 設(shè)置namespace的訪問(wèn)器 SetNamespace
            helpers = append(helpers, SetNamespace(b.namespace))
        }
        if b.requireNamespace {
            // 需要namespace的提示操作 RequireNamespace
            helpers = append(helpers, RequireNamespace(b.namespace))
        }
        // 過(guò)濾namespace的訪問(wèn)器 FilterNamespace
        helpers = append(helpers, FilterNamespace)
        if b.requireObject {
            // 另一個(gè)訪問(wèn)器
            helpers = append(helpers, RetrieveLazy)
        }
        if b.continueOnError {
            // 構(gòu)造被裝飾的訪問(wèn)者
            r.visitor = NewDecoratedVisitor(ContinueOnErrorVisitor{r.visitor}, helpers...)
        } else {
            r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
        }
        return r
    }COPY

    kubectl的子命令基本都是這樣的工作模式。具體的業(yè)務(wù)邏輯就需要看實(shí)際的處理需求了。

    原文鏈接:https://jeffdingzone.com/2021/02/k8s%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%903-kubectl%e5%ae%9e%e7%8e%b0%e5%88%86%e6%9e%90/


    K8S 進(jìn)階訓(xùn)練營(yíng)


     點(diǎn)擊屏末  | 即刻學(xué)習(xí)
    瀏覽 73
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    特黄AV| 亚洲区成人777777精品 | 天天草天天干天天日 | 男人天堂2017手机在线 | 黄片在线看。 |