1
16
17 package autoscale
18
19 import (
20 "context"
21 "fmt"
22
23 "github.com/spf13/cobra"
24 "k8s.io/klog/v2"
25
26 autoscalingv1 "k8s.io/api/autoscaling/v1"
27 "k8s.io/apimachinery/pkg/api/meta"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/cli-runtime/pkg/genericclioptions"
30 "k8s.io/cli-runtime/pkg/genericiooptions"
31 "k8s.io/cli-runtime/pkg/printers"
32 "k8s.io/cli-runtime/pkg/resource"
33 autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
34 "k8s.io/client-go/scale"
35 cmdutil "k8s.io/kubectl/pkg/cmd/util"
36 "k8s.io/kubectl/pkg/scheme"
37 "k8s.io/kubectl/pkg/util"
38 "k8s.io/kubectl/pkg/util/completion"
39 "k8s.io/kubectl/pkg/util/i18n"
40 "k8s.io/kubectl/pkg/util/templates"
41 )
42
43 var (
44 autoscaleLong = templates.LongDesc(i18n.T(`
45 Creates an autoscaler that automatically chooses and sets the number of pods that run in a Kubernetes cluster.
46
47 Looks up a deployment, replica set, stateful set, or replication controller by name and creates an autoscaler that uses the given resource as a reference.
48 An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`))
49
50 autoscaleExample = templates.Examples(i18n.T(`
51 # Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used
52 kubectl autoscale deployment foo --min=2 --max=10
53
54 # Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%
55 kubectl autoscale rc foo --max=5 --cpu-percent=80`))
56 )
57
58
59 type AutoscaleOptions struct {
60 FilenameOptions *resource.FilenameOptions
61
62 RecordFlags *genericclioptions.RecordFlags
63 Recorder genericclioptions.Recorder
64
65 PrintFlags *genericclioptions.PrintFlags
66 ToPrinter func(string) (printers.ResourcePrinter, error)
67
68 Name string
69 Min int32
70 Max int32
71 CPUPercent int32
72
73 createAnnotation bool
74 args []string
75 enforceNamespace bool
76 namespace string
77 dryRunStrategy cmdutil.DryRunStrategy
78 builder *resource.Builder
79 fieldManager string
80
81 HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter
82 scaleKindResolver scale.ScaleKindResolver
83
84 genericiooptions.IOStreams
85 }
86
87
88 func NewAutoscaleOptions(ioStreams genericiooptions.IOStreams) *AutoscaleOptions {
89 return &AutoscaleOptions{
90 PrintFlags: genericclioptions.NewPrintFlags("autoscaled").WithTypeSetter(scheme.Scheme),
91 FilenameOptions: &resource.FilenameOptions{},
92 RecordFlags: genericclioptions.NewRecordFlags(),
93 Recorder: genericclioptions.NoopRecorder{},
94
95 IOStreams: ioStreams,
96 }
97 }
98
99
100 func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
101 o := NewAutoscaleOptions(ioStreams)
102
103 validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
104
105 cmd := &cobra.Command{
106 Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
107 DisableFlagsInUseLine: true,
108 Short: i18n.T("Auto-scale a deployment, replica set, stateful set, or replication controller"),
109 Long: autoscaleLong,
110 Example: autoscaleExample,
111 ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs),
112 Run: func(cmd *cobra.Command, args []string) {
113 cmdutil.CheckErr(o.Complete(f, cmd, args))
114 cmdutil.CheckErr(o.Validate())
115 cmdutil.CheckErr(o.Run())
116 },
117 }
118
119
120 o.RecordFlags.AddFlags(cmd)
121 o.PrintFlags.AddFlags(cmd)
122 cmd.Flags().Int32Var(&o.Min, "min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
123 cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
124 cmd.MarkFlagRequired("max")
125 cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, "The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.")
126 cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
127 cmdutil.AddDryRunFlag(cmd)
128 cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
129 cmdutil.AddApplyAnnotationFlags(cmd)
130 cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-autoscale")
131 return cmd
132 }
133
134
135 func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
136 var err error
137 o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
138 if err != nil {
139 return err
140 }
141 discoveryClient, err := f.ToDiscoveryClient()
142 if err != nil {
143 return err
144 }
145 o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
146 o.builder = f.NewBuilder()
147 o.scaleKindResolver = scale.NewDiscoveryScaleKindResolver(discoveryClient)
148 o.args = args
149 o.RecordFlags.Complete(cmd)
150
151 o.Recorder, err = o.RecordFlags.ToRecorder()
152 if err != nil {
153 return err
154 }
155
156 kubeClient, err := f.KubernetesClientSet()
157 if err != nil {
158 return err
159 }
160 o.HPAClient = kubeClient.AutoscalingV1()
161
162 o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
163 if err != nil {
164 return err
165 }
166
167 o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
168 o.PrintFlags.NamePrintFlags.Operation = operation
169 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
170
171 return o.PrintFlags.ToPrinter()
172 }
173
174 return nil
175 }
176
177
178 func (o *AutoscaleOptions) Validate() error {
179 if o.Max < 1 {
180 return fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", o.Max)
181 }
182 if o.Max < o.Min {
183 return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
184 }
185
186 return nil
187 }
188
189
190 func (o *AutoscaleOptions) Run() error {
191 r := o.builder.
192 Unstructured().
193 ContinueOnError().
194 NamespaceParam(o.namespace).DefaultNamespace().
195 FilenameParam(o.enforceNamespace, o.FilenameOptions).
196 ResourceTypeOrNameArgs(false, o.args...).
197 Flatten().
198 Do()
199 if err := r.Err(); err != nil {
200 return err
201 }
202
203 count := 0
204 err := r.Visit(func(info *resource.Info, err error) error {
205 if err != nil {
206 return err
207 }
208
209 mapping := info.ResourceMapping()
210 gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
211 if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
212 return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
213 }
214
215 hpa := o.createHorizontalPodAutoscaler(info.Name, mapping)
216
217 if err := o.Recorder.Record(hpa); err != nil {
218 klog.V(4).Infof("error recording current command: %v", err)
219 }
220
221 if o.dryRunStrategy == cmdutil.DryRunClient {
222 count++
223
224 printer, err := o.ToPrinter("created")
225 if err != nil {
226 return err
227 }
228 return printer.PrintObj(hpa, o.Out)
229 }
230
231 if err := util.CreateOrUpdateAnnotation(o.createAnnotation, hpa, scheme.DefaultJSONEncoder()); err != nil {
232 return err
233 }
234
235 createOptions := metav1.CreateOptions{}
236 if o.fieldManager != "" {
237 createOptions.FieldManager = o.fieldManager
238 }
239 if o.dryRunStrategy == cmdutil.DryRunServer {
240 createOptions.DryRun = []string{metav1.DryRunAll}
241 }
242 actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), hpa, createOptions)
243 if err != nil {
244 return err
245 }
246
247 count++
248 printer, err := o.ToPrinter("autoscaled")
249 if err != nil {
250 return err
251 }
252 return printer.PrintObj(actualHPA, o.Out)
253 })
254 if err != nil {
255 return err
256 }
257 if count == 0 {
258 return fmt.Errorf("no objects passed to autoscale")
259 }
260 return nil
261 }
262
263 func (o *AutoscaleOptions) createHorizontalPodAutoscaler(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
264 name := o.Name
265 if len(name) == 0 {
266 name = refName
267 }
268
269 scaler := autoscalingv1.HorizontalPodAutoscaler{
270 ObjectMeta: metav1.ObjectMeta{
271 Name: name,
272 },
273 Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
274 ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
275 APIVersion: mapping.GroupVersionKind.GroupVersion().String(),
276 Kind: mapping.GroupVersionKind.Kind,
277 Name: refName,
278 },
279 MaxReplicas: o.Max,
280 },
281 }
282
283 if o.Min > 0 {
284 v := int32(o.Min)
285 scaler.Spec.MinReplicas = &v
286 }
287 if o.CPUPercent >= 0 {
288 c := int32(o.CPUPercent)
289 scaler.Spec.TargetCPUUtilizationPercentage = &c
290 }
291
292 return &scaler
293 }
294
View as plain text