Skip to content

Commit

Permalink
Merge pull request kubernetes#17111 from munnerz/multi-rolling-update
Browse files Browse the repository at this point in the history
Allow rolling-update of a single container in multi-container pods
  • Loading branch information
j3ffml committed Dec 11, 2015
2 parents 30dc184 + 61306c2 commit e53acfe
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 8 deletions.
1 change: 1 addition & 0 deletions contrib/completions/bash/kubectl
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,7 @@ _kubectl_rolling-update()
flags_with_completion=()
flags_completion=()

flags+=("--container=")
flags+=("--deployment-label-key=")
flags+=("--dry-run")
flags+=("--filename=")
Expand Down
4 changes: 4 additions & 0 deletions docs/man/man1/kubectl-rolling-update.1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ existing replication controller and overwrite at least one (common) label in its


.SH OPTIONS
.PP
\fB\-\-container\fP=""
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

.PP
\fB\-\-deployment\-label\-key\fP="deployment"
The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when \-\-image is specified, ignored otherwise
Expand Down
3 changes: 2 additions & 1 deletion docs/user-guide/kubectl/kubectl_rolling-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ $ kubectl rolling-update frontend-v1 frontend-v2 --rollback
### Options

```
--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
--deployment-label-key="deployment": The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise
--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.
Expand Down Expand Up @@ -122,7 +123,7 @@ $ kubectl rolling-update frontend-v1 frontend-v2 --rollback

* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager

###### Auto generated by spf13/cobra on 24-Nov-2015
###### Auto generated by spf13/cobra on 26-Nov-2015

<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rolling-update.md?pixel)]()
Expand Down
4 changes: 3 additions & 1 deletion pkg/kubectl/cmd/rollingupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
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().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")
cmdutil.AddValidateFlags(cmd)
Expand Down Expand Up @@ -155,6 +156,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
timeout := cmdutil.GetFlagDuration(cmd, "timeout")
dryrun := cmdutil.GetFlagBool(cmd, "dry-run")
outputFormat := cmdutil.GetFlagString(cmd, "output")
container := cmdutil.GetFlagString(cmd, "container")

if len(options.Filenames) > 0 {
filename = options.Filenames[0]
Expand Down Expand Up @@ -247,7 +249,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
if oldRc.Spec.Template.Spec.Containers[0].Image == image {
return cmdutil.UsageError(cmd, "Specified --image must be distinct from existing container image")
}
newRc, err = kubectl.CreateNewControllerFromCurrentController(client, cmdNamespace, oldName, newName, image, deploymentKey)
newRc, err = kubectl.CreateNewControllerFromCurrentController(client, client.Codec, cmdNamespace, oldName, newName, image, container, deploymentKey)
if err != nil {
return err
}
Expand Down
30 changes: 24 additions & 6 deletions pkg/kubectl/rolling_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/pkg/util/wait"
)
Expand Down Expand Up @@ -532,23 +533,40 @@ func LoadExistingNextReplicationController(c client.ReplicationControllersNamesp
return newRc, err
}

func CreateNewControllerFromCurrentController(c *client.Client, namespace, oldName, newName, image, deploymentKey string) (*api.ReplicationController, error) {
func CreateNewControllerFromCurrentController(c client.Interface, codec runtime.Codec, namespace, oldName, newName, image, container, deploymentKey string) (*api.ReplicationController, error) {
containerIndex := 0
// load the old RC into the "new" RC
newRc, err := c.ReplicationControllers(namespace).Get(oldName)
if err != nil {
return nil, err
}

if len(newRc.Spec.Template.Spec.Containers) > 1 {
// TODO: support multi-container image update.
return nil, goerrors.New("Image update is not supported for multi-container pods")
if len(container) != 0 {
containerFound := false

for i, c := range newRc.Spec.Template.Spec.Containers {
if c.Name == container {
containerIndex = i
containerFound = true
break
}
}

if !containerFound {
return nil, fmt.Errorf("container %s not found in pod", container)
}
}

if len(newRc.Spec.Template.Spec.Containers) > 1 && len(container) == 0 {
return nil, goerrors.New("Must specify container to update when updating a multi-container pod")
}

if len(newRc.Spec.Template.Spec.Containers) == 0 {
return nil, goerrors.New(fmt.Sprintf("Pod has no containers! (%v)", newRc))
}
newRc.Spec.Template.Spec.Containers[0].Image = image
newRc.Spec.Template.Spec.Containers[containerIndex].Image = image

newHash, err := api.HashObject(newRc, c.Codec)
newHash, err := api.HashObject(newRc, codec)
if err != nil {
return nil, err
}
Expand Down
160 changes: 160 additions & 0 deletions pkg/kubectl/rolling_updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,166 @@ func TestUpdate_assignOriginalAnnotation(t *testing.T) {
}
}

func TestRollingUpdater_multipleContainersInPod(t *testing.T) {
tests := []struct {
oldRc *api.ReplicationController
newRc *api.ReplicationController

container string
image string
deploymentKey string
}{
{
oldRc: &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{
"dk": "old",
},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{
"dk": "old",
},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "container1",
Image: "image1",
},
{
Name: "container2",
Image: "image2",
},
},
},
},
},
},
newRc: &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{
"dk": "old",
},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{
"dk": "old",
},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "container1",
Image: "newimage",
},
{
Name: "container2",
Image: "image2",
},
},
},
},
},
},
container: "container1",
image: "newimage",
deploymentKey: "dk",
},
{
oldRc: &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "bar",
},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{
"dk": "old",
},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{
"dk": "old",
},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "container1",
Image: "image1",
},
},
},
},
},
},
newRc: &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "bar",
},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{
"dk": "old",
},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{
"dk": "old",
},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "container1",
Image: "newimage",
},
},
},
},
},
},
container: "container1",
image: "newimage",
deploymentKey: "dk",
},
}

for _, test := range tests {
fake := &testclient.Fake{}
fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
switch action.(type) {
case testclient.GetAction:
return true, test.oldRc, nil
}
return false, nil, nil
})

codec := testapi.Default.Codec()

deploymentHash, err := api.HashObject(test.newRc, codec)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

test.newRc.Spec.Selector[test.deploymentKey] = deploymentHash
test.newRc.Spec.Template.Labels[test.deploymentKey] = 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)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(updatedRc, test.newRc) {
t.Errorf("expected:\n%v\ngot:\n%v\n", test.newRc, updatedRc)
}
}
}

// TestRollingUpdater_cleanupWithClients ensures that the cleanup policy is
// correctly implemented.
func TestRollingUpdater_cleanupWithClients(t *testing.T) {
Expand Down

0 comments on commit e53acfe

Please sign in to comment.