7
7
"os"
8
8
"os/exec"
9
9
"path/filepath"
10
+ "regexp"
11
+ "sort"
12
+ "strconv"
10
13
"strings"
11
14
"syscall"
12
15
@@ -91,6 +94,7 @@ func NewCommandStartNode(basename string, out, errout io.Writer) (*cobra.Command
91
94
BindImageFormatArgs (options .NodeArgs .ImageFormatArgs , flags , "" )
92
95
BindKubeConnectionArgs (options .NodeArgs .KubeConnectionArgs , flags , "" )
93
96
97
+ flags .BoolVar (& options .NodeArgs .WriteFlagsOnly , "write-flags" , false , "When this is specified only the arguments necessary to start the Kubelet will be output." )
94
98
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)." )
95
99
96
100
// autocompletion hints
@@ -172,12 +176,20 @@ func (o NodeOptions) Validate(args []string) error {
172
176
if o .IsRunFromConfig () {
173
177
return errors .New ("--config may not be set if you're only writing the config" )
174
178
}
179
+ if o .NodeArgs .WriteFlagsOnly {
180
+ return errors .New ("--write-config and --write-flags are mutually exclusive" )
181
+ }
175
182
}
176
183
177
184
// 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
185
+ if len (o .NodeArgs .BootstrapConfigName ) > 0 {
186
+ if o .NodeArgs .WriteFlagsOnly {
187
+ return errors .New ("--write-flags is mutually exclusive with --bootstrap-config-name" )
188
+ }
189
+ if ! o .IsRunFromConfig () {
190
+ if err := o .NodeArgs .Validate (); err != nil {
191
+ return err
192
+ }
181
193
}
182
194
}
183
195
@@ -201,7 +213,7 @@ func (o NodeOptions) StartNode() error {
201
213
return err
202
214
}
203
215
204
- if o .IsWriteConfigOnly () {
216
+ if o .NodeArgs . WriteFlagsOnly || o . IsWriteConfigOnly () {
205
217
return nil
206
218
}
207
219
@@ -224,6 +236,17 @@ func (o NodeOptions) RunNode() error {
224
236
if addr := o .NodeArgs .ListenArg .ListenAddr ; addr .Provided {
225
237
nodeConfig .ServingInfo .BindAddress = addr .HostPort (o .NodeArgs .ListenArg .ListenAddr .DefaultPort )
226
238
}
239
+ // do a local resolution of node config DNS IP, supports bootstrapping cases
240
+ if nodeConfig .DNSIP == "0.0.0.0" {
241
+ glog .V (4 ).Infof ("Defaulting to the DNSIP config to the node's IP" )
242
+ nodeConfig .DNSIP = nodeConfig .NodeIP
243
+ // TODO: the Kubelet should do this defaulting (to the IP it recognizes)
244
+ if len (nodeConfig .DNSIP ) == 0 {
245
+ if ip , err := cmdutil .DefaultLocalIP4 (); err == nil {
246
+ nodeConfig .DNSIP = ip .String ()
247
+ }
248
+ }
249
+ }
227
250
228
251
var validationResults validation.ValidationResults
229
252
switch {
@@ -256,11 +279,11 @@ func (o NodeOptions) RunNode() error {
256
279
return nil
257
280
}
258
281
259
- if err := StartNode ( * nodeConfig , o .NodeArgs .Components ); err != nil {
260
- return err
282
+ if o .NodeArgs .WriteFlagsOnly {
283
+ return WriteKubeletFlags ( * nodeConfig )
261
284
}
262
285
263
- return nil
286
+ return StartNode ( * nodeConfig , o . NodeArgs . Components )
264
287
}
265
288
266
289
// resolveNodeConfig creates a new configuration on disk by reading from the master, reads
@@ -371,41 +394,13 @@ func (o NodeOptions) IsRunFromConfig() bool {
371
394
}
372
395
373
396
// 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
397
+ // in server passed as flags.
398
+ func execKubelet (kubeletArgs []string ) error {
380
399
path := "kubelet"
381
- requireSameBinary := true
382
- if newPath := os .Getenv ("OPENSHIFT_ALLOW_UNSUPPORTED_KUBELET" ); len (newPath ) > 0 {
383
- requireSameBinary = false
384
- path = newPath
385
- }
386
400
kubeletPath , err := exec .LookPath (path )
387
401
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 )
402
+ return err
407
403
}
408
-
409
404
// convert current settings to flags
410
405
args := append ([]string {kubeletPath }, kubeletArgs ... )
411
406
for i := glog .Level (10 ); i > 0 ; i -- {
@@ -426,28 +421,77 @@ func execKubelet(kubeletArgs []string) (bool, error) {
426
421
break
427
422
}
428
423
}
424
+ // execve the child process, replacing this process
429
425
glog .V (3 ).Infof ("Exec %s %s" , kubeletPath , strings .Join (args , " " ))
430
- return false , syscall .Exec (kubeletPath , args , os .Environ ())
426
+ return syscall .Exec (kubeletPath , args , os .Environ ())
431
427
}
432
428
429
+ // safeArgRegexp matches only characters that are known safe. DO NOT add to this list
430
+ // without fully considering whether that new character can be used to break shell escaping
431
+ // rules.
432
+ var safeArgRegexp = regexp .MustCompile (`^[\da-zA-Z\-=_\.,/\:]+$` )
433
+
434
+ // shellEscapeArg quotes an argument if it contains characters that my cause a shell
435
+ // interpreter to split the single argument into multiple.
436
+ func shellEscapeArg (s string ) string {
437
+ if safeArgRegexp .MatchString (s ) {
438
+ return s
439
+ }
440
+ return strconv .Quote (s )
441
+ }
442
+
443
+ // argsMapToArgs writes a sorted list of arguments for flags. It does not escape the
444
+ // flag values.
445
+ func argsMapToArgs (argsMap map [string ][]string ) []string {
446
+ var keys []string
447
+ for key := range argsMap {
448
+ keys = append (keys , key )
449
+ }
450
+ sort .Strings (keys )
451
+
452
+ var args []string
453
+ for _ , key := range keys {
454
+ for _ , token := range argsMap [key ] {
455
+ args = append (args , fmt .Sprintf ("--%s=%v" , key , token ))
456
+ }
457
+ }
458
+ return args
459
+ }
460
+
461
+ // WriteKubeletFlags writes the correct set of flags to start a Kubelet from the provided node config to
462
+ // stdout, instead of launching anything.
463
+ func WriteKubeletFlags (nodeConfig configapi.NodeConfig ) error {
464
+ kubeletFlagsAsMap , err := nodeoptions .ComputeKubeletFlagsAsMap (nodeConfig .KubeletArguments , nodeConfig )
465
+ if err != nil {
466
+ return fmt .Errorf ("cannot create kubelet args: %v" , err )
467
+ }
468
+ kubeletArgs := argsMapToArgs (kubeletFlagsAsMap )
469
+ if err := nodeoptions .CheckFlags (kubeletArgs ); err != nil {
470
+ return err
471
+ }
472
+ var outputArgs []string
473
+ for _ , s := range kubeletArgs {
474
+ outputArgs = append (outputArgs , shellEscapeArg (s ))
475
+ }
476
+ fmt .Println (strings .Join (outputArgs , " " ))
477
+ return nil
478
+ }
479
+
480
+ // StartNode launches the node processes.
433
481
func StartNode (nodeConfig configapi.NodeConfig , components * utilflags.ComponentFlag ) error {
434
482
kubeletFlagsAsMap , err := nodeoptions .ComputeKubeletFlagsAsMap (nodeConfig .KubeletArguments , nodeConfig )
435
483
if err != nil {
436
484
return fmt .Errorf ("cannot create kubelet args: %v" , err )
437
485
}
438
- kubeletArgs := nodeoptions . KubeletArgsMapToArgs (kubeletFlagsAsMap )
486
+ kubeletArgs := argsMapToArgs (kubeletFlagsAsMap )
439
487
if err := nodeoptions .CheckFlags (kubeletArgs ); err != nil {
440
488
return err
441
489
}
442
490
443
491
// as a step towards decomposing OpenShift into Kubernetes components, perform an execve
444
492
// to launch the Kubelet instead of loading in-process
445
493
if components .Calculated ().Equal (sets .NewString (ComponentKubelet )) {
446
- ok , err := execKubelet (kubeletArgs )
447
- if ! ok {
448
- return err
449
- }
450
- if err != nil {
494
+ if err := execKubelet (kubeletArgs ); err != nil {
451
495
utilruntime .HandleError (fmt .Errorf ("Unable to call exec on kubelet, continuing with normal startup: %v" , err ))
452
496
}
453
497
}
0 commit comments