Kubectl 源碼分析
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) > 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, "Error: %v\n", 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 := &cobra.Command{
Use: "kubectl",
Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
Long: templates.LongDesc(`
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("%d warning received", count)
default:
return fmt.Errorf("%d warnings received", count)
}
}
return nil
},
BashCompletionFunction: bashCompletionFunc,
}
flags := cmds.PersistentFlags()
flags.SetNormalizeFunc(cliflag.WarnWordSepNormalizeFunc)
// 參數(shù)的歸一化方法,主要作用是將所有 "_" 轉(zhuǎn)化成 "-"
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(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")
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("kubectl", nil)
cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)
ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
// 添加當(dāng)前的子命令組
groups := templates.CommandGroups{
{
Message: "Basic Commands (Beginner):",
Commands: []*cobra.Command{
create.NewCmdCreate(f, ioStreams),
expose.NewCmdExposeService(f, ioStreams),
run.NewCmdRun(f, ioStreams),
set.NewCmdSet(f, ioStreams),
},
},
{
Message: "Basic Commands (Intermediate):",
Commands: []*cobra.Command{
explain.NewCmdExplain("kubectl", f, ioStreams),
get.NewCmdGet("kubectl", f, ioStreams),
edit.NewCmdEdit(f, ioStreams),
delete.NewCmdDelete(f, ioStreams),
},
},
{
Message: "Deploy Commands:",
Commands: []*cobra.Command{
rollout.NewCmdRollout(f, ioStreams),
scale.NewCmdScale(f, ioStreams),
autoscale.NewCmdAutoscale(f, ioStreams),
},
},
{
Message: "Cluster Management Commands:",
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: "Troubleshooting and Debugging Commands:",
Commands: []*cobra.Command{
describe.NewCmdDescribe("kubectl", 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: "Advanced Commands:",
Commands: []*cobra.Command{
diff.NewCmdDiff(f, ioStreams),
apply.NewCmdApply("kubectl", f, ioStreams),
patch.NewCmdPatch(f, ioStreams),
replace.NewCmdReplace(f, ioStreams),
wait.NewCmdWait(f, ioStreams),
kustomize.NewCmdKustomize(ioStreams),
},
},
{
Message: "Settings Commands:",
Commands: []*cobra.Command{
label.NewCmdLabel(f, ioStreams),
annotate.NewCmdAnnotate("kubectl", f, ioStreams),
completion.NewCmdCompletion(ioStreams.Out, ""),
},
},
}
groups.Add(cmds)
filters := []string{"options"}
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 Commands | rollout\scale\autoscale |
| Cluster Management Commands | certificate\clusterinfo\top\cordon\uncordon\drain\taint |
| Troubleshooting and Debugging Commands | describe\logs\attach\exec\portforward\proxy\cp\auth\debug |
| Advanced Commands | diff\apply\patch\replace\wait\kustomize |
| Settings Commands | label\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 && filepath.Base(os.Args[0]) != "cobra.test" {
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("Error:", err.Error())
c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
}
return c, err
}
// 子命令執(zhí)行前的準(zhǔn)備
cmd.commandCalledAs.called = true
if cmd.commandCalledAs.name == "" {
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 && !c.SilenceErrors {
c.PrintErrln("Error:", err.Error())
}
// If root command has SilentUsage flagged,
// all subcommands should respect it
if !cmd.SilenceUsage && !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("Called Execute() on a nil Command")
}
if len(c.Deprecated) > 0 {
c.Printf("Command %q is deprecated, %s\n", 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't runnable.
helpVal, err := c.Flags().GetBool("help")
if err != nil {
// should be impossible to get here as we always declare a help
// flag in InitDefaultHelpFlag()
c.Println("\"help\" flag declared as non-bool. Please correct your code")
return err
}
if helpVal {
return flag.ErrHelp
}
// for back-compat, only add version flag behavior if version is defined
if c.Version != "" {
versionVal, err := c.Flags().GetBool("version")
if err != nil {
c.Println("\"version\" flag declared as non-bool. Please correct your code")
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 `json:"name"`
Age int `json:"age"`
Score int `json:"score"`
}
func (s Student) accept(visitor Visitor) {
visitor(s)
}
// 存儲(chǔ)教師信息
type Teacher struct {
Name string `json:"name"`
Age int `json:"age"`
Course string `json:"course"`
}
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: "小明", Score: 90}
t := Teacher{Name: "李", Age: 35, Course: "數(shù)學(xué)"}
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("NameVisitor() before call function")
err = fn(info, err)
if err == nil {
fmt.Printf("==> Name=%s, NameSpace=%s\n", info.Name, info.Namespace)
}
fmt.Println("NameVisitor() after call function")
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("OtherThingsVisitor() before call function")
err = fn(info, err)
if err == nil {
fmt.Printf("==> OtherThings=%s\n", info.OtherThings)
}
fmt.Println("OtherThingsVisitor() after call function")
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("LogVisitor() before call function")
err = fn(info, err)
fmt.Println("LogVisitor() after call function")
return err
})
}
// 調(diào)用邏輯
func main() {
info := Info{}
var v Visitor = &info
v = LogVisitor{v}
v = NameVisitor{v}
v = OtherThingsVisitor{v}
loadFile := func(info *Info, err error) error {
info.Name = "Hao Chen"
info.Namespace = "MegaEase"
info.OtherThings = "We are running as remote team."
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è)Visitor的Visitor()方法,這其實(shí)是一種修飾器的模式,用另一個(gè)Visitor修飾了自己
調(diào)用后,輸出如下信息
LogVisitor() before call function
NameVisitor() before call function
OtherThingsVisitor() before call function
==> OtherThings=We are running as remote team.
OtherThingsVisitor() after call function
==> 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 = &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, &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í)
