rollingupdate.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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 cmd
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "os"
  19. "time"
  20. "github.com/golang/glog"
  21. "github.com/renstrom/dedent"
  22. "github.com/spf13/cobra"
  23. "k8s.io/kubernetes/pkg/api"
  24. "k8s.io/kubernetes/pkg/api/errors"
  25. "k8s.io/kubernetes/pkg/api/meta"
  26. "k8s.io/kubernetes/pkg/api/v1"
  27. "k8s.io/kubernetes/pkg/kubectl"
  28. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  29. "k8s.io/kubernetes/pkg/kubectl/resource"
  30. "k8s.io/kubernetes/pkg/util/intstr"
  31. )
  32. // RollingUpdateOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
  33. // referencing the cmd.Flags()
  34. type RollingUpdateOptions struct {
  35. Filenames []string
  36. }
  37. var (
  38. rollingUpdate_long = dedent.Dedent(`
  39. Perform a rolling update of the given ReplicationController.
  40. Replaces the specified replication controller with a new replication controller by updating one pod at a time to use the
  41. new PodTemplate. The new-controller.json must specify the same namespace as the
  42. existing replication controller and overwrite at least one (common) label in its replicaSelector.`)
  43. rollingUpdate_example = dedent.Dedent(`
  44. # Update pods of frontend-v1 using new replication controller data in frontend-v2.json.
  45. kubectl rolling-update frontend-v1 -f frontend-v2.json
  46. # Update pods of frontend-v1 using JSON data passed into stdin.
  47. cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
  48. # Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the
  49. # name of the replication controller.
  50. kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2
  51. # Update the pods of frontend by just changing the image, and keeping the old name.
  52. kubectl rolling-update frontend --image=image:v2
  53. # Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).
  54. kubectl rolling-update frontend-v1 frontend-v2 --rollback
  55. `)
  56. )
  57. var (
  58. updatePeriod, _ = time.ParseDuration("1m0s")
  59. timeout, _ = time.ParseDuration("5m0s")
  60. pollInterval, _ = time.ParseDuration("3s")
  61. )
  62. func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
  63. options := &RollingUpdateOptions{}
  64. cmd := &cobra.Command{
  65. Use: "rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f NEW_CONTROLLER_SPEC)",
  66. // rollingupdate is deprecated.
  67. Aliases: []string{"rollingupdate"},
  68. Short: "Perform a rolling update of the given ReplicationController",
  69. Long: rollingUpdate_long,
  70. Example: rollingUpdate_example,
  71. Run: func(cmd *cobra.Command, args []string) {
  72. err := RunRollingUpdate(f, out, cmd, args, options)
  73. cmdutil.CheckErr(err)
  74. },
  75. }
  76. cmd.Flags().Duration("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
  77. cmd.Flags().Duration("poll-interval", pollInterval, `Time delay between polling for replication controller status after the update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
  78. cmd.Flags().Duration("timeout", 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".`)
  79. usage := "Filename or URL to file to use to create the new replication controller."
  80. kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
  81. cmd.MarkFlagRequired("filename")
  82. cmd.Flags().String("image", "", "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")
  83. cmd.MarkFlagRequired("image")
  84. cmd.Flags().String("deployment-label-key", "deployment", "The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise")
  85. cmd.Flags().String("container", "", "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")
  86. cmd.Flags().String("image-pull-policy", "", "Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.")
  87. cmd.Flags().Bool("rollback", false, "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")
  88. cmdutil.AddDryRunFlag(cmd)
  89. cmdutil.AddValidateFlags(cmd)
  90. cmdutil.AddPrinterFlags(cmd)
  91. cmdutil.AddInclude3rdPartyFlags(cmd)
  92. return cmd
  93. }
  94. func validateArguments(cmd *cobra.Command, filenames, args []string) error {
  95. deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
  96. image := cmdutil.GetFlagString(cmd, "image")
  97. rollback := cmdutil.GetFlagBool(cmd, "rollback")
  98. if len(deploymentKey) == 0 {
  99. return cmdutil.UsageError(cmd, "--deployment-label-key can not be empty")
  100. }
  101. if len(filenames) > 1 {
  102. return cmdutil.UsageError(cmd, "May only specify a single filename for new controller")
  103. }
  104. if !rollback {
  105. if len(filenames) == 0 && len(image) == 0 {
  106. return cmdutil.UsageError(cmd, "Must specify --filename or --image for new controller")
  107. }
  108. if len(filenames) != 0 && len(image) != 0 {
  109. return cmdutil.UsageError(cmd, "--filename and --image can not both be specified")
  110. }
  111. } else {
  112. if len(filenames) != 0 || len(image) != 0 {
  113. return cmdutil.UsageError(cmd, "Don't specify --filename or --image on rollback")
  114. }
  115. }
  116. if len(args) < 1 {
  117. return cmdutil.UsageError(cmd, "Must specify the controller to update")
  118. }
  119. return nil
  120. }
  121. func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *RollingUpdateOptions) error {
  122. if len(os.Args) > 1 && os.Args[1] == "rollingupdate" {
  123. printDeprecationWarning("rolling-update", "rollingupdate")
  124. }
  125. err := validateArguments(cmd, options.Filenames, args)
  126. if err != nil {
  127. return err
  128. }
  129. deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
  130. filename := ""
  131. image := cmdutil.GetFlagString(cmd, "image")
  132. pullPolicy := cmdutil.GetFlagString(cmd, "image-pull-policy")
  133. oldName := args[0]
  134. rollback := cmdutil.GetFlagBool(cmd, "rollback")
  135. period := cmdutil.GetFlagDuration(cmd, "update-period")
  136. interval := cmdutil.GetFlagDuration(cmd, "poll-interval")
  137. timeout := cmdutil.GetFlagDuration(cmd, "timeout")
  138. dryrun := cmdutil.GetDryRunFlag(cmd)
  139. outputFormat := cmdutil.GetFlagString(cmd, "output")
  140. container := cmdutil.GetFlagString(cmd, "container")
  141. if len(options.Filenames) > 0 {
  142. filename = options.Filenames[0]
  143. }
  144. cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
  145. if err != nil {
  146. return err
  147. }
  148. client, err := f.Client()
  149. if err != nil {
  150. return err
  151. }
  152. var newRc *api.ReplicationController
  153. // fetch rc
  154. oldRc, err := client.ReplicationControllers(cmdNamespace).Get(oldName)
  155. if err != nil {
  156. if !errors.IsNotFound(err) || len(image) == 0 || len(args) > 1 {
  157. return err
  158. }
  159. // We're in the middle of a rename, look for an RC with a source annotation of oldName
  160. newRc, err := kubectl.FindSourceController(client, cmdNamespace, oldName)
  161. if err != nil {
  162. return err
  163. }
  164. return kubectl.Rename(client, newRc, oldName)
  165. }
  166. var keepOldName bool
  167. var replicasDefaulted bool
  168. mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
  169. if len(filename) != 0 {
  170. schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
  171. if err != nil {
  172. return err
  173. }
  174. request := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
  175. Schema(schema).
  176. NamespaceParam(cmdNamespace).DefaultNamespace().
  177. FilenameParam(enforceNamespace, false, filename).
  178. Do()
  179. obj, err := request.Object()
  180. if err != nil {
  181. return err
  182. }
  183. var ok bool
  184. // Handle filename input from stdin. The resource builder always returns an api.List
  185. // when creating resource(s) from a stream.
  186. if list, ok := obj.(*api.List); ok {
  187. if len(list.Items) > 1 {
  188. return cmdutil.UsageError(cmd, "%s specifies multiple items", filename)
  189. }
  190. if len(list.Items) == 0 {
  191. return cmdutil.UsageError(cmd, "please make sure %s exists and is not empty", filename)
  192. }
  193. obj = list.Items[0]
  194. }
  195. newRc, ok = obj.(*api.ReplicationController)
  196. if !ok {
  197. if gvks, _, err := typer.ObjectKinds(obj); err == nil {
  198. return cmdutil.UsageError(cmd, "%s contains a %v not a ReplicationController", filename, gvks[0])
  199. }
  200. glog.V(4).Infof("Object %#v is not a ReplicationController", obj)
  201. return cmdutil.UsageError(cmd, "%s does not specify a valid ReplicationController", filename)
  202. }
  203. infos, err := request.Infos()
  204. if err != nil || len(infos) != 1 {
  205. glog.V(2).Infof("was not able to recover adequate information to discover if .spec.replicas was defaulted")
  206. } else {
  207. replicasDefaulted = isReplicasDefaulted(infos[0])
  208. }
  209. }
  210. // If the --image option is specified, we need to create a new rc with at least one different selector
  211. // than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for
  212. // same-image updates.
  213. if len(image) != 0 {
  214. codec := api.Codecs.LegacyCodec(client.APIVersion())
  215. keepOldName = len(args) == 1
  216. newName := findNewName(args, oldRc)
  217. if newRc, err = kubectl.LoadExistingNextReplicationController(client, cmdNamespace, newName); err != nil {
  218. return err
  219. }
  220. if newRc != nil {
  221. if inProgressImage := newRc.Spec.Template.Spec.Containers[0].Image; inProgressImage != image {
  222. return cmdutil.UsageError(cmd, "Found existing in-progress update to image (%s).\nEither continue in-progress update with --image=%s or rollback with --rollback", inProgressImage, inProgressImage)
  223. }
  224. fmt.Fprintf(out, "Found existing update in progress (%s), resuming.\n", newRc.Name)
  225. } else {
  226. config := &kubectl.NewControllerConfig{
  227. Namespace: cmdNamespace,
  228. OldName: oldName,
  229. NewName: newName,
  230. Image: image,
  231. Container: container,
  232. DeploymentKey: deploymentKey,
  233. }
  234. if oldRc.Spec.Template.Spec.Containers[0].Image == image {
  235. if len(pullPolicy) == 0 {
  236. return cmdutil.UsageError(cmd, "--image-pull-policy (Always|Never|IfNotPresent) must be provided when --image is the same as existing container image")
  237. }
  238. config.PullPolicy = api.PullPolicy(pullPolicy)
  239. }
  240. newRc, err = kubectl.CreateNewControllerFromCurrentController(client, codec, config)
  241. if err != nil {
  242. return err
  243. }
  244. }
  245. // Update the existing replication controller with pointers to the 'next' controller
  246. // and adding the <deploymentKey> label if necessary to distinguish it from the 'next' controller.
  247. oldHash, err := api.HashObject(oldRc, codec)
  248. if err != nil {
  249. return err
  250. }
  251. // If new image is same as old, the hash may not be distinct, so add a suffix.
  252. oldHash += "-orig"
  253. oldRc, err = kubectl.UpdateExistingReplicationController(client, oldRc, cmdNamespace, newRc.Name, deploymentKey, oldHash, out)
  254. if err != nil {
  255. return err
  256. }
  257. }
  258. if rollback {
  259. keepOldName = len(args) == 1
  260. newName := findNewName(args, oldRc)
  261. if newRc, err = kubectl.LoadExistingNextReplicationController(client, cmdNamespace, newName); err != nil {
  262. return err
  263. }
  264. if newRc == nil {
  265. return cmdutil.UsageError(cmd, "Could not find %s to rollback.\n", newName)
  266. }
  267. }
  268. if oldName == newRc.Name {
  269. return cmdutil.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s",
  270. filename, oldName)
  271. }
  272. updater := kubectl.NewRollingUpdater(newRc.Namespace, client)
  273. // To successfully pull off a rolling update the new and old rc have to differ
  274. // by at least one selector. Every new pod should have the selector and every
  275. // old pod should not have the selector.
  276. var hasLabel bool
  277. for key, oldValue := range oldRc.Spec.Selector {
  278. if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
  279. hasLabel = true
  280. break
  281. }
  282. }
  283. if !hasLabel {
  284. return cmdutil.UsageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
  285. filename, oldName)
  286. }
  287. // TODO: handle scales during rolling update
  288. if replicasDefaulted {
  289. newRc.Spec.Replicas = oldRc.Spec.Replicas
  290. }
  291. if dryrun {
  292. oldRcData := &bytes.Buffer{}
  293. newRcData := &bytes.Buffer{}
  294. if outputFormat == "" {
  295. oldRcData.WriteString(oldRc.Name)
  296. newRcData.WriteString(newRc.Name)
  297. } else {
  298. if err := f.PrintObject(cmd, mapper, oldRc, oldRcData); err != nil {
  299. return err
  300. }
  301. if err := f.PrintObject(cmd, mapper, newRc, newRcData); err != nil {
  302. return err
  303. }
  304. }
  305. fmt.Fprintf(out, "Rolling from:\n%s\nTo:\n%s\n", string(oldRcData.Bytes()), string(newRcData.Bytes()))
  306. return nil
  307. }
  308. updateCleanupPolicy := kubectl.DeleteRollingUpdateCleanupPolicy
  309. if keepOldName {
  310. updateCleanupPolicy = kubectl.RenameRollingUpdateCleanupPolicy
  311. }
  312. config := &kubectl.RollingUpdaterConfig{
  313. Out: out,
  314. OldRc: oldRc,
  315. NewRc: newRc,
  316. UpdatePeriod: period,
  317. Interval: interval,
  318. Timeout: timeout,
  319. CleanupPolicy: updateCleanupPolicy,
  320. MaxUnavailable: intstr.FromInt(0),
  321. MaxSurge: intstr.FromInt(1),
  322. }
  323. if rollback {
  324. err = kubectl.AbortRollingUpdate(config)
  325. if err != nil {
  326. return err
  327. }
  328. client.ReplicationControllers(config.NewRc.Namespace).Update(config.NewRc)
  329. }
  330. err = updater.Update(config)
  331. if err != nil {
  332. return err
  333. }
  334. message := "rolling updated"
  335. if keepOldName {
  336. newRc.Name = oldName
  337. } else {
  338. message = fmt.Sprintf("rolling updated to %q", newRc.Name)
  339. }
  340. newRc, err = client.ReplicationControllers(cmdNamespace).Get(newRc.Name)
  341. if err != nil {
  342. return err
  343. }
  344. if outputFormat != "" {
  345. return f.PrintObject(cmd, mapper, newRc, out)
  346. }
  347. kinds, _, err := api.Scheme.ObjectKinds(newRc)
  348. if err != nil {
  349. return err
  350. }
  351. _, res := meta.KindToResource(kinds[0])
  352. cmdutil.PrintSuccess(mapper, false, out, res.Resource, oldName, message)
  353. return nil
  354. }
  355. func findNewName(args []string, oldRc *api.ReplicationController) string {
  356. if len(args) >= 2 {
  357. return args[1]
  358. }
  359. if oldRc != nil {
  360. newName, _ := kubectl.GetNextControllerAnnotation(oldRc)
  361. return newName
  362. }
  363. return ""
  364. }
  365. func isReplicasDefaulted(info *resource.Info) bool {
  366. if info == nil || info.VersionedObject == nil {
  367. // was unable to recover versioned info
  368. return false
  369. }
  370. switch t := info.VersionedObject.(type) {
  371. case *v1.ReplicationController:
  372. return t.Spec.Replicas == nil
  373. }
  374. return false
  375. }