123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package cmd
- import (
- "bytes"
- "fmt"
- "io"
- "os"
- "time"
- "github.com/golang/glog"
- "github.com/renstrom/dedent"
- "github.com/spf13/cobra"
- "k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/api/errors"
- "k8s.io/kubernetes/pkg/api/meta"
- "k8s.io/kubernetes/pkg/api/v1"
- "k8s.io/kubernetes/pkg/kubectl"
- cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
- "k8s.io/kubernetes/pkg/kubectl/resource"
- "k8s.io/kubernetes/pkg/util/intstr"
- )
- // RollingUpdateOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
- // referencing the cmd.Flags()
- type RollingUpdateOptions struct {
- Filenames []string
- }
- var (
- rollingUpdate_long = dedent.Dedent(`
- Perform a rolling update of the given ReplicationController.
- Replaces the specified replication controller with a new replication controller by updating one pod at a time to use the
- new PodTemplate. The new-controller.json must specify the same namespace as the
- existing replication controller and overwrite at least one (common) label in its replicaSelector.`)
- rollingUpdate_example = dedent.Dedent(`
- # Update pods of frontend-v1 using new replication controller data in frontend-v2.json.
- kubectl rolling-update frontend-v1 -f frontend-v2.json
- # Update pods of frontend-v1 using JSON data passed into stdin.
- cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
- # Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the
- # name of the replication controller.
- kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2
- # Update the pods of frontend by just changing the image, and keeping the old name.
- kubectl rolling-update frontend --image=image:v2
- # Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2).
- kubectl rolling-update frontend-v1 frontend-v2 --rollback
- `)
- )
- var (
- updatePeriod, _ = time.ParseDuration("1m0s")
- timeout, _ = time.ParseDuration("5m0s")
- pollInterval, _ = time.ParseDuration("3s")
- )
- func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
- options := &RollingUpdateOptions{}
- cmd := &cobra.Command{
- Use: "rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f NEW_CONTROLLER_SPEC)",
- // rollingupdate is deprecated.
- Aliases: []string{"rollingupdate"},
- Short: "Perform a rolling update of the given ReplicationController",
- Long: rollingUpdate_long,
- Example: rollingUpdate_example,
- Run: func(cmd *cobra.Command, args []string) {
- err := RunRollingUpdate(f, out, cmd, args, options)
- cmdutil.CheckErr(err)
- },
- }
- cmd.Flags().Duration("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
- 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".`)
- 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".`)
- usage := "Filename or URL to file to use to create the new replication controller."
- kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
- cmd.MarkFlagRequired("filename")
- 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")
- cmd.MarkFlagRequired("image")
- 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")
- 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")
- cmd.Flags().String("image-pull-policy", "", "Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.")
- 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")
- cmdutil.AddDryRunFlag(cmd)
- cmdutil.AddValidateFlags(cmd)
- cmdutil.AddPrinterFlags(cmd)
- cmdutil.AddInclude3rdPartyFlags(cmd)
- return cmd
- }
- func validateArguments(cmd *cobra.Command, filenames, args []string) error {
- deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
- image := cmdutil.GetFlagString(cmd, "image")
- rollback := cmdutil.GetFlagBool(cmd, "rollback")
- if len(deploymentKey) == 0 {
- return cmdutil.UsageError(cmd, "--deployment-label-key can not be empty")
- }
- if len(filenames) > 1 {
- return cmdutil.UsageError(cmd, "May only specify a single filename for new controller")
- }
- if !rollback {
- if len(filenames) == 0 && len(image) == 0 {
- return cmdutil.UsageError(cmd, "Must specify --filename or --image for new controller")
- }
- if len(filenames) != 0 && len(image) != 0 {
- return cmdutil.UsageError(cmd, "--filename and --image can not both be specified")
- }
- } else {
- if len(filenames) != 0 || len(image) != 0 {
- return cmdutil.UsageError(cmd, "Don't specify --filename or --image on rollback")
- }
- }
- if len(args) < 1 {
- return cmdutil.UsageError(cmd, "Must specify the controller to update")
- }
- return nil
- }
- func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *RollingUpdateOptions) error {
- if len(os.Args) > 1 && os.Args[1] == "rollingupdate" {
- printDeprecationWarning("rolling-update", "rollingupdate")
- }
- err := validateArguments(cmd, options.Filenames, args)
- if err != nil {
- return err
- }
- deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
- filename := ""
- image := cmdutil.GetFlagString(cmd, "image")
- pullPolicy := cmdutil.GetFlagString(cmd, "image-pull-policy")
- oldName := args[0]
- rollback := cmdutil.GetFlagBool(cmd, "rollback")
- period := cmdutil.GetFlagDuration(cmd, "update-period")
- interval := cmdutil.GetFlagDuration(cmd, "poll-interval")
- timeout := cmdutil.GetFlagDuration(cmd, "timeout")
- dryrun := cmdutil.GetDryRunFlag(cmd)
- outputFormat := cmdutil.GetFlagString(cmd, "output")
- container := cmdutil.GetFlagString(cmd, "container")
- if len(options.Filenames) > 0 {
- filename = options.Filenames[0]
- }
- cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
- if err != nil {
- return err
- }
- client, err := f.Client()
- if err != nil {
- return err
- }
- var newRc *api.ReplicationController
- // fetch rc
- oldRc, err := client.ReplicationControllers(cmdNamespace).Get(oldName)
- if err != nil {
- if !errors.IsNotFound(err) || len(image) == 0 || len(args) > 1 {
- return err
- }
- // We're in the middle of a rename, look for an RC with a source annotation of oldName
- newRc, err := kubectl.FindSourceController(client, cmdNamespace, oldName)
- if err != nil {
- return err
- }
- return kubectl.Rename(client, newRc, oldName)
- }
- var keepOldName bool
- var replicasDefaulted bool
- mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
- if len(filename) != 0 {
- schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
- if err != nil {
- return err
- }
- request := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
- Schema(schema).
- NamespaceParam(cmdNamespace).DefaultNamespace().
- FilenameParam(enforceNamespace, false, filename).
- Do()
- obj, err := request.Object()
- if err != nil {
- return err
- }
- var ok bool
- // Handle filename input from stdin. The resource builder always returns an api.List
- // when creating resource(s) from a stream.
- if list, ok := obj.(*api.List); ok {
- if len(list.Items) > 1 {
- return cmdutil.UsageError(cmd, "%s specifies multiple items", filename)
- }
- if len(list.Items) == 0 {
- return cmdutil.UsageError(cmd, "please make sure %s exists and is not empty", filename)
- }
- obj = list.Items[0]
- }
- newRc, ok = obj.(*api.ReplicationController)
- if !ok {
- if gvks, _, err := typer.ObjectKinds(obj); err == nil {
- return cmdutil.UsageError(cmd, "%s contains a %v not a ReplicationController", filename, gvks[0])
- }
- glog.V(4).Infof("Object %#v is not a ReplicationController", obj)
- return cmdutil.UsageError(cmd, "%s does not specify a valid ReplicationController", filename)
- }
- infos, err := request.Infos()
- if err != nil || len(infos) != 1 {
- glog.V(2).Infof("was not able to recover adequate information to discover if .spec.replicas was defaulted")
- } else {
- replicasDefaulted = isReplicasDefaulted(infos[0])
- }
- }
- // If the --image option is specified, we need to create a new rc with at least one different selector
- // than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for
- // same-image updates.
- if len(image) != 0 {
- codec := api.Codecs.LegacyCodec(client.APIVersion())
- keepOldName = len(args) == 1
- newName := findNewName(args, oldRc)
- if newRc, err = kubectl.LoadExistingNextReplicationController(client, cmdNamespace, newName); err != nil {
- return err
- }
- if newRc != nil {
- if inProgressImage := newRc.Spec.Template.Spec.Containers[0].Image; inProgressImage != image {
- 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)
- }
- fmt.Fprintf(out, "Found existing update in progress (%s), resuming.\n", newRc.Name)
- } else {
- config := &kubectl.NewControllerConfig{
- Namespace: cmdNamespace,
- OldName: oldName,
- NewName: newName,
- Image: image,
- Container: container,
- DeploymentKey: deploymentKey,
- }
- if oldRc.Spec.Template.Spec.Containers[0].Image == image {
- if len(pullPolicy) == 0 {
- return cmdutil.UsageError(cmd, "--image-pull-policy (Always|Never|IfNotPresent) must be provided when --image is the same as existing container image")
- }
- config.PullPolicy = api.PullPolicy(pullPolicy)
- }
- newRc, err = kubectl.CreateNewControllerFromCurrentController(client, codec, config)
- if err != nil {
- return err
- }
- }
- // Update the existing replication controller with pointers to the 'next' controller
- // and adding the <deploymentKey> label if necessary to distinguish it from the 'next' controller.
- oldHash, err := api.HashObject(oldRc, codec)
- if err != nil {
- return err
- }
- // If new image is same as old, the hash may not be distinct, so add a suffix.
- oldHash += "-orig"
- oldRc, err = kubectl.UpdateExistingReplicationController(client, oldRc, cmdNamespace, newRc.Name, deploymentKey, oldHash, out)
- if err != nil {
- return err
- }
- }
- if rollback {
- keepOldName = len(args) == 1
- newName := findNewName(args, oldRc)
- if newRc, err = kubectl.LoadExistingNextReplicationController(client, cmdNamespace, newName); err != nil {
- return err
- }
- if newRc == nil {
- return cmdutil.UsageError(cmd, "Could not find %s to rollback.\n", newName)
- }
- }
- if oldName == newRc.Name {
- return cmdutil.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s",
- filename, oldName)
- }
- updater := kubectl.NewRollingUpdater(newRc.Namespace, client)
- // To successfully pull off a rolling update the new and old rc have to differ
- // by at least one selector. Every new pod should have the selector and every
- // old pod should not have the selector.
- var hasLabel bool
- for key, oldValue := range oldRc.Spec.Selector {
- if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
- hasLabel = true
- break
- }
- }
- if !hasLabel {
- return cmdutil.UsageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
- filename, oldName)
- }
- // TODO: handle scales during rolling update
- if replicasDefaulted {
- newRc.Spec.Replicas = oldRc.Spec.Replicas
- }
- if dryrun {
- oldRcData := &bytes.Buffer{}
- newRcData := &bytes.Buffer{}
- if outputFormat == "" {
- oldRcData.WriteString(oldRc.Name)
- newRcData.WriteString(newRc.Name)
- } else {
- if err := f.PrintObject(cmd, mapper, oldRc, oldRcData); err != nil {
- return err
- }
- if err := f.PrintObject(cmd, mapper, newRc, newRcData); err != nil {
- return err
- }
- }
- fmt.Fprintf(out, "Rolling from:\n%s\nTo:\n%s\n", string(oldRcData.Bytes()), string(newRcData.Bytes()))
- return nil
- }
- updateCleanupPolicy := kubectl.DeleteRollingUpdateCleanupPolicy
- if keepOldName {
- updateCleanupPolicy = kubectl.RenameRollingUpdateCleanupPolicy
- }
- config := &kubectl.RollingUpdaterConfig{
- Out: out,
- OldRc: oldRc,
- NewRc: newRc,
- UpdatePeriod: period,
- Interval: interval,
- Timeout: timeout,
- CleanupPolicy: updateCleanupPolicy,
- MaxUnavailable: intstr.FromInt(0),
- MaxSurge: intstr.FromInt(1),
- }
- if rollback {
- err = kubectl.AbortRollingUpdate(config)
- if err != nil {
- return err
- }
- client.ReplicationControllers(config.NewRc.Namespace).Update(config.NewRc)
- }
- err = updater.Update(config)
- if err != nil {
- return err
- }
- message := "rolling updated"
- if keepOldName {
- newRc.Name = oldName
- } else {
- message = fmt.Sprintf("rolling updated to %q", newRc.Name)
- }
- newRc, err = client.ReplicationControllers(cmdNamespace).Get(newRc.Name)
- if err != nil {
- return err
- }
- if outputFormat != "" {
- return f.PrintObject(cmd, mapper, newRc, out)
- }
- kinds, _, err := api.Scheme.ObjectKinds(newRc)
- if err != nil {
- return err
- }
- _, res := meta.KindToResource(kinds[0])
- cmdutil.PrintSuccess(mapper, false, out, res.Resource, oldName, message)
- return nil
- }
- func findNewName(args []string, oldRc *api.ReplicationController) string {
- if len(args) >= 2 {
- return args[1]
- }
- if oldRc != nil {
- newName, _ := kubectl.GetNextControllerAnnotation(oldRc)
- return newName
- }
- return ""
- }
- func isReplicasDefaulted(info *resource.Info) bool {
- if info == nil || info.VersionedObject == nil {
- // was unable to recover versioned info
- return false
- }
- switch t := info.VersionedObject.(type) {
- case *v1.ReplicationController:
- return t.Spec.Replicas == nil
- }
- return false
- }
|