欢迎关注我的公众号:

kubectl 源码分析之run_sed

 

————————————————-------------------------------------------------------------

type RunOptions struct {//run 结构体
PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags

DeleteFlags *delete.DeleteFlags
DeleteOptions *delete.DeleteOptions

DryRun bool

PrintObj func(runtime.Object) error
Recorder genericclioptions.Recorder

DynamicClient dynamic.Interface

ArgsLenAtDash int
Attach bool
Expose bool
Generator string
Image string
Interactive bool
LeaveStdinOpen bool
Port string
Quiet bool
Schedule string
TTY bool

genericclioptions.IOStreams
}
func NewRunOptions(streams genericclioptions.IOStreams) *RunOptions {
return &RunOptions{//初始化run结构体
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
DeleteFlags: delete.NewDeleteFlags("to use to replace the resource."),
RecordFlags: genericclioptions.NewRecordFlags(),

Recorder: genericclioptions.NoopRecorder{},

IOStreams: streams,
}
}
//创建run命令
func NewCmdRun(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewRunOptions(streams)//初始化结构体

cmd := &cobra.Command{//创建cobra命令
Use: "run NAME --image=image [--env=\"key=value\"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [--command] -- [COMMAND] [args...]",
DisableFlagsInUseLine: true,
Short: i18n.T("Run a particular image on the cluster"),
Long: runLong,
Example: runExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd))//准备运行
cmdutil.CheckErr(o.Run(f, cmd, args))//运行
},
}

o.DeleteFlags.AddFlags(cmd)//添加删除选项
o.PrintFlags.AddFlags(cmd)//添加print选项
o.RecordFlags.AddFlags(cmd)//添加record选项

addRunFlags(cmd, o)//添加run选项
cmdutil.AddApplyAnnotationFlags(cmd)//添加save-config选项
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)//添加pod-running-timeout选项
return cmd
}

func addRunFlags(cmd *cobra.Command, opt *RunOptions) {
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringVar(&opt.Generator, "generator", opt.Generator, i18n.T("The name of the API generator to use, see http://kubernetes.io/docs/user-guide/kubectl-conventions/#generators for a list."))
cmd.Flags().StringVar(&opt.Image, "image", opt.Image, i18n.T("The image for the container to run."))
cmd.MarkFlagRequired("image")
cmd.Flags().String("image-pull-policy", "", i18n.T("The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server"))
cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default is 1.")
cmd.Flags().Bool("rm", false, "If true, delete resources created in this command for attached containers.")
cmd.Flags().String("overrides", "", i18n.T("An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field."))
cmd.Flags().StringArray("env", []string{}, "Environment variables to set in the container")
cmd.Flags().String("serviceaccount", "", "Service account to set in the pod spec")
cmd.Flags().StringVar(&opt.Port, "port", opt.Port, i18n.T("The port that this container exposes. If --expose is true, this is also the port used by the service that is created."))
cmd.Flags().Int("hostport", -1, "The host port mapping for the container port. To demonstrate a single-machine container.")
cmd.Flags().StringP("labels", "l", "", "Comma separated labels to apply to the pod(s). Will override previous values.")
cmd.Flags().BoolVarP(&opt.Interactive, "stdin", "i", opt.Interactive, "Keep stdin open on the container(s) in the pod, even if nothing is attached.")
cmd.Flags().BoolVarP(&opt.TTY, "tty", "t", opt.TTY, "Allocated a TTY for each container in the pod.")
cmd.Flags().BoolVar(&opt.Attach, "attach", opt.Attach, "If true, wait for the Pod to start running, and then attach to the Pod as if 'kubectl attach ...' were called. Default false, unless '-i/--stdin' is set, in which case the default is true. With '--restart=Never' the exit code of the container process is returned.")
cmd.Flags().BoolVar(&opt.LeaveStdinOpen, "leave-stdin-open", opt.LeaveStdinOpen, "If the pod is started in interactive mode or with stdin, leave stdin open after the first attach completes. By default, stdin will be closed after the first attach completes.")
cmd.Flags().String("restart", "Always", i18n.T("The restart policy for this Pod. Legal values [Always, OnFailure, Never]. If set to 'Always' a deployment is created, if set to 'OnFailure' a job is created, if set to 'Never', a regular pod is created. For the latter two --replicas must be 1. Default 'Always', for CronJobs `Never`."))
cmd.Flags().Bool("command", false, "If true and extra arguments are present, use them as the 'command' field in the container, rather than the 'args' field which is the default.")
cmd.Flags().String("requests", "", i18n.T("The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'. Note that server side components may assign requests depending on the server configuration, such as limit ranges."))
cmd.Flags().String("limits", "", i18n.T("The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'. Note that server side components may assign limits depending on the server configuration, such as limit ranges."))
cmd.Flags().BoolVar(&opt.Expose, "expose", opt.Expose, "If true, a public, external service is created for the container(s) which are run")
cmd.Flags().String("service-generator", "service/v2", i18n.T("The name of the generator to use for creating a service. Only used if --expose is true"))
cmd.Flags().String("service-overrides", "", i18n.T("An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true."))
cmd.Flags().BoolVar(&opt.Quiet, "quiet", opt.Quiet, "If true, suppress prompt messages.")
cmd.Flags().StringVar(&opt.Schedule, "schedule", opt.Schedule, i18n.T("A schedule in the Cron format the job should be run with."))
}
//准备运行
func (o *RunOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
var err error

o.RecordFlags.Complete(cmd)//complete record
o.Recorder, err = o.RecordFlags.ToRecorder()//record flag 转recorder
if err != nil {
return err
}

o.DynamicClient, err = f.DynamicClient()//设置client
if err != nil {
return err
}

o.ArgsLenAtDash = cmd.ArgsLenAtDash()
o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")//设置dry-run

attachFlag := cmd.Flags().Lookup("attach")
if !attachFlag.Changed && o.Interactive {
o.Attach = true//设置attach
}

if o.DryRun {
o.PrintFlags.Complete("%s (dry run)")//干跑complete
}
printer, err := o.PrintFlags.ToPrinter()//print flag转printer
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object) error {//设置printObj函数
return printer.PrintObj(obj, o.Out)
}

deleteOpts := o.DeleteFlags.ToOptions(o.DynamicClient, o.IOStreams)//delete flag转delete options
deleteOpts.IgnoreNotFound = true
deleteOpts.WaitForDeletion = false
deleteOpts.GracePeriod = -1
deleteOpts.Quiet = o.Quiet

o.DeleteOptions = deleteOpts//设置delete options

return nil
}
//运行命令
func (o *RunOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
// Let kubectl run follow rules for `--`, see #13004 issue
if len(args) == 0 || o.ArgsLenAtDash == 0 {//没有参数报错
return cmdutil.UsageErrorf(cmd, "NAME is required for run")
}

timeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd)//获取podrunningtimeout
if err != nil {
return cmdutil.UsageErrorf(cmd, "%v", err)
}

// validate image name
imageName := o.Image
if imageName == "" {//image为空报错
return fmt.Errorf("--image is required")
}
validImageRef := reference.ReferenceRegexp.MatchString(imageName)//校验image是否有效
if !validImageRef {
return fmt.Errorf("Invalid image name %q: %v", imageName, reference.ErrReferenceInvalidFormat)
}

if o.TTY && !o.Interactive {//如果指定了tty没有指定-i,则报错
return cmdutil.UsageErrorf(cmd, "-i/--stdin is required for containers with -t/--tty=true")
}
replicas := cmdutil.GetFlagInt(cmd, "replicas")//获取副本数量
if o.Interactive && replicas != 1 {//如果指定了-i,副本数量不是1,报错
return cmdutil.UsageErrorf(cmd, "-i/--stdin requires that replicas is 1, found %d", replicas)
}
if o.Expose && len(o.Port) == 0 {//如果指定了expose,没有指定port报错
return cmdutil.UsageErrorf(cmd, "--port must be set when exposing a service")
}

namespace, _, err := f.ToRawKubeConfigLoader().Namespace()//获取名称空间
if err != nil {
return err
}
restartPolicy, err := getRestartPolicy(cmd, o.Interactive)//获取重启策略
if err != nil {
return err
}
if restartPolicy != corev1.RestartPolicyAlways && replicas != 1 {//如果重启策略不是always,并且副本不是1,报错
return cmdutil.UsageErrorf(cmd, "--restart=%s requires that --replicas=1, found %d", restartPolicy, replicas)
}

remove := cmdutil.GetFlagBool(cmd, "rm")//获取rm
if !o.Attach && remove {//如果没有指定attach,rm为true报错
return cmdutil.UsageErrorf(cmd, "--rm should only be used for attached containers")
}

if o.Attach && o.DryRun {// 如果同时指定了attach和dry-run报错
return cmdutil.UsageErrorf(cmd, "--dry-run can't be used with attached containers options (--attach, --stdin, or --tty)")
}

if err := verifyImagePullPolicy(cmd); err != nil {//验证image pull policy是否有效
return err
}

clientset, err := f.KubernetesClientSet()//获取clientset
if err != nil {
return err
}

generatorName := o.Generator
if len(o.Schedule) != 0 && len(generatorName) == 0 {//如果schedule不为空,gneeratorName为空则,为cron的gneeratorName
generatorName = generateversioned.CronJobV1Beta1GeneratorName
}
if len(generatorName) == 0 {//如果generatorName为空
switch restartPolicy {//根据restartPolicy设置generatorName
case corev1.RestartPolicyAlways:
generatorName = generateversioned.DeploymentAppsV1GeneratorName
case corev1.RestartPolicyOnFailure:
generatorName = generateversioned.JobV1GeneratorName
case corev1.RestartPolicyNever:
generatorName = generateversioned.RunPodV1GeneratorName
}

// Falling back because the generator was not provided and the default one could be unavailable.
generatorNameTemp, err := generateversioned.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), o.ErrOut)//判断generatorname是否需要回退
if err != nil {
return err
}
if generatorNameTemp != generatorName {
cmdutil.Warning(o.ErrOut, generatorName, generatorNameTemp)
} else {
generatorName = generatorNameTemp
}
}

// start deprecating all generators except for 'run-pod/v1' which will be
// the only supported on a route to simple kubectl run which should mimic
// docker run
if generatorName != generateversioned.RunPodV1GeneratorName {//如果generatorName不是pod的打印警告
fmt.Fprintf(o.ErrOut, "kubectl run --generator=%s is DEPRECATED and will be removed in a future version. Use kubectl run --generator=%s or kubectl create instead.\n", generatorName, generateversioned.RunPodV1GeneratorName)
}

generators := generateversioned.GeneratorFn("run")//获取run的所有generator
generator, found := generators[generatorName]//根据generatorName获取generator
if !found {
return cmdutil.UsageErrorf(cmd, "generator %q not found", generatorName)
}
names := generator.ParamNames()//获取generator参数名称
params := generate.MakeParams(cmd, names)//生成generator参数
params["name"] = args[0]//设置name的参数
if len(args) > 1 {
params["args"] = args[1:]//设置args的generator参数
}

params["env"] = cmdutil.GetFlagStringArray(cmd, "env")//设置env的generator参数

var createdObjects = []*RunObject{}
runObject, err := o.createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "overrides"), namespace)//创建对象
if err != nil {
return err
}
createdObjects = append(createdObjects, runObject)

allErrs := []error{}
if o.Expose {//如果指定了expose
serviceGenerator := cmdutil.GetFlagString(cmd, "service-generator")//获取service的generator
if len(serviceGenerator) == 0 {
return cmdutil.UsageErrorf(cmd, "No service generator specified")
}
serviceRunObject, err := o.generateService(f, cmd, serviceGenerator, params, namespace)//生成service
if err != nil {
allErrs = append(allErrs, err)
} else {
createdObjects = append(createdObjects, serviceRunObject)
}
}

if o.Attach {//如果指定了attach
if remove {//如果指定了--rm
defer o.removeCreatedObjects(f, createdObjects)//删除创建的对象
}

opts := &attach.AttachOptions{//头灶attachOption对象
StreamOptions: exec.StreamOptions{
IOStreams: o.IOStreams,
Stdin: o.Interactive,
TTY: o.TTY,
Quiet: o.Quiet,
},
GetPodTimeout: timeout,
CommandName: cmd.Parent().CommandPath() + " attach",

Attach: &attach.DefaultRemoteAttach{},
}
config, err := f.ToRESTConfig()//获取restConfig
if err != nil {
return err
}
opts.Config = config
opts.AttachFunc = attach.DefaultAttachFunc//设置attach函数

clientset, err := kubernetes.NewForConfig(config)//根据restconfig获取clientset
if err != nil {
return err
}

attachablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, runObject.Object, opts.GetPodTimeout)//获取attach的pod
if err != nil {
return err
}
err = handleAttachPod(f, clientset.CoreV1(), attachablePod.Namespace, attachablePod.Name, opts)//处理attach
if err != nil {
return err
}

var pod *corev1.Pod
leaveStdinOpen := o.LeaveStdinOpen
waitForExitCode := !leaveStdinOpen && restartPolicy == corev1.RestartPolicyNever
if waitForExitCode {
pod, err = waitForPod(clientset.CoreV1(), attachablePod.Namespace, attachablePod.Name, podCompleted)//等待attach完成
if err != nil {
return err
}
}

// after removal is done, return successfully if we are not interested in the exit code
if !waitForExitCode {
return nil
}

switch pod.Status.Phase {//判断pod状态
case corev1.PodSucceeded:
return nil
case corev1.PodFailed:
unknownRcErr := fmt.Errorf("pod %s/%s failed with unknown exit code", pod.Namespace, pod.Name)
if len(pod.Status.ContainerStatuses) == 0 || pod.Status.ContainerStatuses[0].State.Terminated == nil {
return unknownRcErr
}
// assume here that we have at most one status because kubectl-run only creates one container per pod
rc := pod.Status.ContainerStatuses[0].State.Terminated.ExitCode
if rc == 0 {
return unknownRcErr
}
return uexec.CodeExitError{
Err: fmt.Errorf("pod %s/%s terminated (%s)\n%s", pod.Namespace, pod.Name, pod.Status.ContainerStatuses[0].State.Terminated.Reason, pod.Status.ContainerStatuses[0].State.Terminated.Message),
Code: int(rc),
}
default:
return fmt.Errorf("pod %s/%s left in phase %s", pod.Namespace, pod.Name, pod.Status.Phase)
}

}
if runObject != nil {
if err := o.PrintObj(runObject.Object); err != nil {//打印对象
return err
}
}

return utilerrors.NewAggregate(allErrs)
}
//生成对象
func (o *RunOptions) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command, generator generate.Generator, names []generate.GeneratorParam, params map[string]interface{}, overrides, namespace string) (*RunObject, error) {
err := generate.ValidateParams(names, params)//验证generator参数
if err != nil {
return nil, err
}

// TODO: Validate flag usage against selected generator. More tricky since --expose was added.
obj, err := generator.Generate(params)//用generator生成对象
if err != nil {
return nil, err
}

mapper, err := f.ToRESTMapper()//获取mapper
if err != nil {
return nil, err
}
// run has compiled knowledge of the thing is creating
gvks, _, err := scheme.Scheme.ObjectKinds(obj)//从obj获取gvk
if err != nil {
return nil, err
}
mapping, err := mapper.RESTMapping(gvks[0].GroupKind(), gvks[0].Version)//获取mapping
if err != nil {
return nil, err
}

if len(overrides) > 0 {
codec := runtime.NewCodec(scheme.DefaultJSONEncoder(), scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...))
obj, err = cmdutil.Merge(codec, obj, overrides)//合并对象和overrides
if err != nil {
return nil, err
}
}

if err := o.Recorder.Record(obj); err != nil {//创建change-cause注解
klog.V(4).Infof("error recording current command: %v", err)
}

actualObj := obj
if !o.DryRun {
if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), obj, scheme.DefaultJSONEncoder()); err != nil {//创建last-applied-configuration注解
return nil, err
}
client, err := f.ClientForMapping(mapping)//获取client
if err != nil {
return nil, err
}
actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj, nil)//创建对象
if err != nil {
return nil, err
}
}

return &RunObject{//返回结果
Object: actualObj,
Mapping: mapping,
}, nil
}