scale.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package scale
  14. import (
  15. "fmt"
  16. "time"
  17. "github.com/spf13/cobra"
  18. "k8s.io/klog"
  19. "k8s.io/apimachinery/pkg/api/meta"
  20. "k8s.io/apimachinery/pkg/types"
  21. "k8s.io/cli-runtime/pkg/genericclioptions"
  22. "k8s.io/cli-runtime/pkg/printers"
  23. "k8s.io/cli-runtime/pkg/resource"
  24. "k8s.io/client-go/kubernetes"
  25. "k8s.io/kubernetes/pkg/kubectl"
  26. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  27. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  28. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  29. )
  30. var (
  31. scaleLong = templates.LongDesc(i18n.T(`
  32. Set a new size for a Deployment, ReplicaSet, Replication Controller, or StatefulSet.
  33. Scale also allows users to specify one or more preconditions for the scale action.
  34. If --current-replicas or --resource-version is specified, it is validated before the
  35. scale is attempted, and it is guaranteed that the precondition holds true when the
  36. scale is sent to the server.`))
  37. scaleExample = templates.Examples(i18n.T(`
  38. # Scale a replicaset named 'foo' to 3.
  39. kubectl scale --replicas=3 rs/foo
  40. # Scale a resource identified by type and name specified in "foo.yaml" to 3.
  41. kubectl scale --replicas=3 -f foo.yaml
  42. # If the deployment named mysql's current size is 2, scale mysql to 3.
  43. kubectl scale --current-replicas=2 --replicas=3 deployment/mysql
  44. # Scale multiple replication controllers.
  45. kubectl scale --replicas=5 rc/foo rc/bar rc/baz
  46. # Scale statefulset named 'web' to 3.
  47. kubectl scale --replicas=3 statefulset/web`))
  48. )
  49. const (
  50. timeout = 5 * time.Minute
  51. )
  52. type ScaleOptions struct {
  53. FilenameOptions resource.FilenameOptions
  54. RecordFlags *genericclioptions.RecordFlags
  55. PrintFlags *genericclioptions.PrintFlags
  56. PrintObj printers.ResourcePrinterFunc
  57. Selector string
  58. All bool
  59. Replicas int
  60. ResourceVersion string
  61. CurrentReplicas int
  62. Timeout time.Duration
  63. Recorder genericclioptions.Recorder
  64. builder *resource.Builder
  65. namespace string
  66. enforceNamespace bool
  67. args []string
  68. shortOutput bool
  69. clientSet kubernetes.Interface
  70. scaler kubectl.Scaler
  71. unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
  72. parent string
  73. genericclioptions.IOStreams
  74. }
  75. func NewScaleOptions(ioStreams genericclioptions.IOStreams) *ScaleOptions {
  76. return &ScaleOptions{
  77. PrintFlags: genericclioptions.NewPrintFlags("scaled"),
  78. RecordFlags: genericclioptions.NewRecordFlags(),
  79. CurrentReplicas: -1,
  80. Recorder: genericclioptions.NoopRecorder{},
  81. IOStreams: ioStreams,
  82. }
  83. }
  84. // NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale
  85. func NewCmdScale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
  86. o := NewScaleOptions(ioStreams)
  87. validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
  88. cmd := &cobra.Command{
  89. Use: "scale [--resource-version=version] [--current-replicas=count] --replicas=COUNT (-f FILENAME | TYPE NAME)",
  90. DisableFlagsInUseLine: true,
  91. Short: i18n.T("Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job"),
  92. Long: scaleLong,
  93. Example: scaleExample,
  94. Run: func(cmd *cobra.Command, args []string) {
  95. cmdutil.CheckErr(o.Complete(f, cmd, args))
  96. cmdutil.CheckErr(o.Validate(cmd))
  97. cmdutil.CheckErr(o.RunScale())
  98. },
  99. ValidArgs: validArgs,
  100. }
  101. o.RecordFlags.AddFlags(cmd)
  102. o.PrintFlags.AddFlags(cmd)
  103. cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
  104. cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types")
  105. 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."))
  106. 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.")
  107. cmd.Flags().IntVar(&o.Replicas, "replicas", o.Replicas, "The new desired number of replicas. Required.")
  108. cmd.MarkFlagRequired("replicas")
  109. 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).")
  110. cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to set a new size")
  111. return cmd
  112. }
  113. func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
  114. var err error
  115. o.RecordFlags.Complete(cmd)
  116. o.Recorder, err = o.RecordFlags.ToRecorder()
  117. if err != nil {
  118. return err
  119. }
  120. printer, err := o.PrintFlags.ToPrinter()
  121. if err != nil {
  122. return err
  123. }
  124. o.PrintObj = printer.PrintObj
  125. o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
  126. if err != nil {
  127. return err
  128. }
  129. o.builder = f.NewBuilder()
  130. o.args = args
  131. o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
  132. o.clientSet, err = f.KubernetesClientSet()
  133. if err != nil {
  134. return err
  135. }
  136. o.scaler, err = scaler(f)
  137. if err != nil {
  138. return err
  139. }
  140. o.unstructuredClientForMapping = f.UnstructuredClientForMapping
  141. o.parent = cmd.Parent().Name()
  142. return nil
  143. }
  144. func (o *ScaleOptions) Validate(cmd *cobra.Command) error {
  145. if o.Replicas < 0 {
  146. return fmt.Errorf("The --replicas=COUNT flag is required, and COUNT must be greater than or equal to 0")
  147. }
  148. return nil
  149. }
  150. // RunScale executes the scaling
  151. func (o *ScaleOptions) RunScale() error {
  152. r := o.builder.
  153. Unstructured().
  154. ContinueOnError().
  155. NamespaceParam(o.namespace).DefaultNamespace().
  156. FilenameParam(o.enforceNamespace, &o.FilenameOptions).
  157. ResourceTypeOrNameArgs(o.All, o.args...).
  158. Flatten().
  159. LabelSelectorParam(o.Selector).
  160. Do()
  161. err := r.Err()
  162. if err != nil {
  163. return err
  164. }
  165. infos := []*resource.Info{}
  166. err = r.Visit(func(info *resource.Info, err error) error {
  167. if err == nil {
  168. infos = append(infos, info)
  169. }
  170. return nil
  171. })
  172. if len(o.ResourceVersion) != 0 && len(infos) > 1 {
  173. return fmt.Errorf("cannot use --resource-version with multiple resources")
  174. }
  175. // only set a precondition if the user has requested one. A nil precondition means we can do a blind update, so
  176. // we avoid a Scale GET that may or may not succeed
  177. var precondition *kubectl.ScalePrecondition
  178. if o.CurrentReplicas != -1 || len(o.ResourceVersion) > 0 {
  179. precondition = &kubectl.ScalePrecondition{Size: o.CurrentReplicas, ResourceVersion: o.ResourceVersion}
  180. }
  181. retry := kubectl.NewRetryParams(1*time.Second, 5*time.Minute)
  182. var waitForReplicas *kubectl.RetryParams
  183. if o.Timeout != 0 {
  184. waitForReplicas = kubectl.NewRetryParams(1*time.Second, timeout)
  185. }
  186. counter := 0
  187. err = r.Visit(func(info *resource.Info, err error) error {
  188. if err != nil {
  189. return err
  190. }
  191. mapping := info.ResourceMapping()
  192. if err := o.scaler.Scale(info.Namespace, info.Name, uint(o.Replicas), precondition, retry, waitForReplicas, mapping.Resource.GroupResource()); err != nil {
  193. return err
  194. }
  195. // if the recorder makes a change, compute and create another patch
  196. if mergePatch, err := o.Recorder.MakeRecordMergePatch(info.Object); err != nil {
  197. klog.V(4).Infof("error recording current command: %v", err)
  198. } else if len(mergePatch) > 0 {
  199. client, err := o.unstructuredClientForMapping(mapping)
  200. if err != nil {
  201. return err
  202. }
  203. helper := resource.NewHelper(client, mapping)
  204. if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch, nil); err != nil {
  205. klog.V(4).Infof("error recording reason: %v", err)
  206. }
  207. }
  208. counter++
  209. return o.PrintObj(info.Object, o.Out)
  210. })
  211. if err != nil {
  212. return err
  213. }
  214. if counter == 0 {
  215. return fmt.Errorf("no objects passed to scale")
  216. }
  217. return nil
  218. }
  219. func scaler(f cmdutil.Factory) (kubectl.Scaler, error) {
  220. scalesGetter, err := cmdutil.ScaleClientFn(f)
  221. if err != nil {
  222. return nil, err
  223. }
  224. return kubectl.NewScaler(scalesGetter), nil
  225. }