Kubernetes 系列 - 4. apiserver (一、启动流程)

18 阅读2分钟

4. apiserver (一、启动流程)

4.1 启动文件

基本所有的k8s组件main方法都是一样的:

func main() {
    command := app.NewAPIServerCommand()
    code := cli.Run(command)
    os.Exit(code)
}

先用Cobra组成一个conmmand,再用cli.Run跑起来。

4.2 生成command

主要就是使用cobra创建command,然后利用options设置所有的flagSet,以下是源码:

// NewAPIServerCommand creates a *cobra.Command object with default parameters
func NewAPIServerCommand() *cobra.Command {
    s := options.NewServerRunOptions()
    cmd := &cobra.Command{
        Use: "kube-apiserver",
        ...
    }

    fs := cmd.Flags()
    namedFlagSets := s.Flags()
    verflag.AddFlags(namedFlagSets.FlagSet("global"))
    globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags())
    options.AddCustomGlobalFlags(namedFlagSets.FlagSet("generic"))
    
    for _, f := range namedFlagSets.FlagSets {
       fs.AddFlagSet(f)
    }

    ...
    return cmd
}

为更直观的看清楚逻辑,重构一下:

// NewAPIServerCommand creates a *cobra.Command object with default parameters
func NewAPIServerCommand() *cobra.Command {
    cmdName := "kube-apiserver"

    s := options.NewServerRunOptions()
    // 添加额外的flagSet
    addAdditionalFlags(s)

    cmd := &cobra.Command{
       Use: cmdName,
       ...
    }
    // 将s的flagSet复制到cmd
    copyFlags(s, cmd)
    return cmd
}

4.3 启动command

一步步追踪下去,比较重要的逻辑为:

  1. 读取启动参数
// ExecuteC executes the command.
func (c *Command) ExecuteC() (cmd *Command, err error) {
    ...
    args := c.args

    // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
    if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
       // 1. 读取启动参数
       args = os.Args[1:]
    }

    ...
    // 由args解析成flags,不必关注如何解析的,正常情况下可以flags和args的值是一样的
    err = cmd.execute(flags)
    ...
    return cmd, err
}
  1. 解析参数、运行RunE
func (c *Command) execute(a []string) (err error) {
    ...
    // 解析参数,非法参数会报错
    err = c.ParseFlags(a)
    
    // 开放自定义前置逻辑
    c.preRun()

    // 开放自定义后置逻辑
    defer c.postRun()

    ...
    if c.PreRunE != nil {
       if err := c.PreRunE(c, argWoFlags); err != nil {
          return err
       }
    } else if c.PreRun != nil {
       c.PreRun(c, argWoFlags)
    }

    ...
    if c.RunE != nil {
       // apiserver对应的是这部分
       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)
    }
    ...

    return nil
}
  1. 执行RunE
RunE: func(cmd *cobra.Command, args []string) error {
    ...
    // set default options
    // 这里的 s 就是 4.2 中的 s(ServerRunOptions),设置默认值
    completedOptions, err := s.Complete()
    if err != nil {
       return err
    }

    // validate options
    if errs := completedOptions.Validate(); len(errs) != 0 {
       return utilerrors.NewAggregate(errs)
    }
    // add feature enablement metrics
    utilfeature.DefaultMutableFeatureGate.AddMetrics()
    return Run(completedOptions, genericapiserver.SetupSignalHandler())
}

options在cli.Run(cmd)之前和之后有什么区别呢?这里需要注意的就是在复制的参数使用的是引用类型,所以在cli.Run中解析的参数也在 options 中了。

  1. Run 方法
// Run runs the specified APIServer.  This should never exit.
func Run(opts options.CompletedOptions, stopCh <-chan struct{}) error {
    ...
    // 1. 生成 apiserver、aggregator、apiExtensions 的 config
    config, err := NewConfig(opts)
    if err != nil {
       return err
    }
    // 2. 填充默认值
    completed, err := config.Complete()
    if err != nil {
       return err
    }
    // 3. 创建执行链
    server, err := CreateServerChain(completed)
    if err != nil {
       return err
    }

    prepared, err := server.PrepareRun()
    if err != nil {
       return err
    }

    return prepared.Run(stopCh)
}