rollingupdate.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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 rollingupdate
  14. import (
  15. "bytes"
  16. "fmt"
  17. "time"
  18. "github.com/spf13/cobra"
  19. "k8s.io/klog"
  20. corev1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  24. "k8s.io/apimachinery/pkg/util/intstr"
  25. "k8s.io/cli-runtime/pkg/genericclioptions"
  26. "k8s.io/cli-runtime/pkg/printers"
  27. "k8s.io/cli-runtime/pkg/resource"
  28. "k8s.io/client-go/kubernetes"
  29. scaleclient "k8s.io/client-go/scale"
  30. "k8s.io/kubernetes/pkg/kubectl"
  31. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  32. "k8s.io/kubernetes/pkg/kubectl/scheme"
  33. "k8s.io/kubernetes/pkg/kubectl/util"
  34. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  35. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  36. "k8s.io/kubernetes/pkg/kubectl/validation"
  37. )
  38. var (
  39. rollingUpdateLong = templates.LongDesc(i18n.T(`
  40. Perform a rolling update of the given ReplicationController.
  41. Replaces the specified replication controller with a new replication controller by updating one pod at a time to use the
  42. new PodTemplate. The new-controller.json must specify the same namespace as the
  43. existing replication controller and overwrite at least one (common) label in its replicaSelector.
  44. ![Workflow](http://kubernetes.io/images/docs/kubectl_rollingupdate.svg)`))
  45. rollingUpdateExample = templates.Examples(i18n.T(`
  46. # Update pods of frontend-v1 using new replication controller data in frontend-v2.json.
  47. kubectl rolling-update frontend-v1 -f frontend-v2.json
  48. # Update pods of frontend-v1 using JSON data passed into stdin.
  49. cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
  50. # Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the
  51. # name of the replication controller.
  52. kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2
  53. # Update the pods of frontend by just changing the image, and keeping the old name.
  54. kubectl rolling-update frontend --image=image:v2
  55. # Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).
  56. kubectl rolling-update frontend-v1 frontend-v2 --rollback`))
  57. )
  58. const (
  59. updatePeriod = 1 * time.Minute
  60. timeout = 5 * time.Minute
  61. pollInterval = 3 * time.Second
  62. )
  63. type RollingUpdateOptions struct {
  64. FilenameOptions *resource.FilenameOptions
  65. OldName string
  66. KeepOldName bool
  67. DeploymentKey string
  68. Image string
  69. Container string
  70. PullPolicy string
  71. Rollback bool
  72. Period time.Duration
  73. Timeout time.Duration
  74. Interval time.Duration
  75. DryRun bool
  76. OutputFormat string
  77. Namespace string
  78. EnforceNamespace bool
  79. ScaleClient scaleclient.ScalesGetter
  80. ClientSet kubernetes.Interface
  81. Builder *resource.Builder
  82. ShouldValidate bool
  83. Validator func(bool) (validation.Schema, error)
  84. FindNewName func(*corev1.ReplicationController) string
  85. PrintFlags *genericclioptions.PrintFlags
  86. ToPrinter func(string) (printers.ResourcePrinter, error)
  87. genericclioptions.IOStreams
  88. }
  89. func NewRollingUpdateOptions(streams genericclioptions.IOStreams) *RollingUpdateOptions {
  90. return &RollingUpdateOptions{
  91. PrintFlags: genericclioptions.NewPrintFlags("rolling updated").WithTypeSetter(scheme.Scheme),
  92. FilenameOptions: &resource.FilenameOptions{},
  93. DeploymentKey: "deployment",
  94. Timeout: timeout,
  95. Interval: pollInterval,
  96. Period: updatePeriod,
  97. IOStreams: streams,
  98. }
  99. }
  100. func NewCmdRollingUpdate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
  101. o := NewRollingUpdateOptions(ioStreams)
  102. cmd := &cobra.Command{
  103. Use: "rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f NEW_CONTROLLER_SPEC)",
  104. DisableFlagsInUseLine: true,
  105. Short: "Perform a rolling update. This command is deprecated, use rollout instead.",
  106. Long: rollingUpdateLong,
  107. Example: rollingUpdateExample,
  108. Deprecated: `use "rollout" instead`,
  109. Hidden: true,
  110. Run: func(cmd *cobra.Command, args []string) {
  111. cmdutil.CheckErr(o.Complete(f, cmd, args))
  112. cmdutil.CheckErr(o.Validate(cmd, args))
  113. cmdutil.CheckErr(o.Run())
  114. },
  115. }
  116. o.PrintFlags.AddFlags(cmd)
  117. cmd.Flags().DurationVar(&o.Period, "update-period", o.Period, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
  118. cmd.Flags().DurationVar(&o.Interval, "poll-interval", o.Interval, `Time delay between polling for replication controller status after the update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
  119. cmd.Flags().DurationVar(&o.Timeout, "timeout", o.Timeout, `Max time to wait for a replication controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
  120. usage := "Filename or URL to file to use to create the new replication controller."
  121. cmdutil.AddJsonFilenameFlag(cmd.Flags(), &o.FilenameOptions.Filenames, usage)
  122. cmd.Flags().StringVar(&o.Image, "image", o.Image, i18n.T("Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f"))
  123. cmd.Flags().StringVar(&o.DeploymentKey, "deployment-label-key", o.DeploymentKey, i18n.T("The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise"))
  124. cmd.Flags().StringVar(&o.Container, "container", o.Container, i18n.T("Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod"))
  125. cmd.Flags().StringVar(&o.PullPolicy, "image-pull-policy", o.PullPolicy, i18n.T("Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise."))
  126. cmd.Flags().BoolVar(&o.Rollback, "rollback", o.Rollback, "If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout")
  127. cmdutil.AddDryRunFlag(cmd)
  128. cmdutil.AddValidateFlags(cmd)
  129. return cmd
  130. }
  131. func validateArguments(cmd *cobra.Command, filenames, args []string) error {
  132. deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
  133. image := cmdutil.GetFlagString(cmd, "image")
  134. rollback := cmdutil.GetFlagBool(cmd, "rollback")
  135. errors := []error{}
  136. if len(deploymentKey) == 0 {
  137. errors = append(errors, cmdutil.UsageErrorf(cmd, "--deployment-label-key can not be empty"))
  138. }
  139. if len(filenames) > 1 {
  140. errors = append(errors, cmdutil.UsageErrorf(cmd, "May only specify a single filename for new controller"))
  141. }
  142. if !rollback {
  143. if len(filenames) == 0 && len(image) == 0 {
  144. errors = append(errors, cmdutil.UsageErrorf(cmd, "Must specify --filename or --image for new controller"))
  145. } else if len(filenames) != 0 && len(image) != 0 {
  146. errors = append(errors, cmdutil.UsageErrorf(cmd, "--filename and --image can not both be specified"))
  147. }
  148. } else {
  149. if len(filenames) != 0 || len(image) != 0 {
  150. errors = append(errors, cmdutil.UsageErrorf(cmd, "Don't specify --filename or --image on rollback"))
  151. }
  152. }
  153. if len(args) < 1 {
  154. errors = append(errors, cmdutil.UsageErrorf(cmd, "Must specify the controller to update"))
  155. }
  156. return utilerrors.NewAggregate(errors)
  157. }
  158. func (o *RollingUpdateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
  159. if len(args) > 0 {
  160. o.OldName = args[0]
  161. }
  162. o.DryRun = cmdutil.GetDryRunFlag(cmd)
  163. o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
  164. o.KeepOldName = len(args) == 1
  165. o.ShouldValidate = cmdutil.GetFlagBool(cmd, "validate")
  166. o.Validator = f.Validator
  167. o.FindNewName = func(obj *corev1.ReplicationController) string {
  168. return findNewName(args, obj)
  169. }
  170. var err error
  171. o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
  172. if err != nil {
  173. return err
  174. }
  175. o.ScaleClient, err = cmdutil.ScaleClientFn(f)
  176. if err != nil {
  177. return err
  178. }
  179. o.ClientSet, err = f.KubernetesClientSet()
  180. if err != nil {
  181. return err
  182. }
  183. o.Builder = f.NewBuilder()
  184. o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
  185. o.PrintFlags.NamePrintFlags.Operation = operation
  186. if o.DryRun {
  187. o.PrintFlags.Complete("%s (dry run)")
  188. }
  189. return o.PrintFlags.ToPrinter()
  190. }
  191. return nil
  192. }
  193. func (o *RollingUpdateOptions) Validate(cmd *cobra.Command, args []string) error {
  194. return validateArguments(cmd, o.FilenameOptions.Filenames, args)
  195. }
  196. func (o *RollingUpdateOptions) Run() error {
  197. filename := ""
  198. if len(o.FilenameOptions.Filenames) > 0 {
  199. filename = o.FilenameOptions.Filenames[0]
  200. }
  201. coreClient := o.ClientSet.CoreV1()
  202. var newRc *corev1.ReplicationController
  203. // fetch rc
  204. oldRc, err := coreClient.ReplicationControllers(o.Namespace).Get(o.OldName, metav1.GetOptions{})
  205. if err != nil {
  206. if !errors.IsNotFound(err) || len(o.Image) == 0 || !o.KeepOldName {
  207. return err
  208. }
  209. // We're in the middle of a rename, look for an RC with a source annotation of oldName
  210. newRc, err := kubectl.FindSourceController(coreClient, o.Namespace, o.OldName)
  211. if err != nil {
  212. return err
  213. }
  214. return kubectl.Rename(coreClient, newRc, o.OldName)
  215. }
  216. var replicasDefaulted bool
  217. if len(filename) != 0 {
  218. schema, err := o.Validator(o.ShouldValidate)
  219. if err != nil {
  220. return err
  221. }
  222. request := o.Builder.
  223. Unstructured().
  224. Schema(schema).
  225. NamespaceParam(o.Namespace).DefaultNamespace().
  226. FilenameParam(o.EnforceNamespace, &resource.FilenameOptions{Recursive: false, Filenames: []string{filename}}).
  227. Flatten().
  228. Do()
  229. infos, err := request.Infos()
  230. if err != nil {
  231. return err
  232. }
  233. // Handle filename input from stdin.
  234. if len(infos) > 1 {
  235. return fmt.Errorf("%s specifies multiple items", filename)
  236. }
  237. if len(infos) == 0 {
  238. return fmt.Errorf("please make sure %s exists and is not empty", filename)
  239. }
  240. uncastVersionedObj, err := scheme.Scheme.ConvertToVersion(infos[0].Object, corev1.SchemeGroupVersion)
  241. if err != nil {
  242. klog.V(4).Infof("Object %T is not a ReplicationController", infos[0].Object)
  243. return fmt.Errorf("%s contains a %v not a ReplicationController", filename, infos[0].Object.GetObjectKind().GroupVersionKind())
  244. }
  245. switch t := uncastVersionedObj.(type) {
  246. case *corev1.ReplicationController:
  247. replicasDefaulted = t.Spec.Replicas == nil
  248. newRc = t
  249. }
  250. if newRc == nil {
  251. klog.V(4).Infof("Object %T is not a ReplicationController", infos[0].Object)
  252. return fmt.Errorf("%s contains a %v not a ReplicationController", filename, infos[0].Object.GetObjectKind().GroupVersionKind())
  253. }
  254. }
  255. // If the --image option is specified, we need to create a new rc with at least one different selector
  256. // than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for
  257. // same-image updates.
  258. if len(o.Image) != 0 {
  259. codec := scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion)
  260. newName := o.FindNewName(oldRc)
  261. if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, o.Namespace, newName); err != nil {
  262. return err
  263. }
  264. if newRc != nil {
  265. if inProgressImage := newRc.Spec.Template.Spec.Containers[0].Image; inProgressImage != o.Image {
  266. return fmt.Errorf("Found existing in-progress update to image (%s).\nEither continue in-progress update with --image=%s or rollback with --rollback", inProgressImage, inProgressImage)
  267. }
  268. fmt.Fprintf(o.Out, "Found existing update in progress (%s), resuming.\n", newRc.Name)
  269. } else {
  270. config := &kubectl.NewControllerConfig{
  271. Namespace: o.Namespace,
  272. OldName: o.OldName,
  273. NewName: newName,
  274. Image: o.Image,
  275. Container: o.Container,
  276. DeploymentKey: o.DeploymentKey,
  277. }
  278. if oldRc.Spec.Template.Spec.Containers[0].Image == o.Image {
  279. if len(o.PullPolicy) == 0 {
  280. return fmt.Errorf("--image-pull-policy (Always|Never|IfNotPresent) must be provided when --image is the same as existing container image")
  281. }
  282. config.PullPolicy = corev1.PullPolicy(o.PullPolicy)
  283. }
  284. newRc, err = kubectl.CreateNewControllerFromCurrentController(coreClient, codec, config)
  285. if err != nil {
  286. return err
  287. }
  288. }
  289. // Update the existing replication controller with pointers to the 'next' controller
  290. // and adding the <deploymentKey> label if necessary to distinguish it from the 'next' controller.
  291. oldHash, err := util.HashObject(oldRc, codec)
  292. if err != nil {
  293. return err
  294. }
  295. // If new image is same as old, the hash may not be distinct, so add a suffix.
  296. oldHash += "-orig"
  297. oldRc, err = kubectl.UpdateExistingReplicationController(coreClient, coreClient, oldRc, o.Namespace, newRc.Name, o.DeploymentKey, oldHash, o.Out)
  298. if err != nil {
  299. return err
  300. }
  301. }
  302. if o.Rollback {
  303. newName := o.FindNewName(oldRc)
  304. if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, o.Namespace, newName); err != nil {
  305. return err
  306. }
  307. if newRc == nil {
  308. return fmt.Errorf("Could not find %s to rollback.\n", newName)
  309. }
  310. }
  311. if o.OldName == newRc.Name {
  312. return fmt.Errorf("%s cannot have the same name as the existing ReplicationController %s",
  313. filename, o.OldName)
  314. }
  315. updater := kubectl.NewRollingUpdater(newRc.Namespace, coreClient, coreClient, o.ScaleClient)
  316. // To successfully pull off a rolling update the new and old rc have to differ
  317. // by at least one selector. Every new pod should have the selector and every
  318. // old pod should not have the selector.
  319. var hasLabel bool
  320. for key, oldValue := range oldRc.Spec.Selector {
  321. if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
  322. hasLabel = true
  323. break
  324. }
  325. }
  326. if !hasLabel {
  327. return fmt.Errorf("%s must specify a matching key with non-equal value in Selector for %s",
  328. filename, o.OldName)
  329. }
  330. // TODO: handle scales during rolling update
  331. if replicasDefaulted {
  332. t := *oldRc.Spec.Replicas
  333. newRc.Spec.Replicas = &t
  334. }
  335. if o.DryRun {
  336. oldRcData := &bytes.Buffer{}
  337. newRcData := &bytes.Buffer{}
  338. if o.OutputFormat == "" {
  339. oldRcData.WriteString(oldRc.Name)
  340. newRcData.WriteString(newRc.Name)
  341. } else {
  342. printer, err := o.ToPrinter("rolling updated")
  343. if err != nil {
  344. return err
  345. }
  346. if err := printer.PrintObj(oldRc, oldRcData); err != nil {
  347. return err
  348. }
  349. if err := printer.PrintObj(newRc, newRcData); err != nil {
  350. return err
  351. }
  352. }
  353. fmt.Fprintf(o.Out, "Rolling from:\n%s\nTo:\n%s\n", string(oldRcData.Bytes()), string(newRcData.Bytes()))
  354. return nil
  355. }
  356. updateCleanupPolicy := kubectl.DeleteRollingUpdateCleanupPolicy
  357. if o.KeepOldName {
  358. updateCleanupPolicy = kubectl.RenameRollingUpdateCleanupPolicy
  359. }
  360. config := &kubectl.RollingUpdaterConfig{
  361. Out: o.Out,
  362. OldRc: oldRc,
  363. NewRc: newRc,
  364. UpdatePeriod: o.Period,
  365. Interval: o.Interval,
  366. Timeout: timeout,
  367. CleanupPolicy: updateCleanupPolicy,
  368. MaxUnavailable: intstr.FromInt(0),
  369. MaxSurge: intstr.FromInt(1),
  370. }
  371. if o.Rollback {
  372. err = kubectl.AbortRollingUpdate(config)
  373. if err != nil {
  374. return err
  375. }
  376. coreClient.ReplicationControllers(config.NewRc.Namespace).Update(config.NewRc)
  377. }
  378. err = updater.Update(config)
  379. if err != nil {
  380. return err
  381. }
  382. message := "rolling updated"
  383. if o.KeepOldName {
  384. newRc.Name = o.OldName
  385. } else {
  386. message = fmt.Sprintf("rolling updated to %q", newRc.Name)
  387. }
  388. newRc, err = coreClient.ReplicationControllers(o.Namespace).Get(newRc.Name, metav1.GetOptions{})
  389. if err != nil {
  390. return err
  391. }
  392. printer, err := o.ToPrinter(message)
  393. if err != nil {
  394. return err
  395. }
  396. return printer.PrintObj(newRc, o.Out)
  397. }
  398. func findNewName(args []string, oldRc *corev1.ReplicationController) string {
  399. if len(args) >= 2 {
  400. return args[1]
  401. }
  402. if oldRc != nil {
  403. newName, _ := kubectl.GetNextControllerAnnotation(oldRc)
  404. return newName
  405. }
  406. return ""
  407. }