Commit 1642083e authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #24645 from jlowdermilk/rolling-update

Automatic merge from submit-queue kubectl rolling-update support for same image Fixes #23497. Enables `kubectl rolling-update --image` to the same image, adding a `--image-pull-policy` flag to remove ambiguity. This allows rolling-update to behave as an "update and/or restart" (https://github.com/kubernetes/kubernetes/issues/23497#issuecomment-212349730), or as a forced update when the same tag can mean multiple versions (e.g. `:latest`). cc @janetkuo @nikhiljindal
parents 3b63b06b 9b917502
...@@ -1650,6 +1650,7 @@ _kubectl_rolling-update() ...@@ -1650,6 +1650,7 @@ _kubectl_rolling-update()
flags_with_completion+=("-f") flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml") flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--image=") flags+=("--image=")
flags+=("--image-pull-policy=")
flags+=("--include-extended-apis") flags+=("--include-extended-apis")
flags+=("--no-headers") flags+=("--no-headers")
flags+=("--output=") flags+=("--output=")
......
...@@ -43,6 +43,10 @@ existing replication controller and overwrite at least one (common) label in its ...@@ -43,6 +43,10 @@ existing replication controller and overwrite at least one (common) label in its
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 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
.PP .PP
\fB\-\-image\-pull\-policy\fP=""
Explicit policy for when to pull container images. Required when \-\-image is same as existing image, ignored otherwise.
.PP
\fB\-\-include\-extended\-apis\fP=true \fB\-\-include\-extended\-apis\fP=true
If true, include definitions of new APIs via calls to the API server. [default true] If true, include definitions of new APIs via calls to the API server. [default true]
......
...@@ -78,6 +78,7 @@ kubectl rolling-update frontend-v1 frontend-v2 --rollback ...@@ -78,6 +78,7 @@ kubectl rolling-update frontend-v1 frontend-v2 --rollback
--dry-run[=false]: If true, print out the changes that would be made, but don't actually make them. --dry-run[=false]: If true, print out the changes that would be made, but don't actually make them.
-f, --filename=[]: Filename or URL to file to use to create the new replication controller. -f, --filename=[]: Filename or URL to file to use to create the new replication controller.
--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 --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
--image-pull-policy="": Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.
--include-extended-apis[=true]: If true, include definitions of new APIs via calls to the API server. [default true] --include-extended-apis[=true]: If true, include definitions of new APIs via calls to the API server. [default true]
--no-headers[=false]: When using the default output, don't print headers. --no-headers[=false]: When using the default output, don't print headers.
-o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md]. -o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md].
...@@ -126,7 +127,7 @@ kubectl rolling-update frontend-v1 frontend-v2 --rollback ...@@ -126,7 +127,7 @@ kubectl rolling-update frontend-v1 frontend-v2 --rollback
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra on 5-Apr-2016 ###### Auto generated by spf13/cobra on 21-Apr-2016
<!-- BEGIN MUNGE: GENERATED_ANALYTICS --> <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rolling-update.md?pixel)]() [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rolling-update.md?pixel)]()
......
...@@ -26,6 +26,9 @@ options: ...@@ -26,6 +26,9 @@ options:
- name: image - name: image
usage: | usage: |
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 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
- name: image-pull-policy
usage: |
Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.
- name: include-extended-apis - name: include-extended-apis
default_value: "true" default_value: "true"
usage: | usage: |
......
...@@ -167,6 +167,7 @@ ignore-daemonsets ...@@ -167,6 +167,7 @@ ignore-daemonsets
ignore-not-found ignore-not-found
image-gc-high-threshold image-gc-high-threshold
image-gc-low-threshold image-gc-low-threshold
image-pull-policy
include-extended-apis include-extended-apis
input-base input-base
input-dirs input-dirs
......
...@@ -97,6 +97,7 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { ...@@ -97,6 +97,7 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.MarkFlagRequired("image") 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("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("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("dry-run", false, "If true, print out the changes that would be made, but don't actually make them.") cmd.Flags().Bool("dry-run", false, "If true, print out the changes that would be made, but don't actually make them.")
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") 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.AddValidateFlags(cmd) cmdutil.AddValidateFlags(cmd)
...@@ -150,6 +151,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg ...@@ -150,6 +151,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key") deploymentKey := cmdutil.GetFlagString(cmd, "deployment-label-key")
filename := "" filename := ""
image := cmdutil.GetFlagString(cmd, "image") image := cmdutil.GetFlagString(cmd, "image")
pullPolicy := cmdutil.GetFlagString(cmd, "image-pull-policy")
oldName := args[0] oldName := args[0]
rollback := cmdutil.GetFlagBool(cmd, "rollback") rollback := cmdutil.GetFlagBool(cmd, "rollback")
period := cmdutil.GetFlagDuration(cmd, "update-period") period := cmdutil.GetFlagDuration(cmd, "update-period")
...@@ -233,8 +235,8 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg ...@@ -233,8 +235,8 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
} }
} }
// If the --image option is specified, we need to create a new rc with at least one different selector // 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, which will differ because the new rc has a // than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for
// different image. // same-image updates.
if len(image) != 0 { if len(image) != 0 {
codec := api.Codecs.LegacyCodec(client.APIVersion()) codec := api.Codecs.LegacyCodec(client.APIVersion())
keepOldName = len(args) == 1 keepOldName = len(args) == 1
...@@ -248,10 +250,21 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg ...@@ -248,10 +250,21 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
} }
fmt.Fprintf(out, "Found existing update in progress (%s), resuming.\n", newRc.Name) fmt.Fprintf(out, "Found existing update in progress (%s), resuming.\n", newRc.Name)
} else { } 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 oldRc.Spec.Template.Spec.Containers[0].Image == image {
return cmdutil.UsageError(cmd, "Specified --image must be distinct from existing container 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, cmdNamespace, oldName, newName, image, container, deploymentKey) newRc, err = kubectl.CreateNewControllerFromCurrentController(client, codec, config)
if err != nil { if err != nil {
return err return err
} }
...@@ -262,6 +275,8 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg ...@@ -262,6 +275,8 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
if err != nil { if err != nil {
return err 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) oldRc, err = kubectl.UpdateExistingReplicationController(client, oldRc, cmdNamespace, newRc.Name, deploymentKey, oldHash, out)
if err != nil { if err != nil {
return err return err
......
...@@ -505,19 +505,28 @@ func LoadExistingNextReplicationController(c client.ReplicationControllersNamesp ...@@ -505,19 +505,28 @@ func LoadExistingNextReplicationController(c client.ReplicationControllersNamesp
return newRc, err return newRc, err
} }
func CreateNewControllerFromCurrentController(c client.Interface, codec runtime.Codec, namespace, oldName, newName, image, container, deploymentKey string) (*api.ReplicationController, error) { type NewControllerConfig struct {
Namespace string
OldName, NewName string
Image string
Container string
DeploymentKey string
PullPolicy api.PullPolicy
}
func CreateNewControllerFromCurrentController(c client.Interface, codec runtime.Codec, cfg *NewControllerConfig) (*api.ReplicationController, error) {
containerIndex := 0 containerIndex := 0
// load the old RC into the "new" RC // load the old RC into the "new" RC
newRc, err := c.ReplicationControllers(namespace).Get(oldName) newRc, err := c.ReplicationControllers(cfg.Namespace).Get(cfg.OldName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(container) != 0 { if len(cfg.Container) != 0 {
containerFound := false containerFound := false
for i, c := range newRc.Spec.Template.Spec.Containers { for i, c := range newRc.Spec.Template.Spec.Containers {
if c.Name == container { if c.Name == cfg.Container {
containerIndex = i containerIndex = i
containerFound = true containerFound = true
break break
...@@ -525,31 +534,34 @@ func CreateNewControllerFromCurrentController(c client.Interface, codec runtime. ...@@ -525,31 +534,34 @@ func CreateNewControllerFromCurrentController(c client.Interface, codec runtime.
} }
if !containerFound { if !containerFound {
return nil, fmt.Errorf("container %s not found in pod", container) return nil, fmt.Errorf("container %s not found in pod", cfg.Container)
} }
} }
if len(newRc.Spec.Template.Spec.Containers) > 1 && len(container) == 0 { if len(newRc.Spec.Template.Spec.Containers) > 1 && len(cfg.Container) == 0 {
return nil, goerrors.New("Must specify container to update when updating a multi-container pod") return nil, goerrors.New("Must specify container to update when updating a multi-container pod")
} }
if len(newRc.Spec.Template.Spec.Containers) == 0 { if len(newRc.Spec.Template.Spec.Containers) == 0 {
return nil, goerrors.New(fmt.Sprintf("Pod has no containers! (%v)", newRc)) return nil, goerrors.New(fmt.Sprintf("Pod has no containers! (%v)", newRc))
} }
newRc.Spec.Template.Spec.Containers[containerIndex].Image = image newRc.Spec.Template.Spec.Containers[containerIndex].Image = cfg.Image
if len(cfg.PullPolicy) != 0 {
newRc.Spec.Template.Spec.Containers[containerIndex].ImagePullPolicy = cfg.PullPolicy
}
newHash, err := api.HashObject(newRc, codec) newHash, err := api.HashObject(newRc, codec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(newName) == 0 { if len(cfg.NewName) == 0 {
newName = fmt.Sprintf("%s-%s", newRc.Name, newHash) cfg.NewName = fmt.Sprintf("%s-%s", newRc.Name, newHash)
} }
newRc.Name = newName newRc.Name = cfg.NewName
newRc.Spec.Selector[deploymentKey] = newHash newRc.Spec.Selector[cfg.DeploymentKey] = newHash
newRc.Spec.Template.Labels[deploymentKey] = newHash newRc.Spec.Template.Labels[cfg.DeploymentKey] = newHash
// Clear resource version after hashing so that identical updates get different hashes. // Clear resource version after hashing so that identical updates get different hashes.
newRc.ResourceVersion = "" newRc.ResourceVersion = ""
return newRc, nil return newRc, nil
......
...@@ -1090,12 +1090,19 @@ func TestRollingUpdater_multipleContainersInPod(t *testing.T) { ...@@ -1090,12 +1090,19 @@ func TestRollingUpdater_multipleContainersInPod(t *testing.T) {
test.newRc.Spec.Template.Labels[test.deploymentKey] = deploymentHash test.newRc.Spec.Template.Labels[test.deploymentKey] = deploymentHash
test.newRc.Name = fmt.Sprintf("%s-%s", test.newRc.Name, deploymentHash) test.newRc.Name = fmt.Sprintf("%s-%s", test.newRc.Name, deploymentHash)
updatedRc, err := CreateNewControllerFromCurrentController(fake, codec, "", test.oldRc.ObjectMeta.Name, test.newRc.ObjectMeta.Name, test.image, test.container, test.deploymentKey) config := &NewControllerConfig{
OldName: test.oldRc.ObjectMeta.Name,
NewName: test.newRc.ObjectMeta.Name,
Image: test.image,
Container: test.container,
DeploymentKey: test.deploymentKey,
}
updatedRc, err := CreateNewControllerFromCurrentController(fake, codec, config)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if !reflect.DeepEqual(updatedRc, test.newRc) { if !reflect.DeepEqual(updatedRc, test.newRc) {
t.Errorf("expected:\n%v\ngot:\n%v\n", test.newRc, updatedRc) t.Errorf("expected:\n%#v\ngot:\n%#v\n", test.newRc, updatedRc)
} }
} }
} }
......
...@@ -1419,7 +1419,7 @@ func ValidateController(c *client.Client, containerImage string, replicas int, c ...@@ -1419,7 +1419,7 @@ func ValidateController(c *client.Client, containerImage string, replicas int, c
By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector
waitLoop: waitLoop:
for start := time.Now(); time.Since(start) < PodStartTimeout; time.Sleep(5 * time.Second) { for start := time.Now(); time.Since(start) < PodStartTimeout; time.Sleep(5 * time.Second) {
getPodsOutput := RunKubectlOrDie("get", "pods", "-o", "template", getPodsTemplate, "--api-version=v1", "-l", testname, fmt.Sprintf("--namespace=%v", ns)) getPodsOutput := RunKubectlOrDie("get", "pods", "-o", "template", getPodsTemplate, "-l", testname, fmt.Sprintf("--namespace=%v", ns))
pods := strings.Fields(getPodsOutput) pods := strings.Fields(getPodsOutput)
if numPods := len(pods); numPods != replicas { if numPods := len(pods); numPods != replicas {
By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods)) By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods))
...@@ -1427,13 +1427,13 @@ waitLoop: ...@@ -1427,13 +1427,13 @@ waitLoop:
} }
var runningPods []string var runningPods []string
for _, podID := range pods { for _, podID := range pods {
running := RunKubectlOrDie("get", "pods", podID, "-o", "template", getContainerStateTemplate, "--api-version=v1", fmt.Sprintf("--namespace=%v", ns)) running := RunKubectlOrDie("get", "pods", podID, "-o", "template", getContainerStateTemplate, fmt.Sprintf("--namespace=%v", ns))
if running != "true" { if running != "true" {
Logf("%s is created but not running", podID) Logf("%s is created but not running", podID)
continue waitLoop continue waitLoop
} }
currentImage := RunKubectlOrDie("get", "pods", podID, "-o", "template", getImageTemplate, "--api-version=v1", fmt.Sprintf("--namespace=%v", ns)) currentImage := RunKubectlOrDie("get", "pods", podID, "-o", "template", getImageTemplate, fmt.Sprintf("--namespace=%v", ns))
if currentImage != containerImage { if currentImage != containerImage {
Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage) Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage)
continue waitLoop continue waitLoop
......
...@@ -1025,6 +1025,40 @@ var _ = framework.KubeDescribe("Kubectl client", func() { ...@@ -1025,6 +1025,40 @@ var _ = framework.KubeDescribe("Kubectl client", func() {
}) })
}) })
framework.KubeDescribe("Kubectl rolling-update", func() {
var nsFlag string
var rcName string
var c *client.Client
BeforeEach(func() {
c = f.Client
nsFlag = fmt.Sprintf("--namespace=%v", ns)
rcName = "e2e-test-nginx-rc"
})
AfterEach(func() {
framework.RunKubectlOrDie("delete", "rc", rcName, nsFlag)
})
It("should support rolling-update to same image [Conformance]", func() {
By("running the image " + nginxImage)
framework.RunKubectlOrDie("run", rcName, "--image="+nginxImage, "--generator=run/v1", nsFlag)
By("verifying the rc " + rcName + " was created")
rc, err := c.ReplicationControllers(ns).Get(rcName)
if err != nil {
framework.Failf("Failed getting rc %s: %v", rcName, err)
}
containers := rc.Spec.Template.Spec.Containers
if containers == nil || len(containers) != 1 || containers[0].Image != nginxImage {
framework.Failf("Failed creating rc %s for 1 pod with expected image %s", rcName, nginxImage)
}
By("rolling-update to same image controller")
framework.RunKubectlOrDie("rolling-update", rcName, "--update-period=1s", "--image="+nginxImage, "--image-pull-policy="+string(api.PullIfNotPresent), nsFlag)
framework.ValidateController(c, nginxImage, 1, rcName, "run="+rcName, noOpValidatorFn, ns)
})
})
framework.KubeDescribe("Kubectl run deployment", func() { framework.KubeDescribe("Kubectl run deployment", func() {
var nsFlag string var nsFlag string
var dName string var dName string
...@@ -1449,6 +1483,8 @@ func getUDData(jpgExpected string, ns string) func(*client.Client, string) error ...@@ -1449,6 +1483,8 @@ func getUDData(jpgExpected string, ns string) func(*client.Client, string) error
} }
} }
func noOpValidatorFn(c *client.Client, podID string) error { return nil }
// newBlockingReader returns a reader that allows reading the given string, // newBlockingReader returns a reader that allows reading the given string,
// then blocks until Close() is called on the returned closer. // then blocks until Close() is called on the returned closer.
// //
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment