1
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
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
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
209
210
211
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
219
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
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