2014-12-10 21:48:48 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2014-12-10 21:48:48 +00:00
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 (
2015-04-23 23:27:19 +00:00
"bytes"
"crypto/md5"
"errors"
2014-12-10 21:48:48 +00:00
"fmt"
"io"
2015-03-27 23:24:59 +00:00
"os"
2015-04-23 23:27:19 +00:00
"time"
2014-12-10 21:48:48 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
2015-04-28 00:10:39 +00:00
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
2015-04-23 23:27:19 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
2014-12-10 21:48:48 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
2015-04-07 18:21:25 +00:00
cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
2015-03-17 03:41:20 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
2015-04-23 23:27:19 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
2015-04-30 23:07:40 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
2014-12-10 21:48:48 +00:00
"github.com/spf13/cobra"
)
const (
2015-02-20 21:28:43 +00:00
updatePeriod = "1m0s"
timeout = "5m0s"
pollInterval = "3s"
2015-03-27 23:24:59 +00:00
rollingUpdate_long = ` Perform a rolling update of the given ReplicationController .
2014-12-10 21:48:48 +00:00
2015-02-03 17:59:21 +00:00
Replaces the specified controller with new controller , updating one pod at a time to use the
2014-12-10 21:48:48 +00:00
new PodTemplate . The new - controller . json must specify the same namespace as the
2015-02-20 21:28:43 +00:00
existing controller and overwrite at least one ( common ) label in its replicaSelector . `
2015-03-27 23:24:59 +00:00
rollingUpdate_example = ` // Update pods of frontend-v1 using new controller data in frontend-v2.json.
$ kubectl rolling - update frontend - v1 - f frontend - v2 . json
2014-12-10 21:48:48 +00:00
2015-02-20 21:28:43 +00:00
// Update pods of frontend-v1 using JSON data passed into stdin.
2015-04-23 23:27:19 +00:00
$ cat frontend - v2 . json | kubectl rolling - update frontend - v1 - f -
2015-04-23 23:27:19 +00:00
// Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the
// name of the replication controller.
2015-04-23 23:27:19 +00:00
$ kubectl rolling - update frontend - v1 frontend - v2 -- image = image : v2
2015-04-23 23:27:19 +00:00
// Update the pods of frontend by just changing the image, and keeping the old name
$ kubectl rolling - update frontend -- image = image : v2
2015-04-23 23:27:19 +00:00
`
2015-02-20 21:28:43 +00:00
)
2015-02-03 17:59:21 +00:00
2015-04-07 18:21:25 +00:00
func NewCmdRollingUpdate ( f * cmdutil . Factory , out io . Writer ) * cobra . Command {
2015-02-20 21:28:43 +00:00
cmd := & cobra . Command {
2015-04-23 23:27:19 +00:00
Use : "rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f NEW_CONTROLLER_SPEC)" ,
2015-03-27 23:24:59 +00:00
// rollingupdate is deprecated.
Aliases : [ ] string { "rollingupdate" } ,
2015-02-20 21:28:43 +00:00
Short : "Perform a rolling update of the given ReplicationController." ,
2015-03-27 23:24:59 +00:00
Long : rollingUpdate_long ,
Example : rollingUpdate_example ,
2014-12-10 21:48:48 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-03-09 22:08:16 +00:00
err := RunRollingUpdate ( f , out , cmd , args )
2015-04-07 18:21:25 +00:00
cmdutil . CheckErr ( err )
2015-03-09 22:08:16 +00:00
} ,
}
cmd . Flags ( ) . String ( "update-period" , updatePeriod , ` Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". ` )
cmd . Flags ( ) . String ( "poll-interval" , pollInterval , ` Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". ` )
cmd . Flags ( ) . String ( "timeout" , timeout , ` Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". ` )
cmd . Flags ( ) . StringP ( "filename" , "f" , "" , "Filename or URL to file to use to create the new controller." )
2015-04-23 23:27:19 +00:00
cmd . Flags ( ) . String ( "image" , "" , "Image to upgrade the controller to. Can not be used with --filename/-f" )
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 ( ) . Bool ( "dry-run" , false , "If true, print out the changes that would be made, but don't actually make them." )
2015-04-30 17:28:36 +00:00
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" )
2015-04-23 23:27:19 +00:00
cmdutil . AddPrinterFlags ( cmd )
2015-03-09 22:08:16 +00:00
return cmd
}
2015-01-02 18:08:37 +00:00
2015-04-23 23:27:19 +00:00
func validateArguments ( cmd * cobra . Command , args [ ] string ) ( deploymentKey , filename , image , oldName string , err error ) {
deploymentKey = cmdutil . GetFlagString ( cmd , "deployment-label-key" )
filename = cmdutil . GetFlagString ( cmd , "filename" )
image = cmdutil . GetFlagString ( cmd , "image" )
if len ( deploymentKey ) == 0 {
return "" , "" , "" , "" , cmdutil . UsageError ( cmd , "--deployment-label-key can not be empty" )
}
if len ( filename ) == 0 && len ( image ) == 0 {
return "" , "" , "" , "" , cmdutil . UsageError ( cmd , "Must specify --filename or --image for new controller" )
}
if len ( filename ) != 0 && len ( image ) != 0 {
return "" , "" , "" , "" , cmdutil . UsageError ( cmd , "--filename and --image can not both be specified" )
}
if len ( args ) < 1 {
return "" , "" , "" , "" , cmdutil . UsageError ( cmd , "Must specify the controller to update" )
}
return deploymentKey , filename , image , args [ 0 ] , nil
}
2015-04-07 18:21:25 +00:00
func RunRollingUpdate ( f * cmdutil . Factory , out io . Writer , cmd * cobra . Command , args [ ] string ) error {
2015-03-27 23:24:59 +00:00
if os . Args [ 1 ] == "rollingupdate" {
printDeprecationWarning ( "rolling-update" , "rollingupdate" )
}
2015-04-23 23:27:19 +00:00
deploymentKey , filename , image , oldName , err := validateArguments ( cmd , args )
if err != nil {
return err
2015-03-09 22:08:16 +00:00
}
2015-04-07 18:21:25 +00:00
period := cmdutil . GetFlagDuration ( cmd , "update-period" )
interval := cmdutil . GetFlagDuration ( cmd , "poll-interval" )
timeout := cmdutil . GetFlagDuration ( cmd , "timeout" )
2015-04-23 23:27:19 +00:00
dryrun := cmdutil . GetFlagBool ( cmd , "dry-run" )
2014-12-10 21:48:48 +00:00
2015-03-17 03:41:20 +00:00
cmdNamespace , err := f . DefaultNamespace ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-01-08 13:40:02 +00:00
2015-04-23 23:27:19 +00:00
client , err := f . Client ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-04-23 23:27:19 +00:00
// fetch rc
oldRc , err := client . ReplicationControllers ( cmdNamespace ) . Get ( oldName )
if err != nil {
return err
}
2015-04-23 23:27:19 +00:00
keepOldName := false
2015-04-23 23:27:19 +00:00
mapper , typer := f . Object ( )
var newRc * api . ReplicationController
if len ( filename ) != 0 {
obj , err := resource . NewBuilder ( mapper , typer , f . ClientMapperForCommand ( ) ) .
NamespaceParam ( cmdNamespace ) . RequireNamespace ( ) .
FilenameParam ( filename ) .
Do ( ) .
Object ( )
if err != nil {
return err
}
var ok bool
newRc , ok = obj . ( * api . ReplicationController )
if ! ok {
return cmdutil . UsageError ( cmd , "%s does not specify a valid ReplicationController" , filename )
}
}
2015-04-29 22:06:42 +00:00
// 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
// different image.
2015-04-23 23:27:19 +00:00
if len ( image ) != 0 {
2015-04-28 00:10:39 +00:00
var newName string
2015-04-23 23:27:19 +00:00
var err error
2015-04-23 23:27:19 +00:00
if len ( args ) >= 2 {
newName = args [ 1 ]
} else {
2015-04-28 00:10:39 +00:00
newName , _ = kubectl . GetNextControllerAnnotation ( oldRc )
2015-04-23 23:27:19 +00:00
}
2015-04-23 23:27:19 +00:00
2015-04-28 00:10:39 +00:00
if len ( newName ) > 0 {
newRc , err = client . ReplicationControllers ( cmdNamespace ) . Get ( newName )
2015-04-30 17:28:36 +00:00
if err != nil {
if ! apierrors . IsNotFound ( err ) {
return err
} else {
newRc = nil
}
} else {
fmt . Fprint ( out , "Found existing update in progress (%s), resuming.\n" , newName )
2015-04-28 00:10:39 +00:00
}
}
if newRc == nil {
// load the old RC into the "new" RC
if newRc , err = client . ReplicationControllers ( cmdNamespace ) . Get ( oldName ) ; err != nil {
return err
}
2015-04-23 23:27:19 +00:00
2015-04-28 00:10:39 +00:00
if len ( newRc . Spec . Template . Spec . Containers ) > 1 {
// TODO: support multi-container image update.
return errors . New ( "Image update is not supported for multi-container pods" )
}
if len ( newRc . Spec . Template . Spec . Containers ) == 0 {
return cmdutil . UsageError ( cmd , "Pod has no containers! (%v)" , newRc )
}
newRc . Spec . Template . Spec . Containers [ 0 ] . Image = image
newHash , err := hashObject ( newRc , client . Codec )
if err != nil {
2015-04-23 23:27:19 +00:00
return err
}
2015-04-28 00:10:39 +00:00
if len ( newName ) == 0 {
keepOldName = true
newName = fmt . Sprintf ( "%s-%s" , newRc . Name , newHash )
}
newRc . Name = newName
newRc . Spec . Selector [ deploymentKey ] = newHash
newRc . Spec . Template . Labels [ deploymentKey ] = newHash
// Clear resource version after hashing so that identical updates get different hashes.
newRc . ResourceVersion = ""
kubectl . SetNextControllerAnnotation ( oldRc , newName )
if _ , found := oldRc . Spec . Selector [ deploymentKey ] ; ! found {
2015-04-29 22:06:42 +00:00
if oldRc , err = addDeploymentKeyToReplicationController ( oldRc , client , deploymentKey , cmdNamespace , out ) ; err != nil {
2015-04-28 00:10:39 +00:00
return err
}
}
2015-04-23 23:27:19 +00:00
}
2015-03-09 22:08:16 +00:00
}
2015-03-17 03:41:20 +00:00
newName := newRc . Name
2015-03-09 22:08:16 +00:00
if oldName == newName {
2015-04-07 18:21:25 +00:00
return cmdutil . UsageError ( cmd , "%s cannot have the same name as the existing ReplicationController %s" ,
2015-03-09 22:08:16 +00:00
filename , oldName )
}
2014-12-10 21:48:48 +00:00
2015-04-15 20:50:08 +00:00
updater := kubectl . NewRollingUpdater ( newRc . Namespace , kubectl . NewRollingUpdaterClient ( client ) )
2015-03-09 22:08:16 +00:00
2015-04-29 22:06:42 +00:00
// 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.
2015-03-09 22:08:16 +00:00
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 {
2015-04-07 18:21:25 +00:00
return cmdutil . UsageError ( cmd , "%s must specify a matching key with non-equal value in Selector for %s" ,
2015-03-09 22:08:16 +00:00
filename , oldName )
}
// TODO: handle resizes during rolling update
if newRc . Spec . Replicas == 0 {
newRc . Spec . Replicas = oldRc . Spec . Replicas
}
2015-04-23 23:27:19 +00:00
if dryrun {
oldRcData := & bytes . Buffer { }
if err := f . PrintObject ( cmd , oldRc , oldRcData ) ; err != nil {
return err
}
newRcData := & bytes . Buffer { }
if err := f . PrintObject ( cmd , 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
}
2015-04-23 23:27:19 +00:00
updateCleanupPolicy := kubectl . DeleteRollingUpdateCleanupPolicy
if keepOldName {
updateCleanupPolicy = kubectl . RenameRollingUpdateCleanupPolicy
}
2015-04-30 17:28:36 +00:00
config := & kubectl . RollingUpdaterConfig {
2015-04-17 18:58:43 +00:00
Out : out ,
OldRc : oldRc ,
NewRc : newRc ,
UpdatePeriod : period ,
Interval : interval ,
Timeout : timeout ,
2015-04-23 23:27:19 +00:00
CleanupPolicy : updateCleanupPolicy ,
2015-04-30 17:28:36 +00:00
}
if cmdutil . GetFlagBool ( cmd , "rollback" ) {
kubectl . AbortRollingUpdate ( config )
client . ReplicationControllers ( config . NewRc . Namespace ) . Update ( config . NewRc )
}
err = updater . Update ( config )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-04-23 23:27:19 +00:00
if keepOldName {
fmt . Fprintf ( out , "%s\n" , oldName )
} else {
fmt . Fprintf ( out , "%s\n" , newName )
}
2015-03-09 22:08:16 +00:00
return nil
2014-12-10 21:48:48 +00:00
}
2015-04-23 23:27:19 +00:00
func hashObject ( obj runtime . Object , codec runtime . Codec ) ( string , error ) {
data , err := codec . Encode ( obj )
if err != nil {
return "" , err
}
return fmt . Sprintf ( "%x" , md5 . Sum ( data ) ) , nil
}
const MaxRetries = 3
2015-04-30 23:07:40 +00:00
type updateFunc func ( controller * api . ReplicationController )
// updateWithRetries updates applies the given rc as an update.
func updateWithRetries ( rcClient client . ReplicationControllerInterface , rc * api . ReplicationController , applyUpdate updateFunc ) ( * api . ReplicationController , error ) {
// Each update could take ~100ms, so give it 0.5 second
var err error
oldRc := rc
err = wait . Poll ( 10 * time . Millisecond , 500 * time . Millisecond , func ( ) ( bool , error ) {
// Apply the update, then attempt to push it to the apiserver.
applyUpdate ( rc )
if rc , err = rcClient . Update ( rc ) ; err == nil {
// rc contains the latest controller post update
return true , nil
}
// Update the controller with the latest resource version, if the update failed we
// can't trust rc so use oldRc.Name.
if rc , err = rcClient . Get ( oldRc . Name ) ; err != nil {
// The Get failed: Value in rc cannot be trusted.
rc = oldRc
}
// The Get passed: rc contains the latest controller, expect a poll for the update.
return false , nil
} )
// If the error is non-nil the returned controller cannot be trusted, if it is nil, the returned
// controller contains the applied update.
return rc , err
}
2015-04-29 22:06:42 +00:00
func addDeploymentKeyToReplicationController ( oldRc * api . ReplicationController , client * client . Client , deploymentKey , namespace string , out io . Writer ) ( * api . ReplicationController , error ) {
2015-04-23 23:27:19 +00:00
oldHash , err := hashObject ( oldRc , client . Codec )
if err != nil {
2015-04-29 22:06:42 +00:00
return nil , err
2015-04-23 23:27:19 +00:00
}
2015-04-24 23:39:38 +00:00
// First, update the template label. This ensures that any newly created pods will have the new label
if oldRc . Spec . Template . Labels == nil {
oldRc . Spec . Template . Labels = map [ string ] string { }
}
2015-04-30 23:07:40 +00:00
if oldRc , err = updateWithRetries ( client . ReplicationControllers ( namespace ) , oldRc , func ( rc * api . ReplicationController ) {
rc . Spec . Template . Labels [ deploymentKey ] = oldHash
} ) ; err != nil {
2015-04-29 22:06:42 +00:00
return nil , err
2015-04-24 23:39:38 +00:00
}
2015-04-29 22:06:42 +00:00
// Update all pods managed by the rc to have the new hash label, so they are correctly adopted
2015-04-23 23:27:19 +00:00
// TODO: extract the code from the label command and re-use it here.
podList , err := client . Pods ( namespace ) . List ( labels . SelectorFromSet ( oldRc . Spec . Selector ) , fields . Everything ( ) )
if err != nil {
2015-04-29 22:06:42 +00:00
return nil , err
2015-04-23 23:27:19 +00:00
}
for ix := range podList . Items {
pod := & podList . Items [ ix ]
if pod . Labels == nil {
pod . Labels = map [ string ] string {
deploymentKey : oldHash ,
}
} else {
pod . Labels [ deploymentKey ] = oldHash
}
err = nil
delay := 3
for i := 0 ; i < MaxRetries ; i ++ {
_ , err = client . Pods ( namespace ) . Update ( pod )
if err != nil {
fmt . Fprint ( out , "Error updating pod (%v), retrying after %d seconds" , err , delay )
time . Sleep ( time . Second * time . Duration ( delay ) )
delay *= delay
} else {
break
}
}
if err != nil {
2015-04-29 22:06:42 +00:00
return nil , err
2015-04-23 23:27:19 +00:00
}
}
2015-04-24 23:39:38 +00:00
2015-04-23 23:27:19 +00:00
if oldRc . Spec . Selector == nil {
oldRc . Spec . Selector = map [ string ] string { }
}
2015-04-24 23:39:38 +00:00
// Copy the old selector, so that we can scrub out any orphaned pods
selectorCopy := map [ string ] string { }
for k , v := range oldRc . Spec . Selector {
selectorCopy [ k ] = v
2015-04-23 23:27:19 +00:00
}
2015-04-29 22:06:42 +00:00
// Update the selector of the rc so it manages all the pods we updated above
2015-04-30 23:07:40 +00:00
if oldRc , err = updateWithRetries ( client . ReplicationControllers ( namespace ) , oldRc , func ( rc * api . ReplicationController ) {
rc . Spec . Selector [ deploymentKey ] = oldHash
} ) ; err != nil {
2015-04-29 22:06:42 +00:00
return nil , err
2015-04-23 23:27:19 +00:00
}
2015-04-24 23:39:38 +00:00
2015-04-29 22:06:42 +00:00
// Clean up any orphaned pods that don't have the new label, this can happen if the rc manager
// doesn't see the update to its pod template and creates a new pod with the old labels after
// we've finished re-adopting existing pods to the rc.
2015-04-24 23:39:38 +00:00
podList , err = client . Pods ( namespace ) . List ( labels . SelectorFromSet ( selectorCopy ) , fields . Everything ( ) )
for ix := range podList . Items {
pod := & podList . Items [ ix ]
if value , found := pod . Labels [ deploymentKey ] ; ! found || value != oldHash {
2015-04-28 12:21:57 +00:00
if err := client . Pods ( namespace ) . Delete ( pod . Name , nil ) ; err != nil {
2015-04-29 22:06:42 +00:00
return nil , err
2015-04-24 23:39:38 +00:00
}
}
}
2015-04-23 23:27:19 +00:00
2015-04-29 22:06:42 +00:00
return oldRc , nil
2015-04-23 23:27:19 +00:00
}