kubernetes里的命令行接口叫做kubectl,用来控制kubernetes集群,我们要追踪的命令是kubectl create -f-会从指定文件创建k8s资源,我们要创建的资源是使用了nginx基础镜像的单副本pod。
首先是kubectl命令的创建,在/cmd/kubectl/kubectl.go文件中,实现了kubectl命令的创建:
func main() {
rand.Seed(time.Now().UTC().UnixNano())
command := cmd.NewDefaultKubectlCommand()
// TODO: once we switch everything over to Cobra commands, we can go back to calling
// utilflag.InitFlags() (by removing its pflag.Parse() call). For now, we have to set the
// normalize func and add the go flag set by hand.
pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
// utilflag.InitFlags()
logs.InitLogs()
defer logs.FlushLogs()
if err := command.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
main函数中调用了NewDefaultKubectlCommand函数,该函数的实现在:
/pkg/kubectl/cmd/cmd.go中:
func NewDefaultKubectlCommand() *cobra.Command {
return NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr)
}
该函数调用了同在cmd.go文件中的NewKubectlCommand函数:
// NewKubectlCommand creates the `kubectl` command and its nested children.
func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
// Parent command to which all subcommands are added.
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/`),
Run: runHelp,
BashCompletionFunction: bashCompletionFunc,
}
flags := cmds.PersistentFlags()
flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags
该函数的作用就是创建kubectl以及其子命令。
接下来再来看看实现kubectl create子命令的源码。
/pkg/kubectl/cmd/create/create.go
func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewCreateOptions(ioStreams)
cmd := &cobra.Command{
Use: "create -f FILENAME",
DisableFlagsInUseLine: true,
Short: i18n.T("Create a resource from a file or from stdin."),
Long: createLong,
Example: createExample,
Run: func(cmd *cobra.Command, args []string) {
if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) {
defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut)
defaultRunFunc(cmd, args)
return
}
cmdutil.CheckErr(o.Complete(f, cmd))
cmdutil.CheckErr(o.ValidateArgs(cmd, args))
cmdutil.CheckErr(o.RunCreate(f, cmd))
},
}
......
其中调用的RunCreate函数就是create的主要实现。该函数也在此文件中:
func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
// raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
// the validator enforces this, so
if len(o.Raw) > 0 {
return o.raw(f)
}
if o.EditBeforeCreate {
return RunEditOnCreate(f, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions)
}
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
LabelSelectorParam(o.Selector).
Flatten().
Do()
该函数调用了NewBuilder、Schema等一系列函数,这段代码所做的事情是将命令行接收到的参数转化为一个资源的列。它也负责创建一个可以用来迭代访问所有资源的Visitor结构。这个命令比较复杂,因为它使用了Builder模式的变种,使用独立的函数做各自的数据初始化工作。函数Schema、ContinueOnError、NamespaceParam、DefaultNamespace、FilenameParam、SelectorParam和Flatten都引入了一个指向Builder结构的指针,执行一些对它的修改,并且将这个结构体返回给调用链中的下一个方法来执行这些修改。这些函数都在此文件中:
/pkg/kubectl/genericclioptions/resource/builder.go
一旦所有的初始化都完成,resource.NewBuilder函数会调用Do函数。这个Do函数很关键,它会返回一个Result对象,并且将执行对资源的创建。Do函数还会创建一个Visitor对象,可以用来遍历所有关联到resource.NewBuilder执行过程的资源。Do函数的实现展示如下:
也在builder.go中
// Do returns a Result object with a Visitor for the resources identified by the Builder.
// The visitor will respect the error behavior specified by ContinueOnError. Note that stream
// inputs are consumed by the first execution - use Infos() or Object() on the Result to capture a list
// for further iteration.
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)
}
helpers := []VisitorFunc{}
if b.defaultNamespace {
helpers = append(helpers, SetNamespace(b.namespace))
}
if b.requireNamespace {
helpers = append(helpers, RequireNamespace(b.namespace))
}
helpers = append(helpers, FilterNamespace)
if b.requireObject {
helpers = append(helpers, RetrieveLazy)
}
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
if b.continueOnError {
r.visitor = ContinueOnErrorVisitor{r.visitor}
}
return r
}
创建了一个新的DecoratedVisitor,并作为Builder Do函数返回的Result的一部分。这个DecoratedVisitor有一个Visit函数将会调用传给它的Visitor函数。
/pkg/kubectl/genericclioptions/resource/visitor.go
// NewDecoratedVisitor will create a visitor that invokes the provided visitor functions before
// the user supplied visitor function is invoked, giving them the opportunity to mutate the Info
// object or terminate early with an error.
func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor {
if len(fn) == 0 {
return v
}
return DecoratedVisitor{v, fn}
}
// 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)
})
}
也就是说返回到Do函数中,r对象由Do返回,拥有用来调用DecoratedVisitor的函数Visit,也就是上面的Visit。这为我们找到了从create.go的RunCreate函数到实际最终调用的匿名函数,以及包含了API Server进行调用的createAndRefresh函数
回到RunCreate函数,在调用完Do后,
Do()
err = r.Err()
if err != nil {
return err
}
count := 0
err = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
if err := o.Recorder.Record(info.Object); err != nil {
glog.V(4).Infof("error recording current command: %v", err)
}
if !o.DryRun {
if err := createAndRefresh(info); err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err)
}
}
调用了r.Visit函数,该函数的实现就是上面的visitor.go中的Visit函数。
然后调用了createAndRefresh函数:
该函数就在create,go文件中:
// createAndRefresh creates an object from input info and refreshes info with that object
func createAndRefresh(info *resource.Info) error {
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}
info.Refresh(obj, true)
return nil
}
这个createAndRefresh函数调用了NewHelper函数,该函数:
/pkg/kubectl/genericclioptions/resource/helper.go
// NewHelper creates a Helper from a ResourceMapping
func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
return &Helper{
Resource: mapping.Resource.Resource,
RESTClient: client,
NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,
}
}
这里的代码返回了一个新的Helper对象。
回到createAndRefresh函数中,调用完NewHelper函数,调用了Create函数,
在/pkg/kubectl/genericclioptions/resource/helper.go中
func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) {
if modify {
// Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj)
if err != nil {
// We don't know how to clear the version on this object, so send it to the server as is
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
}
if version != "" {
if err := metadataAccessor.SetResourceVersion(obj, ""); err != nil {
return nil, err
}
}
}
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
}
func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object) (runtime.Object, error) {
return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(obj).Do().Get()
}
可以看到Create函数调用了一个createResource函数。以及实际向API Server发送的用来创建yaml文件描述的资源的REST调用。