...

Source file src/k8s.io/kubectl/pkg/cmd/scale/scale.go

Documentation: k8s.io/kubectl/pkg/cmd/scale

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package scale
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/spf13/cobra"
    24  	"k8s.io/klog/v2"
    25  
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"k8s.io/cli-runtime/pkg/genericclioptions"
    29  	"k8s.io/cli-runtime/pkg/genericiooptions"
    30  	"k8s.io/cli-runtime/pkg/printers"
    31  	"k8s.io/cli-runtime/pkg/resource"
    32  	"k8s.io/client-go/kubernetes"
    33  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    34  	"k8s.io/kubectl/pkg/scale"
    35  	"k8s.io/kubectl/pkg/util/completion"
    36  	"k8s.io/kubectl/pkg/util/i18n"
    37  	"k8s.io/kubectl/pkg/util/templates"
    38  )
    39  
    40  var (
    41  	scaleLong = templates.LongDesc(i18n.T(`
    42  		Set a new size for a deployment, replica set, replication controller, or stateful set.
    43  
    44  		Scale also allows users to specify one or more preconditions for the scale action.
    45  
    46  		If --current-replicas or --resource-version is specified, it is validated before the
    47  		scale is attempted, and it is guaranteed that the precondition holds true when the
    48  		scale is sent to the server.`))
    49  
    50  	scaleExample = templates.Examples(i18n.T(`
    51  		# Scale a replica set named 'foo' to 3
    52  		kubectl scale --replicas=3 rs/foo
    53  
    54  		# Scale a resource identified by type and name specified in "foo.yaml" to 3
    55  		kubectl scale --replicas=3 -f foo.yaml
    56  
    57  		# If the deployment named mysql's current size is 2, scale mysql to 3
    58  		kubectl scale --current-replicas=2 --replicas=3 deployment/mysql
    59  
    60  		# Scale multiple replication controllers
    61  		kubectl scale --replicas=5 rc/example1 rc/example2 rc/example3
    62  
    63  		# Scale stateful set named 'web' to 3
    64  		kubectl scale --replicas=3 statefulset/web`))
    65  )
    66  
    67  type ScaleOptions struct {
    68  	FilenameOptions resource.FilenameOptions
    69  	RecordFlags     *genericclioptions.RecordFlags
    70  	PrintFlags      *genericclioptions.PrintFlags
    71  	PrintObj        printers.ResourcePrinterFunc
    72  
    73  	Selector        string
    74  	All             bool
    75  	Replicas        int
    76  	ResourceVersion string
    77  	CurrentReplicas int
    78  	Timeout         time.Duration
    79  
    80  	Recorder                     genericclioptions.Recorder
    81  	builder                      *resource.Builder
    82  	namespace                    string
    83  	enforceNamespace             bool
    84  	args                         []string
    85  	shortOutput                  bool
    86  	clientSet                    kubernetes.Interface
    87  	scaler                       scale.Scaler
    88  	unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
    89  	parent                       string
    90  	dryRunStrategy               cmdutil.DryRunStrategy
    91  
    92  	genericiooptions.IOStreams
    93  }
    94  
    95  func NewScaleOptions(ioStreams genericiooptions.IOStreams) *ScaleOptions {
    96  	return &ScaleOptions{
    97  		PrintFlags:      genericclioptions.NewPrintFlags("scaled"),
    98  		RecordFlags:     genericclioptions.NewRecordFlags(),
    99  		CurrentReplicas: -1,
   100  		Recorder:        genericclioptions.NoopRecorder{},
   101  		IOStreams:       ioStreams,
   102  	}
   103  }
   104  
   105  // NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale
   106  func NewCmdScale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   107  	o := NewScaleOptions(ioStreams)
   108  
   109  	validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
   110  
   111  	cmd := &cobra.Command{
   112  		Use:                   "scale [--resource-version=version] [--current-replicas=count] --replicas=COUNT (-f FILENAME | TYPE NAME)",
   113  		DisableFlagsInUseLine: true,
   114  		Short:                 i18n.T("Set a new size for a deployment, replica set, or replication controller"),
   115  		Long:                  scaleLong,
   116  		Example:               scaleExample,
   117  		ValidArgsFunction:     completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs),
   118  		Run: func(cmd *cobra.Command, args []string) {
   119  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   120  			cmdutil.CheckErr(o.Validate())
   121  			cmdutil.CheckErr(o.RunScale())
   122  		},
   123  	}
   124  
   125  	o.RecordFlags.AddFlags(cmd)
   126  	o.PrintFlags.AddFlags(cmd)
   127  
   128  	cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types")
   129  	cmd.Flags().StringVar(&o.ResourceVersion, "resource-version", o.ResourceVersion, i18n.T("Precondition for resource version. Requires that the current resource version match this value in order to scale."))
   130  	cmd.Flags().IntVar(&o.CurrentReplicas, "current-replicas", o.CurrentReplicas, "Precondition for current size. Requires that the current size of the resource match this value in order to scale. -1 (default) for no condition.")
   131  	cmd.Flags().IntVar(&o.Replicas, "replicas", o.Replicas, "The new desired number of replicas. Required.")
   132  	cmd.MarkFlagRequired("replicas")
   133  	cmd.Flags().DurationVar(&o.Timeout, "timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
   134  	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to set a new size")
   135  	cmdutil.AddDryRunFlag(cmd)
   136  	cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
   137  	return cmd
   138  }
   139  
   140  func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   141  	var err error
   142  	o.RecordFlags.Complete(cmd)
   143  	o.Recorder, err = o.RecordFlags.ToRecorder()
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
   153  	printer, err := o.PrintFlags.ToPrinter()
   154  	if err != nil {
   155  		return err
   156  	}
   157  	o.PrintObj = printer.PrintObj
   158  
   159  	o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   160  	if err != nil {
   161  		return err
   162  	}
   163  	o.builder = f.NewBuilder()
   164  	o.args = args
   165  	o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
   166  	o.clientSet, err = f.KubernetesClientSet()
   167  	if err != nil {
   168  		return err
   169  	}
   170  	o.scaler, err = scaler(f)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	o.unstructuredClientForMapping = f.UnstructuredClientForMapping
   175  	o.parent = cmd.Parent().Name()
   176  
   177  	return nil
   178  }
   179  
   180  func (o *ScaleOptions) Validate() error {
   181  	if o.Replicas < 0 {
   182  		return fmt.Errorf("The --replicas=COUNT flag is required, and COUNT must be greater than or equal to 0")
   183  	}
   184  
   185  	if o.CurrentReplicas < -1 {
   186  		return fmt.Errorf("The --current-replicas must specify an integer of -1 or greater")
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  // RunScale executes the scaling
   193  func (o *ScaleOptions) RunScale() error {
   194  	r := o.builder.
   195  		Unstructured().
   196  		ContinueOnError().
   197  		NamespaceParam(o.namespace).DefaultNamespace().
   198  		FilenameParam(o.enforceNamespace, &o.FilenameOptions).
   199  		ResourceTypeOrNameArgs(o.All, o.args...).
   200  		Flatten().
   201  		LabelSelectorParam(o.Selector).
   202  		Do()
   203  	err := r.Err()
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	// We don't immediately return infoErr if it is not nil.
   209  	// Because we want to proceed for other valid resources and
   210  	// at the end of the function, we'll return this
   211  	// to show invalid resources to the user.
   212  	infos, infoErr := r.Infos()
   213  
   214  	if len(o.ResourceVersion) != 0 && len(infos) > 1 {
   215  		return fmt.Errorf("cannot use --resource-version with multiple resources")
   216  	}
   217  
   218  	// only set a precondition if the user has requested one.  A nil precondition means we can do a blind update, so
   219  	// we avoid a Scale GET that may or may not succeed
   220  	var precondition *scale.ScalePrecondition
   221  	if o.CurrentReplicas != -1 || len(o.ResourceVersion) > 0 {
   222  		precondition = &scale.ScalePrecondition{Size: o.CurrentReplicas, ResourceVersion: o.ResourceVersion}
   223  	}
   224  	retry := scale.NewRetryParams(1*time.Second, 5*time.Minute)
   225  
   226  	var waitForReplicas *scale.RetryParams
   227  	if o.Timeout != 0 && o.dryRunStrategy == cmdutil.DryRunNone {
   228  		waitForReplicas = scale.NewRetryParams(1*time.Second, o.Timeout)
   229  	}
   230  
   231  	if len(infos) == 0 {
   232  		return fmt.Errorf("no objects passed to scale")
   233  	}
   234  
   235  	for _, info := range infos {
   236  		mapping := info.ResourceMapping()
   237  		if o.dryRunStrategy == cmdutil.DryRunClient {
   238  			if err := o.PrintObj(info.Object, o.Out); err != nil {
   239  				return err
   240  			}
   241  			continue
   242  		}
   243  
   244  		if err := o.scaler.Scale(info.Namespace, info.Name, uint(o.Replicas), precondition, retry, waitForReplicas, mapping.Resource, o.dryRunStrategy == cmdutil.DryRunServer); err != nil {
   245  			return err
   246  		}
   247  
   248  		// if the recorder makes a change, compute and create another patch
   249  		if mergePatch, err := o.Recorder.MakeRecordMergePatch(info.Object); err != nil {
   250  			klog.V(4).Infof("error recording current command: %v", err)
   251  		} else if len(mergePatch) > 0 {
   252  			client, err := o.unstructuredClientForMapping(mapping)
   253  			if err != nil {
   254  				return err
   255  			}
   256  			helper := resource.NewHelper(client, mapping)
   257  			if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch, nil); err != nil {
   258  				klog.V(4).Infof("error recording reason: %v", err)
   259  			}
   260  		}
   261  
   262  		err := o.PrintObj(info.Object, o.Out)
   263  		if err != nil {
   264  			return err
   265  		}
   266  	}
   267  
   268  	return infoErr
   269  }
   270  
   271  func scaler(f cmdutil.Factory) (scale.Scaler, error) {
   272  	scalesGetter, err := cmdutil.ScaleClientFn(f)
   273  	if err != nil {
   274  		return nil, err
   275  	}
   276  
   277  	return scale.NewScaler(scalesGetter), nil
   278  }
   279  

View as plain text