Skip to content

Commit 049d247

Browse files
Support --write-flags on openshift start node
This acts like --write-config, but instead outputs the flags that the specified --config file would pass to the Kubelet. This prepares us to stop calling openshift start node and instead direct invoke the Kubelet. Remove the unsupported kubelet binary code because we are no longer calling ourself. Also move a small chunk of flag specialization code into a more appropriate place.
1 parent 0592af3 commit 049d247

File tree

6 files changed

+109
-72
lines changed

6 files changed

+109
-72
lines changed

contrib/completions/bash/openshift

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contrib/completions/zsh/openshift

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cmd/server/api/validation/node.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ func ValidateNodeConfig(config *api.NodeConfig, fldPath *field.Path) ValidationR
2424
func ValidateInClusterNodeConfig(config *api.NodeConfig, fldPath *field.Path) ValidationResults {
2525
validationResults := ValidationResults{}
2626

27-
if len(config.NodeName) == 0 {
27+
hasBootstrapConfig := len(config.KubeletArguments["bootstrap-kubeconfig"]) > 0
28+
if len(config.NodeName) == 0 && !hasBootstrapConfig {
2829
validationResults.AddErrors(field.Required(fldPath.Child("nodeName"), ""))
2930
}
3031
if len(config.NodeIP) > 0 {
@@ -42,7 +43,9 @@ func ValidateInClusterNodeConfig(config *api.NodeConfig, fldPath *field.Path) Va
4243
validationResults.AddErrors(ValidateHostPort(config.DNSBindAddress, fldPath.Child("dnsBindAddress"))...)
4344
}
4445
if len(config.DNSIP) > 0 {
45-
validationResults.AddErrors(ValidateSpecifiedIP(config.DNSIP, fldPath.Child("dnsIP"))...)
46+
if !hasBootstrapConfig || config.DNSIP != "0.0.0.0" {
47+
validationResults.AddErrors(ValidateSpecifiedIP(config.DNSIP, fldPath.Child("dnsIP"))...)
48+
}
4649
}
4750
for i, nameserver := range config.DNSNameservers {
4851
validationResults.AddErrors(ValidateSpecifiedIPPort(nameserver, fldPath.Child("dnsNameservers").Index(i))...)

pkg/cmd/server/kubernetes/node/options/options.go

+24-21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package node
33
import (
44
"fmt"
55
"net"
6+
"sort"
67
"strings"
78
"time"
89

@@ -23,9 +24,8 @@ import (
2324
"github.com/openshift/origin/pkg/network"
2425
)
2526

26-
// computeKubeletFlags returns the flags to use when starting the kubelet
27-
// TODO this needs to return a []string and be passed to cobra, but as an intermediate step, we'll compute the map and run it through the existing paths
28-
func ComputeKubeletFlagsAsMap(startingArgs map[string][]string, options configapi.NodeConfig) (map[string][]string, error) {
27+
// ComputeKubeletFlags returns the flags to use when starting the kubelet.
28+
func ComputeKubeletFlags(startingArgs map[string][]string, options configapi.NodeConfig) ([]string, error) {
2929
args := map[string][]string{}
3030
for key, slice := range startingArgs {
3131
for _, val := range slice {
@@ -115,32 +115,35 @@ func ComputeKubeletFlagsAsMap(startingArgs map[string][]string, options configap
115115
}
116116
}
117117

118-
// we sometimes have different clusterdns options. I really don't understand why
119-
externalKubeClient, _, err := configapi.GetExternalKubeClient(options.MasterKubeConfig, options.MasterClientConnectionOverrides)
120-
if err != nil {
121-
return nil, err
122-
}
123-
args["cluster-dns"] = getClusterDNS(externalKubeClient, args["cluster-dns"])
124-
125-
return args, nil
126-
}
127-
128-
func KubeletArgsMapToArgs(argsMap map[string][]string) []string {
129-
args := []string{}
130-
for key, value := range argsMap {
131-
for _, token := range value {
132-
args = append(args, fmt.Sprintf("--%s=%v", key, token))
118+
// default cluster-dns to the master's DNS if possible, but only if we can reach the master
119+
// TODO: this exists to support legacy cases where the node defaulted to the master's DNS.
120+
// we can remove this when we drop support for master DNS when CoreDNS is in use everywhere.
121+
if len(args["cluster-dns"]) == 0 {
122+
if externalKubeClient, _, err := configapi.GetExternalKubeClient(options.MasterKubeConfig, options.MasterClientConnectionOverrides); err == nil {
123+
args["cluster-dns"] = getClusterDNS(externalKubeClient, args["cluster-dns"])
133124
}
134125
}
135126

136127
// there is a special case. If you set `--cgroups-per-qos=false` and `--enforce-node-allocatable` is
137128
// an empty string, `--enforce-node-allocatable=""` needs to be explicitly set
138129
// cgroups-per-qos defaults to true
139-
if cgroupArg, enforceAllocatable := argsMap["cgroups-per-qos"], argsMap["enforce-node-allocatable"]; len(cgroupArg) == 1 && cgroupArg[0] == "false" && len(enforceAllocatable) == 0 {
140-
args = append(args, `--enforce-node-allocatable=`)
130+
if cgroupArg, enforceAllocatable := args["cgroups-per-qos"], args["enforce-node-allocatable"]; len(cgroupArg) == 1 && cgroupArg[0] == "false" && len(enforceAllocatable) == 0 {
131+
args["enforce-node-allocatable"] = []string{""}
141132
}
142133

143-
return args
134+
var keys []string
135+
for key := range args {
136+
keys = append(keys, key)
137+
}
138+
sort.Strings(keys)
139+
140+
var arguments []string
141+
for _, key := range keys {
142+
for _, token := range args[key] {
143+
arguments = append(arguments, fmt.Sprintf("--%s=%v", key, token))
144+
}
145+
}
146+
return arguments, nil
144147
}
145148

146149
func setIfUnset(cmdLineArgs map[string][]string, key string, value ...string) {

pkg/cmd/server/start/node_args.go

+4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ type NodeArgs struct {
5252
// Components is the set of enabled components.
5353
Components *utilflags.ComponentFlag
5454

55+
// WriteFlagsOnly will print flags to run the Kubelet from the provided arguments rather than launching
56+
// the Kubelet itself.
57+
WriteFlagsOnly bool
58+
5559
// NodeName is the hostname to identify this node with the master.
5660
NodeName string
5761

pkg/cmd/server/start/start_node.go

+72-49
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
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,28 +420,57 @@ 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)
440+
}
441+
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+
kubeletArgs, err := nodeoptions.ComputeKubeletFlags(nodeConfig.KubeletArguments, nodeConfig)
446+
if err != nil {
447+
return fmt.Errorf("cannot create kubelet args: %v", err)
448+
}
449+
if err := nodeoptions.CheckFlags(kubeletArgs); err != nil {
450+
return err
451+
}
452+
var outputArgs []string
453+
for _, s := range kubeletArgs {
454+
outputArgs = append(outputArgs, shellEscapeArg(s))
455+
}
456+
fmt.Println(strings.Join(outputArgs, " "))
457+
return nil
431458
}
432459

460+
// StartNode launches the node processes.
433461
func StartNode(nodeConfig configapi.NodeConfig, components *utilflags.ComponentFlag) error {
434-
kubeletFlagsAsMap, err := nodeoptions.ComputeKubeletFlagsAsMap(nodeConfig.KubeletArguments, nodeConfig)
462+
kubeletArgs, err := nodeoptions.ComputeKubeletFlags(nodeConfig.KubeletArguments, nodeConfig)
435463
if err != nil {
436464
return fmt.Errorf("cannot create kubelet args: %v", err)
437465
}
438-
kubeletArgs := nodeoptions.KubeletArgsMapToArgs(kubeletFlagsAsMap)
439466
if err := nodeoptions.CheckFlags(kubeletArgs); err != nil {
440467
return err
441468
}
442469

443470
// as a step towards decomposing OpenShift into Kubernetes components, perform an execve
444471
// to launch the Kubelet instead of loading in-process
445472
if components.Calculated().Equal(sets.NewString(ComponentKubelet)) {
446-
ok, err := execKubelet(kubeletArgs)
447-
if !ok {
448-
return err
449-
}
450-
if err != nil {
473+
if err := execKubelet(kubeletArgs); err != nil {
451474
utilruntime.HandleError(fmt.Errorf("Unable to call exec on kubelet, continuing with normal startup: %v", err))
452475
}
453476
}
@@ -475,9 +498,9 @@ func StartNode(nodeConfig configapi.NodeConfig, components *utilflags.ComponentF
475498
glog.V(4).Infof("Unable to build network options: %v", err)
476499
return err
477500
}
478-
clusterDomain := ""
479-
if len(kubeletFlagsAsMap["cluster-domain"]) > 0 {
480-
clusterDomain = kubeletFlagsAsMap["cluster-domain"][0]
501+
clusterDomain := nodeConfig.DNSDomain
502+
if len(nodeConfig.KubeletArguments["cluster-domain"]) > 0 {
503+
clusterDomain = nodeConfig.KubeletArguments["cluster-domain"][0]
481504
}
482505
networkConfig, err := network.New(nodeConfig, clusterDomain, proxyConfig, components.Enabled(ComponentProxy), components.Enabled(ComponentDNS) && len(nodeConfig.DNSBindAddress) > 0)
483506
if err != nil {

0 commit comments

Comments
 (0)