当前位置:   article > 正文

Kubernetes源码之kubectl剖析_error: must specify one of -f and -k

error: must specify one of -f and -k

目录

1.Kubernetes架构简介

2.kuberctl入口源码剖析

2.1 源码入口

2.2 kubectl命令行客户端代码

2.3 剖析kubectl create子命令

2.4 cobra.Command命令的执行模板

3.ApiServer服务端代码剖析

3.1 ApiServer启动入口

3.2 ApiServer命令启动执行方法

3.3 APiServer启动http server监听http请求

3.4 各Rest请求处理的Handler构建

4.总结

5.参考文章


1.Kubernetes架构简介

Kubernetes架构分为三层,即:控制平面、数据平面、计算平面。控制平面主要是指kube-apiserver、kube-controller-manager、kube-scheduler三个组件;数据平面是指etcd集群;计算平面是指kubelet、kube-proxy组件。我们通常会将控制平面和数据平面部署在同一组节点上,通常称其为Master节点,而计算平面称为Node节点。控制平面是整个集群的大脑,负责控制、调度集群资源;计算平面负责运行工作负载,是控制平面调度的对象;而数据平面则用来存储整个集群持久化数据,比如我们提交的配置文件以及集群状态信息等。我们通常通过kubectl这个命令行客户端来控制Kubernetes集群。

kubernetes集群中各个模块之间的依赖关系如下图所示:

Kubernetes 组件

 

图1 kubernetes架构图(源自:https://kubernetes.io/zh/docs/concepts/architecture/cloud-controller/

kubectl通过命令行向apiserver发起rest请求,进行相应的资源调度管理相关操作,由apiserver收到命令之后落地到etcd数据库,然后其他各个模块与apiserver交互,实现相关资源调度工作的执行。

图2 服务调用依赖关系图(源自:https://blog.csdn.net/u014458692/article/details/108068088

各个组件说明:

kube-apiserver 对外暴露Kubernetes API,所有对集群的操作都是通过这组API完成,包括客户端下达应用编排命令给Kubernetes集群;kubelet上报集群资源使用情况;以及各个组件之间的交互都是通过这套API完成的。

kube-controller-manager 负责整个Kubernetes的管理工作,保证集群中各种资源处于期望状态,当监控到集群中某个资源状态与期望状态不符时,controller-manager会触发调度操作。

kube-scheduler 调度器负责Kubernetes集群的具体调度工作,接收来自于controller-manager触发的调度操作请求,然后根据请求规格、调度约束、整体资源情况进行调度计算,最后将任务发送到目标节点由kubelet组件执行。

etcd 是一个高效KV存储系统。在Kubernetes环境中主要用于存储所有需要持久化的数据。

kubelet 是Node节点上的核心组件,负责与docker daemon进行交互运行docker容器;配置网络和数据卷;监控并上报节点资源使用情况以供调度器使用。

kube-proxy 主要负责Service Endpoint到POD实例的请求转发及负载均衡的规则管理。

 

2.kuberctl入口源码剖析

本文剖析源码的版本:kubernetes的源码版本为1.20, golang的源码版本为:1.14

2.1 源码入口

kubectl的源码入口:cmd/kubectl/kubectl.go:35

cmd包里面包含kubernetes常用的各种命令行工具,其中kubectl工具的入口方法为kubectl.go文件中的main方法,通过command := cmd.NewDefaultKubectlCommand()方法实现新建一个kubectl的命令行对象,然后调用command.Execute()方法执行命令,实现与控制面板的服务端服务对接。具体入口源码如下:

  1. func main() {
  2. rand.Seed(time.Now().UnixNano())
  3. command := cmd.NewDefaultKubectlCommand()
  4. // TODO: once we switch everything over to Cobra commands, we can go back to calling
  5. // cliflag.InitFlags() (by removing its pflag.Parse() call). For now, we have to set the
  6. // normalize func and add the go flag set by hand.
  7. pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)
  8. pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
  9. // cliflag.InitFlags()
  10. logs.InitLogs()
  11. defer logs.FlushLogs()
  12. if err := command.Execute(); err != nil {
  13. os.Exit(1)
  14. }
  15. }

2.2 kubectl命令行客户端代码

命令行工具采用的是Cobra的工具包对参数进行解析,解析成功之后,采用一整套的模板方法实现整个执行过程的调度。

首先分析一下kubectl命令行对象的创建,具体的创建该对象的函数为:pkg/kubectl/cmd/cmd.go:432 NewKubectlCommand 函数,源代码如下:

  1. // NewKubectlCommand creates the `kubectl` command and its nested children.
  2. func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
  3. warningHandler := rest.NewWarningWriter(err, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(err)})
  4. warningsAsErrors := false
  5. // Parent command to which all subcommands are added.
  6. cmds := &cobra.Command{
  7. Use: "kubectl",
  8. Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
  9. Long: templates.LongDesc(`
  10. kubectl controls the Kubernetes cluster manager.
  11. Find more information at:
  12. https://kubernetes.io/docs/reference/kubectl/overview/`),
  13. Run: runHelp,
  14. // Hook before and after Run initialize and write profiles to disk,
  15. // respectively.
  16. PersistentPreRunE: func(*cobra.Command, []string) error {
  17. rest.SetDefaultWarningHandler(warningHandler)
  18. return initProfiling()
  19. },
  20. PersistentPostRunE: func(*cobra.Command, []string) error {
  21. if err := flushProfiling(); err != nil {
  22. return err
  23. }
  24. if warningsAsErrors {
  25. count := warningHandler.WarningCount()
  26. switch count {
  27. case 0:
  28. // no warnings
  29. case 1:
  30. return fmt.Errorf("%d warning received", count)
  31. default:
  32. return fmt.Errorf("%d warnings received", count)
  33. }
  34. }
  35. return nil
  36. },
  37. BashCompletionFunction: bashCompletionFunc,
  38. }
  39. flags := cmds.PersistentFlags()
  40. flags.SetNormalizeFunc(cliflag.WarnWordSepNormalizeFunc) // Warn for "_" flags
  41. // Normalize all flags that are coming from other packages or pre-configurations
  42. // a.k.a. change all "_" to "-". e.g. glog package
  43. flags.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)
  44. addProfilingFlags(flags)
  45. flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")
  46. kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
  47. kubeConfigFlags.AddFlags(flags)
  48. matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
  49. matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())
  50. cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)
  51. f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
  52. // Sending in 'nil' for the getLanguageFn() results in using
  53. // the LANG environment variable.
  54. //
  55. // TODO: Consider adding a flag or file preference for setting
  56. // the language, instead of just loading from the LANG env. variable.
  57. i18n.LoadTranslations("kubectl", nil)
  58. // From this point and forward we get warnings on flags that contain "_" separators
  59. cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)
  60. ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
  61. groups := templates.CommandGroups{
  62. {
  63. Message: "Basic Commands (Beginner):",
  64. Commands: []*cobra.Command{
  65. create.NewCmdCreate(f, ioStreams),
  66. expose.NewCmdExposeService(f, ioStreams),
  67. run.NewCmdRun(f, ioStreams),
  68. set.NewCmdSet(f, ioStreams),
  69. },
  70. },
  71. {
  72. Message: "Basic Commands (Intermediate):",
  73. Commands: []*cobra.Command{
  74. explain.NewCmdExplain("kubectl", f, ioStreams),
  75. get.NewCmdGet("kubectl", f, ioStreams),
  76. edit.NewCmdEdit(f, ioStreams),
  77. delete.NewCmdDelete(f, ioStreams),
  78. },
  79. },
  80. {
  81. Message: "Deploy Commands:",
  82. Commands: []*cobra.Command{
  83. rollout.NewCmdRollout(f, ioStreams),
  84. scale.NewCmdScale(f, ioStreams),
  85. autoscale.NewCmdAutoscale(f, ioStreams),
  86. },
  87. },
  88. {
  89. Message: "Cluster Management Commands:",
  90. Commands: []*cobra.Command{
  91. certificates.NewCmdCertificate(f, ioStreams),
  92. clusterinfo.NewCmdClusterInfo(f, ioStreams),
  93. top.NewCmdTop(f, ioStreams),
  94. drain.NewCmdCordon(f, ioStreams),
  95. drain.NewCmdUncordon(f, ioStreams),
  96. drain.NewCmdDrain(f, ioStreams),
  97. taint.NewCmdTaint(f, ioStreams),
  98. },
  99. },
  100. {
  101. Message: "Troubleshooting and Debugging Commands:",
  102. Commands: []*cobra.Command{
  103. describe.NewCmdDescribe("kubectl", f, ioStreams),
  104. logs.NewCmdLogs(f, ioStreams),
  105. attach.NewCmdAttach(f, ioStreams),
  106. cmdexec.NewCmdExec(f, ioStreams),
  107. portforward.NewCmdPortForward(f, ioStreams),
  108. proxy.NewCmdProxy(f, ioStreams),
  109. cp.NewCmdCp(f, ioStreams),
  110. auth.NewCmdAuth(f, ioStreams),
  111. },
  112. },
  113. {
  114. Message: "Advanced Commands:",
  115. Commands: []*cobra.Command{
  116. diff.NewCmdDiff(f, ioStreams),
  117. apply.NewCmdApply("kubectl", f, ioStreams),
  118. patch.NewCmdPatch(f, ioStreams),
  119. replace.NewCmdReplace(f, ioStreams),
  120. wait.NewCmdWait(f, ioStreams),
  121. convert.NewCmdConvert(f, ioStreams),
  122. kustomize.NewCmdKustomize(ioStreams),
  123. },
  124. },
  125. {
  126. Message: "Settings Commands:",
  127. Commands: []*cobra.Command{
  128. label.NewCmdLabel(f, ioStreams),
  129. annotate.NewCmdAnnotate("kubectl", f, ioStreams),
  130. completion.NewCmdCompletion(ioStreams.Out, ""),
  131. },
  132. },
  133. }
  134. groups.Add(cmds)
  135. filters := []string{"options"}
  136. // Hide the "alpha" subcommand if there are no alpha commands in this build.
  137. alpha := cmdpkg.NewCmdAlpha(f, ioStreams)
  138. if !alpha.HasSubCommands() {
  139. filters = append(filters, alpha.Name())
  140. }
  141. templates.ActsAsRootCommand(cmds, filters, groups...)
  142. for name, completion := range bashCompletionFlags {
  143. if cmds.Flag(name) != nil {
  144. if cmds.Flag(name).Annotations == nil {
  145. cmds.Flag(name).Annotations = map[string][]string{}
  146. }
  147. cmds.Flag(name).Annotations[cobra.BashCompCustom] = append(
  148. cmds.Flag(name).Annotations[cobra.BashCompCustom],
  149. completion,
  150. )
  151. }
  152. }
  153. cmds.AddCommand(alpha)
  154. cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams))
  155. cmds.AddCommand(plugin.NewCmdPlugin(f, ioStreams))
  156. cmds.AddCommand(version.NewCmdVersion(f, ioStreams))
  157. cmds.AddCommand(apiresources.NewCmdAPIVersions(f, ioStreams))
  158. cmds.AddCommand(apiresources.NewCmdAPIResources(f, ioStreams))
  159. cmds.AddCommand(options.NewCmdOptions(ioStreams.Out))
  160. return cmds
  161. }

kubectl的命令行cobra.Command是一个父子树,其中kubectl命令作为树的根节点,然后添加了子节点(groups变量),各个子节点以kubectl Command节点为父节点,如根据文件创建,编辑文件,创建命名空间,创建部署服务,创建证书等等。同样,kubectl的子节点还可以拥有自己的子节点,创建都类似。

2.3 剖析kubectl create子命令

参考一个kubectl create 命令解析的入口: vendor/k8s.io/kubectl/pkg/cmd/create/create.go:100 NewCmdCreate方法,其源代码如下:

  1. // NewCmdCreate returns new initialized instance of create sub command
  2. func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
  3. o := NewCreateOptions(ioStreams)
  4. cmd := &cobra.Command{
  5. Use: "create -f FILENAME",
  6. DisableFlagsInUseLine: true,
  7. Short: i18n.T("Create a resource from a file or from stdin."),
  8. Long: createLong,
  9. Example: createExample,
  10. Run: func(cmd *cobra.Command, args []string) {
  11. if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) {
  12. ioStreams.ErrOut.Write([]byte("Error: must specify one of -f and -k\n\n"))
  13. defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut)
  14. defaultRunFunc(cmd, args)
  15. return
  16. }
  17. cmdutil.CheckErr(o.Complete(f, cmd))
  18. cmdutil.CheckErr(o.ValidateArgs(cmd, args))
  19. cmdutil.CheckErr(o.RunCreate(f, cmd))
  20. },
  21. }
  22. // bind flag structs
  23. o.RecordFlags.AddFlags(cmd)
  24. usage := "to use to create the resource"
  25. cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
  26. cmdutil.AddValidateFlags(cmd)
  27. cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating")
  28. cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows",
  29. "Only relevant if --edit=true. Defaults to the line ending native to your platform.")
  30. cmdutil.AddApplyAnnotationFlags(cmd)
  31. cmdutil.AddDryRunFlag(cmd)
  32. cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
  33. cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.")
  34. cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-create")
  35. o.PrintFlags.AddFlags(cmd)
  36. // create subcommands
  37. cmd.AddCommand(NewCmdCreateNamespace(f, ioStreams))
  38. cmd.AddCommand(NewCmdCreateQuota(f, ioStreams))
  39. cmd.AddCommand(NewCmdCreateSecret(f, ioStreams))
  40. cmd.AddCommand(NewCmdCreateConfigMap(f, ioStreams))
  41. cmd.AddCommand(NewCmdCreateServiceAccount(f, ioStreams))
  42. cmd.AddCommand(NewCmdCreateService(f, ioStreams))
  43. cmd.AddCommand(NewCmdCreateDeployment(f, ioStreams))
  44. cmd.AddCommand(NewCmdCreateClusterRole(f, ioStreams))
  45. cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, ioStreams))
  46. cmd.AddCommand(NewCmdCreateRole(f, ioStreams))
  47. cmd.AddCommand(NewCmdCreateRoleBinding(f, ioStreams))
  48. cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, ioStreams))
  49. cmd.AddCommand(NewCmdCreatePriorityClass(f, ioStreams))
  50. cmd.AddCommand(NewCmdCreateJob(f, ioStreams))
  51. cmd.AddCommand(NewCmdCreateCronJob(f, ioStreams))
  52. return cmd
  53. }

代码中,通过cmdutil.AddFilenameOptionFlags,cmdutil.AddValidateFlags,cmd.Flags().BoolVar,cmd.Flags().Bool等方法为kubectl命令添加对应的命令行参数; 通过cmd.AddCommand() 方法为create命令创建子命令,如namespace,secret, configmap,role, job等等,以此构建一整套命令行的树结构。

在NewCmdCreate方法中,采用cobra.Command命令的Run方法中,首先对参数调用cmdutil.CheckErr(o.Complete(f, cmd))方法实现参数的补齐,其次调用cmdutil.CheckErr(o.ValidateArgs(cmd, args))方法实现对参数的合理性进行校验,最后通过cmdutil.CheckErr(o.RunCreate(f, cmd))方法实现对命令的执行。

查看RunCreate方法源码,vendor/k8s.io/kubectl/pkg/cmd/create/create.go:227 RunCreate方法,通过该方法创建,方法中通过链路形式创建request请求,通过 obj, err := resource.NewHelper(info.Client, info.Mapping).DryRun(o.DryRunStrategy == cmdutil.DryRunServer).WithFieldManager(o.fieldManager).Create(info.Namespace, true, info.Object)调用,实现Rest调用触发服务端API服务,完成相应的创建动作。

  1. // RunCreate performs the creation
  2. func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
  3. // raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
  4. // the validator enforces this, so
  5. if len(o.Raw) > 0 {
  6. restClient, err := f.RESTClient()
  7. if err != nil {
  8. return err
  9. }
  10. return rawhttp.RawPost(restClient, o.IOStreams, o.Raw, o.FilenameOptions.Filenames[0])
  11. }
  12. if o.EditBeforeCreate {
  13. return RunEditOnCreate(f, o.PrintFlags, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions, o.fieldManager)
  14. }
  15. schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
  16. if err != nil {
  17. return err
  18. }
  19. cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
  20. if err != nil {
  21. return err
  22. }
  23. r := f.NewBuilder().
  24. Unstructured().
  25. Schema(schema).
  26. ContinueOnError().
  27. NamespaceParam(cmdNamespace).DefaultNamespace().
  28. FilenameParam(enforceNamespace, &o.FilenameOptions).
  29. LabelSelectorParam(o.Selector).
  30. Flatten().
  31. Do()
  32. err = r.Err()
  33. if err != nil {
  34. return err
  35. }
  36. count := 0
  37. err = r.Visit(func(info *resource.Info, err error) error {
  38. if err != nil {
  39. return err
  40. }
  41. if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, scheme.DefaultJSONEncoder()); err != nil {
  42. return cmdutil.AddSourceToErr("creating", info.Source, err)
  43. }
  44. if err := o.Recorder.Record(info.Object); err != nil {
  45. klog.V(4).Infof("error recording current command: %v", err)
  46. }
  47. if o.DryRunStrategy != cmdutil.DryRunClient {
  48. if o.DryRunStrategy == cmdutil.DryRunServer {
  49. if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
  50. return cmdutil.AddSourceToErr("creating", info.Source, err)
  51. }
  52. }
  53. obj, err := resource.
  54. NewHelper(info.Client, info.Mapping).
  55. DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
  56. WithFieldManager(o.fieldManager).
  57. Create(info.Namespace, true, info.Object)
  58. if err != nil {
  59. return cmdutil.AddSourceToErr("creating", info.Source, err)
  60. }
  61. info.Refresh(obj, true)
  62. }
  63. count++
  64. return o.PrintObj(info.Object)
  65. })
  66. if err != nil {
  67. return err
  68. }
  69. if count == 0 {
  70. return fmt.Errorf("no objects passed to create")
  71. }
  72. return nil
  73. }

rest调用通过cliet-go发起远程调用 源码可以追溯到staging/src/k8s.io/client-go/rest/request.go:826 request 方法,如下源码:

  1. // request connects to the server and invokes the provided function when a server response is
  2. // received. It handles retry behavior and up front validation of requests. It will invoke
  3. // fn at most once. It will return an error if a problem occurred prior to connecting to the
  4. // server - the provided function is responsible for handling server errors.
  5. func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error {
  6. //Metrics for total request latency
  7. start := time.Now()
  8. defer func() {
  9. metrics.RequestLatency.Observe(r.verb, r.finalURLTemplate(), time.Since(start))
  10. }()
  11. if r.err != nil {
  12. klog.V(4).Infof("Error in request: %v", r.err)
  13. return r.err
  14. }
  15. if err := r.requestPreflightCheck(); err != nil {
  16. return err
  17. }
  18. client := r.c.Client
  19. if client == nil {
  20. client = http.DefaultClient
  21. }
  22. // Throttle the first try before setting up the timeout configured on the
  23. // client. We don't want a throttled client to return timeouts to callers
  24. // before it makes a single request.
  25. if err := r.tryThrottle(ctx); err != nil {
  26. return err
  27. }
  28. if r.timeout > 0 {
  29. var cancel context.CancelFunc
  30. ctx, cancel = context.WithTimeout(ctx, r.timeout)
  31. defer cancel()
  32. }
  33. // Right now we make about ten retry attempts if we get a Retry-After response.
  34. retries := 0
  35. for {
  36. url := r.URL().String()
  37. req, err := http.NewRequest(r.verb, url, r.body)
  38. if err != nil {
  39. return err
  40. }
  41. req = req.WithContext(ctx)
  42. req.Header = r.headers
  43. r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
  44. if retries > 0 {
  45. // We are retrying the request that we already send to apiserver
  46. // at least once before.
  47. // This request should also be throttled with the client-internal rate limiter.
  48. if err := r.tryThrottle(ctx); err != nil {
  49. return err
  50. }
  51. }
  52. resp, err := client.Do(req)
  53. updateURLMetrics(r, resp, err)
  54. if err != nil {
  55. r.backoff.UpdateBackoff(r.URL(), err, 0)
  56. } else {
  57. r.backoff.UpdateBackoff(r.URL(), err, resp.StatusCode)
  58. }
  59. if err != nil {
  60. // "Connection reset by peer" or "apiserver is shutting down" are usually a transient errors.
  61. // Thus in case of "GET" operations, we simply retry it.
  62. // We are not automatically retrying "write" operations, as
  63. // they are not idempotent.
  64. if r.verb != "GET" {
  65. return err
  66. }
  67. // For connection errors and apiserver shutdown errors retry.
  68. if net.IsConnectionReset(err) || net.IsProbableEOF(err) {
  69. // For the purpose of retry, we set the artificial "retry-after" response.
  70. // TODO: Should we clean the original response if it exists?
  71. resp = &http.Response{
  72. StatusCode: http.StatusInternalServerError,
  73. Header: http.Header{"Retry-After": []string{"1"}},
  74. Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
  75. }
  76. } else {
  77. return err
  78. }
  79. }
  80. done := func() bool {
  81. // Ensure the response body is fully read and closed
  82. // before we reconnect, so that we reuse the same TCP
  83. // connection.
  84. defer func() {
  85. const maxBodySlurpSize = 2 << 10
  86. if resp.ContentLength <= maxBodySlurpSize {
  87. io.Copy(ioutil.Discard, &io.LimitedReader{R: resp.Body, N: maxBodySlurpSize})
  88. }
  89. resp.Body.Close()
  90. }()
  91. retries++
  92. if seconds, wait := checkWait(resp); wait && retries <= r.maxRetries {
  93. if seeker, ok := r.body.(io.Seeker); ok && r.body != nil {
  94. _, err := seeker.Seek(0, 0)
  95. if err != nil {
  96. klog.V(4).Infof("Could not retry request, can't Seek() back to beginning of body for %T", r.body)
  97. fn(req, resp)
  98. return true
  99. }
  100. }
  101. klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", seconds, retries, url)
  102. r.backoff.Sleep(time.Duration(seconds) * time.Second)
  103. return false
  104. }
  105. fn(req, resp)
  106. return true
  107. }()
  108. if done {
  109. return nil
  110. }
  111. }
  112. }

其中构造URL参数依赖方法 staging/src/k8s.io/client-go/rest/request.go:468 URL() 方法,在该方法中完成命令行参数转变为url path的拼接封装,如下源码:

  1. staging/src/k8s.io/client-go/rest/request.go:468
  2. // URL returns the current working URL.
  3. func (r *Request) URL() *url.URL {
  4. p := r.pathPrefix
  5. if r.namespaceSet && len(r.namespace) > 0 {
  6. p = path.Join(p, "namespaces", r.namespace)
  7. }
  8. if len(r.resource) != 0 {
  9. p = path.Join(p, strings.ToLower(r.resource))
  10. }
  11. // Join trims trailing slashes, so preserve r.pathPrefix's trailing slash for backwards compatibility if nothing was changed
  12. if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 {
  13. p = path.Join(p, r.resourceName, r.subresource, r.subpath)
  14. }
  15. finalURL := &url.URL{}
  16. if r.c.base != nil {
  17. *finalURL = *r.c.base
  18. }
  19. finalURL.Path = p
  20. query := url.Values{}
  21. for key, values := range r.params {
  22. for _, value := range values {
  23. query.Add(key, value)
  24. }
  25. }
  26. // timeout is handled specially here.
  27. if r.timeout != 0 {
  28. query.Set("timeout", r.timeout.String())
  29. }
  30. finalURL.RawQuery = query.Encode()
  31. return finalURL
  32. }

2.4 cobra.Command命令的执行模板

kubectl命令行,采用cobra.Command创建,其中命令的执行是有一整套的命令执行模板方法,该执行方法的入口为:vendor/github.com/spf13/cobra/command.go:892 ExecuteC方法,具体源码参考如下代码段。代码中执行

  1. // ExecuteC executes the command.
  2. func (c *Command) ExecuteC() (cmd *Command, err error) {
  3. if c.ctx == nil {
  4. c.ctx = context.Background()
  5. }
  6. // Regardless of what command execute is called on, run on Root only
  7. if c.HasParent() {
  8. return c.Root().ExecuteC()
  9. }
  10. // windows hook
  11. if preExecHookFn != nil {
  12. preExecHookFn(c)
  13. }
  14. // initialize help as the last point possible to allow for user
  15. // overriding
  16. c.InitDefaultHelpCmd()
  17. args := c.args
  18. // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
  19. if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
  20. args = os.Args[1:]
  21. }
  22. // initialize the hidden command to be used for bash completion
  23. c.initCompleteCmd(args)
  24. var flags []string
  25. if c.TraverseChildren {
  26. cmd, flags, err = c.Traverse(args)
  27. } else {
  28. cmd, flags, err = c.Find(args)
  29. }
  30. if err != nil {
  31. // If found parse to a subcommand and then failed, talk about the subcommand
  32. if cmd != nil {
  33. c = cmd
  34. }
  35. if !c.SilenceErrors {
  36. c.Println("Error:", err.Error())
  37. c.Printf("Run '%v --help' for usage.\n", c.CommandPath())
  38. }
  39. return c, err
  40. }
  41. cmd.commandCalledAs.called = true
  42. if cmd.commandCalledAs.name == "" {
  43. cmd.commandCalledAs.name = cmd.Name()
  44. }
  45. // We have to pass global context to children command
  46. // if context is present on the parent command.
  47. if cmd.ctx == nil {
  48. cmd.ctx = c.ctx
  49. }
  50. err = cmd.execute(flags)
  51. if err != nil {
  52. // Always show help if requested, even if SilenceErrors is in
  53. // effect
  54. if err == flag.ErrHelp {
  55. cmd.HelpFunc()(cmd, args)
  56. return cmd, nil
  57. }
  58. // If root command has SilentErrors flagged,
  59. // all subcommands should respect it
  60. if !cmd.SilenceErrors && !c.SilenceErrors {
  61. c.Println("Error:", err.Error())
  62. }
  63. // If root command has SilentUsage flagged,
  64. // all subcommands should respect it
  65. if !cmd.SilenceUsage && !c.SilenceUsage {
  66. c.Println(cmd.UsageString())
  67. }
  68. }
  69. return cmd, err
  70. }

执行模板中依赖的执行execute方法vendor/github.com/spf13/cobra/command.go:755 execute方法,该方法定义了各个方法的前后依赖关系,执行顺序如下:

  1. // The *Run functions are executed in the following order:
  2. // * PersistentPreRun()
  3. // * PreRun()
  4. // * Run()
  5. // * PostRun()
  6. // * PersistentPostRun()
  7. // All functions get the same args, the arguments after the command name.

调用服务端通过rest方法调用,依赖go语言底层的http的包实现,具体参考:vendor/k8s.io/client-go/rest/request.go:826 request 方法。

  1. func (c *Command) execute(a []string) (err error) {
  2. if c == nil {
  3. return fmt.Errorf("Called Execute() on a nil Command")
  4. }
  5. if len(c.Deprecated) > 0 {
  6. c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
  7. }
  8. // initialize help and version flag at the last point possible to allow for user
  9. // overriding
  10. c.InitDefaultHelpFlag()
  11. c.InitDefaultVersionFlag()
  12. err = c.ParseFlags(a)
  13. if err != nil {
  14. return c.FlagErrorFunc()(c, err)
  15. }
  16. // If help is called, regardless of other flags, return we want help.
  17. // Also say we need help if the command isn't runnable.
  18. helpVal, err := c.Flags().GetBool("help")
  19. if err != nil {
  20. // should be impossible to get here as we always declare a help
  21. // flag in InitDefaultHelpFlag()
  22. c.Println("\"help\" flag declared as non-bool. Please correct your code")
  23. return err
  24. }
  25. if helpVal {
  26. return flag.ErrHelp
  27. }
  28. // for back-compat, only add version flag behavior if version is defined
  29. if c.Version != "" {
  30. versionVal, err := c.Flags().GetBool("version")
  31. if err != nil {
  32. c.Println("\"version\" flag declared as non-bool. Please correct your code")
  33. return err
  34. }
  35. if versionVal {
  36. err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c)
  37. if err != nil {
  38. c.Println(err)
  39. }
  40. return err
  41. }
  42. }
  43. if !c.Runnable() {
  44. return flag.ErrHelp
  45. }
  46. c.preRun()
  47. argWoFlags := c.Flags().Args()
  48. if c.DisableFlagParsing {
  49. argWoFlags = a
  50. }
  51. if err := c.ValidateArgs(argWoFlags); err != nil {
  52. return err
  53. }
  54. for p := c; p != nil; p = p.Parent() {
  55. if p.PersistentPreRunE != nil {
  56. if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
  57. return err
  58. }
  59. break
  60. } else if p.PersistentPreRun != nil {
  61. p.PersistentPreRun(c, argWoFlags)
  62. break
  63. }
  64. }
  65. if c.PreRunE != nil {
  66. if err := c.PreRunE(c, argWoFlags); err != nil {
  67. return err
  68. }
  69. } else if c.PreRun != nil {
  70. c.PreRun(c, argWoFlags)
  71. }
  72. if err := c.validateRequiredFlags(); err != nil {
  73. return err
  74. }
  75. if c.RunE != nil {
  76. if err := c.RunE(c, argWoFlags); err != nil {
  77. return err
  78. }
  79. } else {
  80. c.Run(c, argWoFlags)
  81. }
  82. if c.PostRunE != nil {
  83. if err := c.PostRunE(c, argWoFlags); err != nil {
  84. return err
  85. }
  86. } else if c.PostRun != nil {
  87. c.PostRun(c, argWoFlags)
  88. }
  89. for p := c; p != nil; p = p.Parent() {
  90. if p.PersistentPostRunE != nil {
  91. if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
  92. return err
  93. }
  94. break
  95. } else if p.PersistentPostRun != nil {
  96. p.PersistentPostRun(c, argWoFlags)
  97. break
  98. }
  99. }
  100. return nil
  101. }


3.ApiServer服务端代码剖析

上一节讲了客户端kubectl客户端发起命令,最终转换为http rest请求,触发服务调用。那么服务端是如何接收并处理相应的请求呢?服务端由apiserver接收来自kubectl客户端的调用,并进行相应的业务逻辑处理。服务端的整体框架的构建参考如下章节描述。

3.1 ApiServer启动入口

服务端apiserver启动入口:cmd/kube-apiserver/apiserver.go:32 main()方法,通过该方法启动apiserver。源码入口如下:

  1. func main() {
  2. rand.Seed(time.Now().UnixNano())
  3. command := app.NewAPIServerCommand()
  4. // TODO: once we switch everything over to Cobra commands, we can go back to calling
  5. // utilflag.InitFlags() (by removing its pflag.Parse() call). For now, we have to set the
  6. // normalize func and add the go flag set by hand.
  7. // utilflag.InitFlags()
  8. logs.InitLogs()
  9. defer logs.FlushLogs()
  10. if err := command.Execute(); err != nil {
  11. os.Exit(1)
  12. }
  13. }

3.2 ApiServer命令启动执行方法

通过层层代码跟进,发现服务端启动APIServer,执行命令分创建服务调用链路,然后准备执行,最后执行启动Server方法,入口为:cmd/kube-apiserver/app/server.go:161 Run()方法,源码如下:

  1. cmd/kube-apiserver/app/server.go:161
  2. // Run runs the specified APIServer. This should never exit.
  3. func Run(completeOptions completedServerRunOptions, stopCh <-chan struct{}) error {
  4. // To help debugging, immediately log version
  5. klog.Infof("Version: %+v", version.Get())
  6. server, err := CreateServerChain(completeOptions, stopCh)
  7. if err != nil {
  8. return err
  9. }
  10. prepared, err := server.PrepareRun()
  11. if err != nil {
  12. return err
  13. }
  14. return prepared.Run(stopCh)
  15. }
  16. // 层层分析深入到如下代码:
  17. staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go:316
  18. // Run spawns the secure http server. It only returns if stopCh is closed
  19. // or the secure port cannot be listened on initially.
  20. func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
  21. delayedStopCh := make(chan struct{})
  22. go func() {
  23. defer close(delayedStopCh)
  24. <-stopCh
  25. // As soon as shutdown is initiated, /readyz should start returning failure.
  26. // This gives the load balancer a window defined by ShutdownDelayDuration to detect that /readyz is red
  27. // and stop sending traffic to this server.
  28. close(s.readinessStopCh)
  29. time.Sleep(s.ShutdownDelayDuration)
  30. }()
  31. // close socket after delayed stopCh
  32. stoppedCh, err := s.NonBlockingRun(delayedStopCh)
  33. if err != nil {
  34. return err
  35. }
  36. <-stopCh
  37. // run shutdown hooks directly. This includes deregistering from the kubernetes endpoint in case of kube-apiserver.
  38. err = s.RunPreShutdownHooks()
  39. if err != nil {
  40. return err
  41. }
  42. // wait for the delayed stopCh before closing the handler chain (it rejects everything after Wait has been called).
  43. <-delayedStopCh
  44. // wait for stoppedCh that is closed when the graceful termination (server.Shutdown) is finished.
  45. <-stoppedCh
  46. // Wait for all requests to finish, which are bounded by the RequestTimeout variable.
  47. s.HandlerChainWaitGroup.Wait()
  48. return nil
  49. }

3.3 APiServer启动http server监听http请求

通过深入分析源码,进入到启动http Server的入口: staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go:360 NonBlockingRun()方法,如下:

  1. // NonBlockingRun spawns the secure http server. An error is
  2. // returned if the secure port cannot be listened on.
  3. // The returned channel is closed when the (asynchronous) termination is finished.
  4. func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}) (<-chan struct{}, error) {
  5. // Use an stop channel to allow graceful shutdown without dropping audit events
  6. // after http server shutdown.
  7. auditStopCh := make(chan struct{})
  8. // Start the audit backend before any request comes in. This means we must call Backend.Run
  9. // before http server start serving. Otherwise the Backend.ProcessEvents call might block.
  10. if s.AuditBackend != nil {
  11. if err := s.AuditBackend.Run(auditStopCh); err != nil {
  12. return nil, fmt.Errorf("failed to run the audit backend: %v", err)
  13. }
  14. }
  15. // Use an internal stop channel to allow cleanup of the listeners on error.
  16. internalStopCh := make(chan struct{})
  17. var stoppedCh <-chan struct{}
  18. if s.SecureServingInfo != nil && s.Handler != nil {
  19. var err error
  20. stoppedCh, err = s.SecureServingInfo.Serve(s.Handler, s.ShutdownTimeout, internalStopCh)
  21. if err != nil {
  22. close(internalStopCh)
  23. close(auditStopCh)
  24. return nil, err
  25. }
  26. }
  27. // Now that listener have bound successfully, it is the
  28. // responsibility of the caller to close the provided channel to
  29. // ensure cleanup.
  30. go func() {
  31. <-stopCh
  32. close(internalStopCh)
  33. if stoppedCh != nil {
  34. <-stoppedCh
  35. }
  36. s.HandlerChainWaitGroup.Wait()
  37. close(auditStopCh)
  38. }()
  39. s.RunPostStartHooks(stopCh)
  40. if _, err := systemd.SdNotify(true, "READY=1\n"); err != nil {
  41. klog.Errorf("Unable to send systemd daemon successful start message: %v\n", err)
  42. }
  43. return stoppedCh, nil
  44. }

启动http Server 方法入口: staging/src/k8s.io/apiserver/pkg/server/secure_serving.go:147 Serve()方法,源码如下所示。该方法中最后执行RunServer()方法启动http server监听请求服务,并进行相应的资源处理。

  1. // Serve runs the secure http server. It fails only if certificates cannot be loaded or the initial listen call fails.
  2. // The actual server loop (stoppable by closing stopCh) runs in a go routine, i.e. Serve does not block.
  3. // It returns a stoppedCh that is closed when all non-hijacked active requests have been processed.
  4. func (s *SecureServingInfo) Serve(handler http.Handler, shutdownTimeout time.Duration, stopCh <-chan struct{}) (<-chan struct{}, error) {
  5. if s.Listener == nil {
  6. return nil, fmt.Errorf("listener must not be nil")
  7. }
  8. tlsConfig, err := s.tlsConfig(stopCh)
  9. if err != nil {
  10. return nil, err
  11. }
  12. secureServer := &http.Server{
  13. Addr: s.Listener.Addr().String(),
  14. Handler: handler,
  15. MaxHeaderBytes: 1 << 20,
  16. TLSConfig: tlsConfig,
  17. }
  18. // At least 99% of serialized resources in surveyed clusters were smaller than 256kb.
  19. // This should be big enough to accommodate most API POST requests in a single frame,
  20. // and small enough to allow a per connection buffer of this size multiplied by `MaxConcurrentStreams`.
  21. const resourceBody99Percentile = 256 * 1024
  22. http2Options := &http2.Server{}
  23. // shrink the per-stream buffer and max framesize from the 1MB default while still accommodating most API POST requests in a single frame
  24. http2Options.MaxUploadBufferPerStream = resourceBody99Percentile
  25. http2Options.MaxReadFrameSize = resourceBody99Percentile
  26. // use the overridden concurrent streams setting or make the default of 250 explicit so we can size MaxUploadBufferPerConnection appropriately
  27. if s.HTTP2MaxStreamsPerConnection > 0 {
  28. http2Options.MaxConcurrentStreams = uint32(s.HTTP2MaxStreamsPerConnection)
  29. } else {
  30. http2Options.MaxConcurrentStreams = 250
  31. }
  32. // increase the connection buffer size from the 1MB default to handle the specified number of concurrent streams
  33. http2Options.MaxUploadBufferPerConnection = http2Options.MaxUploadBufferPerStream * int32(http2Options.MaxConcurrentStreams)
  34. if !s.DisableHTTP2 {
  35. // apply settings to the server
  36. if err := http2.ConfigureServer(secureServer, http2Options); err != nil {
  37. return nil, fmt.Errorf("error configuring http2: %v", err)
  38. }
  39. }
  40. // use tlsHandshakeErrorWriter to handle messages of tls handshake error
  41. tlsErrorWriter := &tlsHandshakeErrorWriter{os.Stderr}
  42. tlsErrorLogger := log.New(tlsErrorWriter, "", 0)
  43. secureServer.ErrorLog = tlsErrorLogger
  44. klog.Infof("Serving securely on %s", secureServer.Addr)
  45. return RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
  46. }

3.4 各Rest请求处理的Handler构建

在上面3.2节中的Run方法中,通过CreateServerChain方法构建ApiServer服务请求的整套体系,该方法中调用CreateKubeAPIServer()方法,该方法中kubeAPIServer, err := kubeAPIServerConfig.Complete().New(delegateAPIServer)对New方法的调用,然后对s, err := c.GenericConfig.New("kube-apiserver", delegationTarget)方法调用,在NewAPIServerHandler方法中创建了路由关系的处理,即:gorestfulContainer.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}

具体源码见:vendor/k8s.io/apiserver/pkg/server/handler.go:73 NewAPIServerHandler() 如下:

  1. cmd/kube-apiserver/app/server.go:228
  2. // CreateKubeAPIServer creates and wires a workable kube-apiserver
  3. func CreateKubeAPIServer(kubeAPIServerConfig *controlplane.Config, delegateAPIServer genericapiserver.DelegationTarget) (*controlplane.Instance, error) {
  4. kubeAPIServer, err := kubeAPIServerConfig.Complete().New(delegateAPIServer)
  5. if err != nil {
  6. return nil, err
  7. }
  8. return kubeAPIServer, nil
  9. }
  10. vendor/k8s.io/apiserver/pkg/server/config.go:522
  11. / New creates a new server which logically combines the handling chain with the passed server.
  12. // name is used to differentiate for logging. The handler chain in particular can be difficult as it starts delgating.
  13. // delegationTarget may not be nil.
  14. func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*GenericAPIServer, error) {
  15. ..........
  16. apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler())
  17. ..........
  18. }
  19. vendor/k8s.io/apiserver/pkg/server/handler.go:73
  20. func NewAPIServerHandler(name string, s runtime.NegotiatedSerializer, handlerChainBuilder HandlerChainBuilderFn, notFoundHandler http.Handler) *APIServerHandler {
  21. nonGoRestfulMux := mux.NewPathRecorderMux(name)
  22. if notFoundHandler != nil {
  23. nonGoRestfulMux.NotFoundHandler(notFoundHandler)
  24. }
  25. gorestfulContainer := restful.NewContainer()
  26. gorestfulContainer.ServeMux = http.NewServeMux()
  27. gorestfulContainer.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
  28. gorestfulContainer.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
  29. logStackOnRecover(s, panicReason, httpWriter)
  30. })
  31. gorestfulContainer.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
  32. serviceErrorHandler(s, serviceErr, request, response)
  33. })
  34. director := director{
  35. name: name,
  36. goRestfulContainer: gorestfulContainer,
  37. nonGoRestfulMux: nonGoRestfulMux,
  38. }
  39. return &APIServerHandler{
  40. FullHandlerChain: handlerChainBuilder(director),
  41. GoRestfulContainer: gorestfulContainer,
  42. NonGoRestfulMux: nonGoRestfulMux,
  43. Director: director,
  44. }
  45. }

关于各个Rest的处理,依赖各个WebService服务进行处理,通过循环匹配path和webservice对象,实现服务之间的映射匹配,选择匹配最成功的一个进行相应的request的处理,具体的参考源码vendor/github.com/emicklei/go-restful/curly.go:19 CurlyRouter.SelectRoute()方法,具体源码如下:

  1. vendor/github.com/emicklei/go-restful/curly.go:19
  2. // SelectRoute is part of the Router interface and returns the best match
  3. // for the WebService and its Route for the given Request.
  4. func (c CurlyRouter) SelectRoute(
  5. webServices []*WebService,
  6. httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
  7. requestTokens := tokenizePath(httpRequest.URL.Path)
  8. detectedService := c.detectWebService(requestTokens, webServices)
  9. if detectedService == nil {
  10. if trace {
  11. traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
  12. }
  13. return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
  14. }
  15. candidateRoutes := c.selectRoutes(detectedService, requestTokens)
  16. if len(candidateRoutes) == 0 {
  17. if trace {
  18. traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
  19. }
  20. return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
  21. }
  22. selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
  23. if selectedRoute == nil {
  24. return detectedService, nil, err
  25. }
  26. return detectedService, selectedRoute, nil
  27. }
  28. vendor/github.com/emicklei/go-restful/curly.go:122
  29. // detectWebService returns the best matching webService given the list of path tokens.
  30. // see also computeWebserviceScore
  31. func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
  32. var best *WebService
  33. score := -1
  34. for _, each := range webServices {
  35. matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
  36. if matches && (eachScore > score) {
  37. best = each
  38. score = eachScore
  39. }
  40. }
  41. return best
  42. }

关于WebService与Container的关系,参考源码vendor/github.com/emicklei/go-restful/container.go:88 Add()方法实现将WebService添加到Container对象中。具体的关于服务启动的时候,何时添加服务到Container中,有待进一步分析相关源码。关于WebService struct源码中描述如下: WebService holds a collection of Route values that bind a Http Method + URL Path to a function.

  1. vendor/github.com/emicklei/go-restful/container.go:88
  2. // Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
  3. func (c *Container) Add(service *WebService) *Container {
  4. c.webServicesLock.Lock()
  5. defer c.webServicesLock.Unlock()
  6. // if rootPath was not set then lazy initialize it
  7. if len(service.rootPath) == 0 {
  8. service.Path("/")
  9. }
  10. // cannot have duplicate root paths
  11. for _, each := range c.webServices {
  12. if each.RootPath() == service.RootPath() {
  13. log.Printf("WebService with duplicate root path detected:['%v']", each)
  14. os.Exit(1)
  15. }
  16. }
  17. // If not registered on root then add specific mapping
  18. if !c.isRegisteredOnRoot {
  19. c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
  20. }
  21. c.webServices = append(c.webServices, service)
  22. return c
  23. }

pkg/kubelet/cri/streaming/server.go:108 NewServer

4.总结

上述章节,以kubectl create为例,阐述了通过kubectl命令行,创建Command命令,然后通过client-go的rest方法发送请求到服务端的关键流程节点;然后描述了服务端apiserver的启动流程,以及WebService服务注册以及针对http request的路由处理链路,从而实现了端到端的整个流程的流转。关于WebService的构建以及后续的服务逻辑的处理,后续在逐步研究分享。

 

5.参考文章

https://blog.csdn.net/u014458692/article/details/108068088

https://kubernetes.io/zh/docs/concepts/architecture/cloud-controller/

https://www.cnblogs.com/waken-captain/p/10509705.html

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/671830
推荐阅读
相关标签
  

闽ICP备14008679号