77 "os"
88 "os/exec"
99 "path/filepath"
10+ "regexp"
11+ "strconv"
1012 "strings"
1113 "syscall"
1214
@@ -91,6 +93,7 @@ func NewCommandStartNode(basename string, out, errout io.Writer) (*cobra.Command
9193 BindImageFormatArgs (options .NodeArgs .ImageFormatArgs , flags , "" )
9294 BindKubeConnectionArgs (options .NodeArgs .KubeConnectionArgs , flags , "" )
9395
96+ flags .BoolVar (& options .NodeArgs .WriteFlagsOnly , "write-flags" , false , "When this is specified only the arguments necessary to start the Kubelet will be output." )
9497 flags .StringVar (& options .NodeArgs .BootstrapConfigName , "bootstrap-config-name" , options .NodeArgs .BootstrapConfigName , "On startup, the node will request a client cert from the master and get its config from this config map in the openshift-node namespace (experimental)." )
9598
9699 // autocompletion hints
@@ -172,12 +175,20 @@ func (o NodeOptions) Validate(args []string) error {
172175 if o .IsRunFromConfig () {
173176 return errors .New ("--config may not be set if you're only writing the config" )
174177 }
178+ if o .NodeArgs .WriteFlagsOnly {
179+ return errors .New ("--write-config and --write-flags are mutually exclusive" )
180+ }
175181 }
176182
177183 // if we are starting up using a config file, run no validations here
178- if len (o .NodeArgs .BootstrapConfigName ) > 0 && ! o .IsRunFromConfig () {
179- if err := o .NodeArgs .Validate (); err != nil {
180- return err
184+ if len (o .NodeArgs .BootstrapConfigName ) > 0 {
185+ if o .NodeArgs .WriteFlagsOnly {
186+ return errors .New ("--write-flags is mutually exclusive with --bootstrap-config-name" )
187+ }
188+ if ! o .IsRunFromConfig () {
189+ if err := o .NodeArgs .Validate (); err != nil {
190+ return err
191+ }
181192 }
182193 }
183194
@@ -201,7 +212,7 @@ func (o NodeOptions) StartNode() error {
201212 return err
202213 }
203214
204- if o .IsWriteConfigOnly () {
215+ if o .NodeArgs . WriteFlagsOnly || o . IsWriteConfigOnly () {
205216 return nil
206217 }
207218
@@ -224,6 +235,17 @@ func (o NodeOptions) RunNode() error {
224235 if addr := o .NodeArgs .ListenArg .ListenAddr ; addr .Provided {
225236 nodeConfig .ServingInfo .BindAddress = addr .HostPort (o .NodeArgs .ListenArg .ListenAddr .DefaultPort )
226237 }
238+ // do a local resolution of node config DNS IP, supports bootstrapping cases
239+ if nodeConfig .DNSIP == "0.0.0.0" {
240+ glog .V (4 ).Infof ("Defaulting to the DNSIP config to the node's IP" )
241+ nodeConfig .DNSIP = nodeConfig .NodeIP
242+ // TODO: the Kubelet should do this defaulting (to the IP it recognizes)
243+ if len (nodeConfig .DNSIP ) == 0 {
244+ if ip , err := cmdutil .DefaultLocalIP4 (); err == nil {
245+ nodeConfig .DNSIP = ip .String ()
246+ }
247+ }
248+ }
227249
228250 var validationResults validation.ValidationResults
229251 switch {
@@ -256,11 +278,11 @@ func (o NodeOptions) RunNode() error {
256278 return nil
257279 }
258280
259- if err := StartNode ( * nodeConfig , o .NodeArgs .Components ); err != nil {
260- return err
281+ if o .NodeArgs .WriteFlagsOnly {
282+ return WriteKubeletFlags ( * nodeConfig )
261283 }
262284
263- return nil
285+ return StartNode ( * nodeConfig , o . NodeArgs . Components )
264286}
265287
266288// resolveNodeConfig creates a new configuration on disk by reading from the master, reads
@@ -371,41 +393,13 @@ func (o NodeOptions) IsRunFromConfig() bool {
371393}
372394
373395// execKubelet attempts to call execve() for the kubelet with the configuration defined
374- // in server passed as flags. If the binary is not the same as the current file and
375- // the environment variable OPENSHIFT_ALLOW_UNSUPPORTED_KUBELET is unset, the method
376- // will return an error. The returned boolean indicates whether fallback to in-process
377- // is allowed.
378- func execKubelet (kubeletArgs []string ) (bool , error ) {
379- // verify the Kubelet binary to use
396+ // in server passed as flags.
397+ func execKubelet (kubeletArgs []string ) error {
380398 path := "kubelet"
381- requireSameBinary := true
382- if newPath := os .Getenv ("OPENSHIFT_ALLOW_UNSUPPORTED_KUBELET" ); len (newPath ) > 0 {
383- requireSameBinary = false
384- path = newPath
385- }
386399 kubeletPath , err := exec .LookPath (path )
387400 if err != nil {
388- return requireSameBinary , err
389- }
390- kubeletFile , err := os .Stat (kubeletPath )
391- if err != nil {
392- return requireSameBinary , err
393- }
394- thisPath , err := exec .LookPath (os .Args [0 ])
395- if err != nil {
396- return true , err
397- }
398- thisFile , err := os .Stat (thisPath )
399- if err != nil {
400- return true , err
401- }
402- if ! os .SameFile (thisFile , kubeletFile ) {
403- if requireSameBinary {
404- return true , fmt .Errorf ("binary at %q is not the same file as %q, cannot execute" , thisPath , kubeletPath )
405- }
406- glog .Warningf ("UNSUPPORTED: Executing a different Kubelet than the current binary is not supported: %s" , kubeletPath )
401+ return err
407402 }
408-
409403 // convert current settings to flags
410404 args := append ([]string {kubeletPath }, kubeletArgs ... )
411405 for i := glog .Level (10 ); i > 0 ; i -- {
@@ -426,10 +420,45 @@ func execKubelet(kubeletArgs []string) (bool, error) {
426420 break
427421 }
428422 }
423+ // execve the child process, replacing this process
429424 glog .V (3 ).Infof ("Exec %s %s" , kubeletPath , strings .Join (args , " " ))
430- return false , syscall .Exec (kubeletPath , args , os .Environ ())
425+ return syscall .Exec (kubeletPath , args , os .Environ ())
426+ }
427+
428+ // safeArgRegexp matches only characters that are known safe. DO NOT add to this list
429+ // without fully considering whether that new character can be used to break shell escaping
430+ // rules.
431+ var safeArgRegexp = regexp .MustCompile (`^[\da-zA-Z\-=_\.,/\:]+$` )
432+
433+ // shellEscapeArg quotes an argument if it contains characters that my cause a shell
434+ // interpreter to split the single argument into multiple.
435+ func shellEscapeArg (s string ) string {
436+ if safeArgRegexp .MatchString (s ) {
437+ return s
438+ }
439+ return strconv .Quote (s )
431440}
432441
442+ // WriteKubeletFlags writes the correct set of flags to start a Kubelet from the provided node config to
443+ // stdout, instead of launching anything.
444+ func WriteKubeletFlags (nodeConfig configapi.NodeConfig ) error {
445+ kubeletFlagsAsMap , err := nodeoptions .ComputeKubeletFlagsAsMap (nodeConfig .KubeletArguments , nodeConfig )
446+ if err != nil {
447+ return fmt .Errorf ("cannot create kubelet args: %v" , err )
448+ }
449+ kubeletArgs := nodeoptions .KubeletArgsMapToArgs (kubeletFlagsAsMap )
450+ if err := nodeoptions .CheckFlags (kubeletArgs ); err != nil {
451+ return err
452+ }
453+ var outputArgs []string
454+ for _ , s := range kubeletArgs {
455+ outputArgs = append (outputArgs , shellEscapeArg (s ))
456+ }
457+ fmt .Println (strings .Join (outputArgs , " " ))
458+ return nil
459+ }
460+
461+ // StartNode launches the node processes.
433462func StartNode (nodeConfig configapi.NodeConfig , components * utilflags.ComponentFlag ) error {
434463 kubeletFlagsAsMap , err := nodeoptions .ComputeKubeletFlagsAsMap (nodeConfig .KubeletArguments , nodeConfig )
435464 if err != nil {
@@ -443,11 +472,7 @@ func StartNode(nodeConfig configapi.NodeConfig, components *utilflags.ComponentF
443472 // as a step towards decomposing OpenShift into Kubernetes components, perform an execve
444473 // to launch the Kubelet instead of loading in-process
445474 if components .Calculated ().Equal (sets .NewString (ComponentKubelet )) {
446- ok , err := execKubelet (kubeletArgs )
447- if ! ok {
448- return err
449- }
450- if err != nil {
475+ if err := execKubelet (kubeletArgs ); err != nil {
451476 utilruntime .HandleError (fmt .Errorf ("Unable to call exec on kubelet, continuing with normal startup: %v" , err ))
452477 }
453478 }
0 commit comments