2015-10-31 00:16:57 +00:00
/ *
Copyright 2015 The Kubernetes Authors All rights reserved .
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 (
2016-01-27 18:27:14 +00:00
"errors"
2015-10-31 00:16:57 +00:00
"fmt"
"io"
"reflect"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/controller"
// "k8s.io/kubernetes/pkg/api/unversioned"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/runtime"
)
type DrainOptions struct {
client * client . Client
factory * cmdutil . Factory
Force bool
GracePeriodSeconds int
2016-01-27 18:27:14 +00:00
IgnoreDaemonsets bool
2015-10-31 00:16:57 +00:00
mapper meta . RESTMapper
nodeInfo * resource . Info
out io . Writer
typer runtime . ObjectTyper
}
const (
cordon_long = ` Mark node as unschedulable .
`
cordon_example = ` # Mark node "foo" as unschedulable .
2016-02-29 14:41:09 +00:00
kubectl cordon foo
2015-10-31 00:16:57 +00:00
`
)
func NewCmdCordon ( f * cmdutil . Factory , out io . Writer ) * cobra . Command {
options := & DrainOptions { factory : f , out : out }
2016-03-10 01:27:19 +00:00
cmd := & cobra . Command {
2015-10-31 00:16:57 +00:00
Use : "cordon NODE" ,
Short : "Mark node as unschedulable" ,
Long : cordon_long ,
Example : cordon_example ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( options . SetupDrain ( cmd , args ) )
cmdutil . CheckErr ( options . RunCordonOrUncordon ( true ) )
} ,
}
2016-03-10 01:27:19 +00:00
return cmd
2015-10-31 00:16:57 +00:00
}
const (
uncordon_long = ` Mark node as schedulable .
`
uncordon_example = ` # Mark node "foo" as schedulable .
$ kubectl uncordon foo
`
)
func NewCmdUncordon ( f * cmdutil . Factory , out io . Writer ) * cobra . Command {
options := & DrainOptions { factory : f , out : out }
2016-03-10 01:27:19 +00:00
cmd := & cobra . Command {
2015-10-31 00:16:57 +00:00
Use : "uncordon NODE" ,
Short : "Mark node as schedulable" ,
Long : uncordon_long ,
Example : uncordon_example ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( options . SetupDrain ( cmd , args ) )
cmdutil . CheckErr ( options . RunCordonOrUncordon ( false ) )
} ,
}
2016-03-10 01:27:19 +00:00
return cmd
2015-10-31 00:16:57 +00:00
}
const (
drain_long = ` Drain node in preparation for maintenance .
The given node will be marked unschedulable to prevent new pods from arriving .
Then drain deletes all pods except mirror pods ( which cannot be deleted through
2016-01-27 18:27:14 +00:00
the API server ) . If there are DaemonSet - managed pods , drain will not proceed
without -- ignore - daemonsets , and regardless it will not delete any
DaemonSet - managed pods , because those pods would be immediately replaced by the
2016-03-17 22:54:39 +00:00
DaemonSet controller , which ignores unschedulable markings . If there are any
2016-01-27 18:27:14 +00:00
pods that are neither mirror pods nor managed -- by ReplicationController ,
DaemonSet or Job -- , then drain will not delete any pods unless you use -- force .
2015-10-31 00:16:57 +00:00
When you are ready to put the node back into service , use kubectl uncordon , which
will make the node schedulable again .
`
2016-01-16 01:14:22 +00:00
drain_example = ` # Drain node "foo" , even if there are pods not managed by a ReplicationController , Job , or DaemonSet on it .
2015-10-31 00:16:57 +00:00
$ kubectl drain foo -- force
2016-01-16 01:14:22 +00:00
# As above , but abort if there are pods not managed by a ReplicationController , Job , or DaemonSet , and use a grace period of 15 minutes .
2015-10-31 00:16:57 +00:00
$ kubectl drain foo -- grace - period = 900
`
)
func NewCmdDrain ( f * cmdutil . Factory , out io . Writer ) * cobra . Command {
options := & DrainOptions { factory : f , out : out }
cmd := & cobra . Command {
Use : "drain NODE" ,
Short : "Drain node in preparation for maintenance" ,
Long : drain_long ,
Example : drain_example ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( options . SetupDrain ( cmd , args ) )
cmdutil . CheckErr ( options . RunDrain ( ) )
} ,
}
2016-01-16 01:14:22 +00:00
cmd . Flags ( ) . BoolVar ( & options . Force , "force" , false , "Continue even if there are pods not managed by a ReplicationController, Job, or DaemonSet." )
2016-01-27 18:27:14 +00:00
cmd . Flags ( ) . BoolVar ( & options . IgnoreDaemonsets , "ignore-daemonsets" , false , "Ignore DaemonSet-managed pods." )
2015-10-31 00:16:57 +00:00
cmd . Flags ( ) . IntVar ( & options . GracePeriodSeconds , "grace-period" , - 1 , "Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used." )
return cmd
}
// SetupDrain populates some fields from the factory, grabs command line
// arguments and looks up the node using Builder
func ( o * DrainOptions ) SetupDrain ( cmd * cobra . Command , args [ ] string ) error {
var err error
if len ( args ) != 1 {
return cmdutil . UsageError ( cmd , fmt . Sprintf ( "USAGE: %s [flags]" , cmd . Use ) )
}
if o . client , err = o . factory . Client ( ) ; err != nil {
return err
}
2016-03-10 01:27:19 +00:00
o . mapper , o . typer = o . factory . Object ( false )
2015-10-31 00:16:57 +00:00
cmdNamespace , _ , err := o . factory . DefaultNamespace ( )
if err != nil {
return err
}
2016-03-10 01:27:19 +00:00
r := o . factory . NewBuilder ( cmdutil . GetIncludeThirdPartyAPIs ( cmd ) ) .
2015-10-31 00:16:57 +00:00
NamespaceParam ( cmdNamespace ) . DefaultNamespace ( ) .
ResourceNames ( "node" , args [ 0 ] ) .
Do ( )
if err = r . Err ( ) ; err != nil {
return err
}
return r . Visit ( func ( info * resource . Info , err error ) error {
if err != nil {
return err
}
o . nodeInfo = info
return nil
} )
}
// RunDrain runs the 'drain' command
func ( o * DrainOptions ) RunDrain ( ) error {
if err := o . RunCordonOrUncordon ( true ) ; err != nil {
return err
}
pods , err := o . getPodsForDeletion ( )
if err != nil {
return err
}
if err = o . deletePods ( pods ) ; err != nil {
return err
}
cmdutil . PrintSuccess ( o . mapper , false , o . out , "node" , o . nodeInfo . Name , "drained" )
return nil
}
// getPodsForDeletion returns all the pods we're going to delete. If there are
// any unmanaged pods and the user didn't pass --force, we return that list in
// an error.
func ( o * DrainOptions ) getPodsForDeletion ( ) ( [ ] api . Pod , error ) {
pods := [ ] api . Pod { }
podList , err := o . client . Pods ( api . NamespaceAll ) . List ( api . ListOptions { FieldSelector : fields . SelectorFromSet ( fields . Set { "spec.nodeName" : o . nodeInfo . Name } ) } )
if err != nil {
return pods , err
}
unreplicatedPodNames := [ ] string { }
2016-01-27 18:27:14 +00:00
daemonSetPodNames := [ ] string { }
2015-10-31 00:16:57 +00:00
for _ , pod := range podList . Items {
_ , found := pod . ObjectMeta . Annotations [ types . ConfigMirrorAnnotationKey ]
if found {
// Skip mirror pod
continue
}
replicated := false
2016-01-27 18:27:14 +00:00
daemonset_pod := false
2015-10-31 00:16:57 +00:00
creatorRef , found := pod . ObjectMeta . Annotations [ controller . CreatedByAnnotation ]
if found {
// Now verify that the specified creator actually exists.
var sr api . SerializedReference
2015-12-21 05:37:49 +00:00
if err := runtime . DecodeInto ( o . factory . Decoder ( true ) , [ ] byte ( creatorRef ) , & sr ) ; err != nil {
2015-10-31 00:16:57 +00:00
return pods , err
}
if sr . Reference . Kind == "ReplicationController" {
rc , err := o . client . ReplicationControllers ( sr . Reference . Namespace ) . Get ( sr . Reference . Name )
// Assume the only reason for an error is because the RC is
// gone/missing, not for any other cause. TODO(mml): something more
// sophisticated than this
if err == nil && rc != nil {
replicated = true
}
} else if sr . Reference . Kind == "DaemonSet" {
ds , err := o . client . DaemonSets ( sr . Reference . Namespace ) . Get ( sr . Reference . Name )
// Assume the only reason for an error is because the DaemonSet is
// gone/missing, not for any other cause. TODO(mml): something more
// sophisticated than this
if err == nil && ds != nil {
2016-01-27 18:27:14 +00:00
// Otherwise, treat daemonset-managed pods as unmanaged since
// DaemonSet Controller currently ignores the unschedulable bit.
// FIXME(mml): Add link to the issue concerning a proper way to drain
// daemonset pods, probably using taints.
daemonset_pod = true
2015-10-31 00:16:57 +00:00
}
2016-01-16 01:14:22 +00:00
} else if sr . Reference . Kind == "Job" {
2016-02-17 23:07:38 +00:00
job , err := o . client . ExtensionsClient . Jobs ( sr . Reference . Namespace ) . Get ( sr . Reference . Name )
2016-01-16 01:14:22 +00:00
// Assume the only reason for an error is because the Job is
// gone/missing, not for any other cause. TODO(mml): something more
// sophisticated than this
if err == nil && job != nil {
replicated = true
}
2015-10-31 00:16:57 +00:00
}
}
2016-01-27 18:27:14 +00:00
switch {
case daemonset_pod :
daemonSetPodNames = append ( daemonSetPodNames , pod . Name )
case ! replicated :
2015-10-31 00:16:57 +00:00
unreplicatedPodNames = append ( unreplicatedPodNames , pod . Name )
2016-01-27 18:27:14 +00:00
if o . Force {
pods = append ( pods , pod )
}
default :
pods = append ( pods , pod )
2015-10-31 00:16:57 +00:00
}
}
2016-01-27 18:27:14 +00:00
daemonSetErrors := ! o . IgnoreDaemonsets && len ( daemonSetPodNames ) > 0
unreplicatedErrors := ! o . Force && len ( unreplicatedPodNames ) > 0
switch {
case daemonSetErrors && unreplicatedErrors :
return [ ] api . Pod { } , errors . New ( unmanagedMsg ( unreplicatedPodNames , daemonSetPodNames , true ) )
case daemonSetErrors && ! unreplicatedErrors :
return [ ] api . Pod { } , errors . New ( unmanagedMsg ( [ ] string { } , daemonSetPodNames , true ) )
case unreplicatedErrors && ! daemonSetErrors :
return [ ] api . Pod { } , errors . New ( unmanagedMsg ( unreplicatedPodNames , [ ] string { } , true ) )
}
2015-10-31 00:16:57 +00:00
if len ( unreplicatedPodNames ) > 0 {
2016-01-27 18:27:14 +00:00
fmt . Fprintf ( o . out , "WARNING: About to delete these %s\n" , unmanagedMsg ( unreplicatedPodNames , [ ] string { } , false ) )
2015-10-31 00:16:57 +00:00
}
2016-01-27 18:27:14 +00:00
if len ( daemonSetPodNames ) > 0 {
fmt . Fprintf ( o . out , "WARNING: Skipping %s\n" , unmanagedMsg ( [ ] string { } , daemonSetPodNames , false ) )
}
2015-10-31 00:16:57 +00:00
return pods , nil
}
2016-01-27 18:27:14 +00:00
// Helper for generating errors or warnings about unmanaged pods.
func unmanagedMsg ( unreplicatedNames [ ] string , daemonSetNames [ ] string , include_guidance bool ) string {
msgs := [ ] string { }
if len ( unreplicatedNames ) > 0 {
msg := fmt . Sprintf ( "pods not managed by ReplicationController, Job, or DaemonSet: %s" , strings . Join ( unreplicatedNames , "," ) )
if include_guidance {
msg += " (use --force to override)"
}
msgs = append ( msgs , msg )
}
if len ( daemonSetNames ) > 0 {
msg := fmt . Sprintf ( "DaemonSet-managed pods: %s" , strings . Join ( daemonSetNames , "," ) )
if include_guidance {
msg += " (use --ignore-daemonsets to ignore)"
}
msgs = append ( msgs , msg )
}
return strings . Join ( msgs , " and " )
}
2015-10-31 00:16:57 +00:00
// deletePods deletes the pods on the api server
func ( o * DrainOptions ) deletePods ( pods [ ] api . Pod ) error {
deleteOptions := api . DeleteOptions { }
if o . GracePeriodSeconds >= 0 {
gracePeriodSeconds := int64 ( o . GracePeriodSeconds )
deleteOptions . GracePeriodSeconds = & gracePeriodSeconds
}
for _ , pod := range pods {
err := o . client . Pods ( pod . Namespace ) . Delete ( pod . Name , & deleteOptions )
if err != nil {
return err
}
cmdutil . PrintSuccess ( o . mapper , false , o . out , "pod" , pod . Name , "deleted" )
}
return nil
}
// RunCordonOrUncordon runs either Cordon or Uncordon. The desired value for
// "Unschedulable" is passed as the first arg.
func ( o * DrainOptions ) RunCordonOrUncordon ( desired bool ) error {
cmdNamespace , _ , err := o . factory . DefaultNamespace ( )
if err != nil {
return err
}
if o . nodeInfo . Mapping . GroupVersionKind . Kind == "Node" {
unsched := reflect . ValueOf ( o . nodeInfo . Object ) . Elem ( ) . FieldByName ( "Spec" ) . FieldByName ( "Unschedulable" )
if unsched . Bool ( ) == desired {
cmdutil . PrintSuccess ( o . mapper , false , o . out , o . nodeInfo . Mapping . Resource , o . nodeInfo . Name , already ( desired ) )
} else {
helper := resource . NewHelper ( o . client , o . nodeInfo . Mapping )
unsched . SetBool ( desired )
_ , err := helper . Replace ( cmdNamespace , o . nodeInfo . Name , true , o . nodeInfo . Object )
if err != nil {
return err
}
cmdutil . PrintSuccess ( o . mapper , false , o . out , o . nodeInfo . Mapping . Resource , o . nodeInfo . Name , changed ( desired ) )
}
} else {
cmdutil . PrintSuccess ( o . mapper , false , o . out , o . nodeInfo . Mapping . Resource , o . nodeInfo . Name , "skipped" )
}
return nil
}
// already() and changed() return suitable strings for {un,}cordoning
func already ( desired bool ) string {
if desired {
return "already cordoned"
}
return "already uncordoned"
}
func changed ( desired bool ) string {
if desired {
return "cordoned"
}
return "uncordoned"
}