Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write Kubelet flags as an option on openshift start node to support moving directly to kubelet #18322

Merged
merged 2 commits into from
Feb 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contrib/completions/bash/openshift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions contrib/completions/zsh/openshift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pkg/cmd/server/api/validation/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ func ValidateNodeConfig(config *api.NodeConfig, fldPath *field.Path) ValidationR
func ValidateInClusterNodeConfig(config *api.NodeConfig, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}

if len(config.NodeName) == 0 {
hasBootstrapConfig := len(config.KubeletArguments["bootstrap-kubeconfig"]) > 0
if len(config.NodeName) == 0 && !hasBootstrapConfig {
validationResults.AddErrors(field.Required(fldPath.Child("nodeName"), ""))
}
if len(config.NodeIP) > 0 {
Expand All @@ -42,7 +43,9 @@ func ValidateInClusterNodeConfig(config *api.NodeConfig, fldPath *field.Path) Va
validationResults.AddErrors(ValidateHostPort(config.DNSBindAddress, fldPath.Child("dnsBindAddress"))...)
}
if len(config.DNSIP) > 0 {
validationResults.AddErrors(ValidateSpecifiedIP(config.DNSIP, fldPath.Child("dnsIP"))...)
if !hasBootstrapConfig || config.DNSIP != "0.0.0.0" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

demorgans please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think that's any easier to read. parenthesis are more confusing.

validationResults.AddErrors(ValidateSpecifiedIP(config.DNSIP, fldPath.Child("dnsIP"))...)
}
}
for i, nameserver := range config.DNSNameservers {
validationResults.AddErrors(ValidateSpecifiedIPPort(nameserver, fldPath.Child("dnsNameservers").Index(i))...)
Expand Down
45 changes: 24 additions & 21 deletions pkg/cmd/server/kubernetes/node/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package node
import (
"fmt"
"net"
"sort"
"strings"
"time"

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

// computeKubeletFlags returns the flags to use when starting the kubelet
// 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
func ComputeKubeletFlagsAsMap(startingArgs map[string][]string, options configapi.NodeConfig) (map[string][]string, error) {
// ComputeKubeletFlags returns the flags to use when starting the kubelet.
func ComputeKubeletFlags(startingArgs map[string][]string, options configapi.NodeConfig) ([]string, error) {
args := map[string][]string{}
for key, slice := range startingArgs {
for _, val := range slice {
Expand Down Expand Up @@ -115,32 +115,35 @@ func ComputeKubeletFlagsAsMap(startingArgs map[string][]string, options configap
}
}

// we sometimes have different clusterdns options. I really don't understand why
externalKubeClient, _, err := configapi.GetExternalKubeClient(options.MasterKubeConfig, options.MasterClientConnectionOverrides)
if err != nil {
return nil, err
}
args["cluster-dns"] = getClusterDNS(externalKubeClient, args["cluster-dns"])

return args, nil
}

func KubeletArgsMapToArgs(argsMap map[string][]string) []string {
args := []string{}
for key, value := range argsMap {
for _, token := range value {
args = append(args, fmt.Sprintf("--%s=%v", key, token))
// default cluster-dns to the master's DNS if possible, but only if we can reach the master
// TODO: this exists to support legacy cases where the node defaulted to the master's DNS.
// we can remove this when we drop support for master DNS when CoreDNS is in use everywhere.
if len(args["cluster-dns"]) == 0 {
if externalKubeClient, _, err := configapi.GetExternalKubeClient(options.MasterKubeConfig, options.MasterClientConnectionOverrides); err == nil {
args["cluster-dns"] = getClusterDNS(externalKubeClient, args["cluster-dns"])
}
}

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

return args
var keys []string
for key := range args {
keys = append(keys, key)
}
sort.Strings(keys)

var arguments []string
for _, key := range keys {
for _, token := range args[key] {
arguments = append(arguments, fmt.Sprintf("--%s=%v", key, token))
}
}
return arguments, nil
}

func setIfUnset(cmdLineArgs map[string][]string, key string, value ...string) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/cmd/server/start/node_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ type NodeArgs struct {
// Components is the set of enabled components.
Components *utilflags.ComponentFlag

// WriteFlagsOnly will print flags to run the Kubelet from the provided arguments rather than launching
// the Kubelet itself.
WriteFlagsOnly bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sdodson I knew it was only a matter of time....


// NodeName is the hostname to identify this node with the master.
NodeName string

Expand Down
121 changes: 72 additions & 49 deletions pkg/cmd/server/start/start_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"syscall"

Expand Down Expand Up @@ -91,6 +93,7 @@ func NewCommandStartNode(basename string, out, errout io.Writer) (*cobra.Command
BindImageFormatArgs(options.NodeArgs.ImageFormatArgs, flags, "")
BindKubeConnectionArgs(options.NodeArgs.KubeConnectionArgs, flags, "")

flags.BoolVar(&options.NodeArgs.WriteFlagsOnly, "write-flags", false, "When this is specified only the arguments necessary to start the Kubelet will be output.")
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).")

// autocompletion hints
Expand Down Expand Up @@ -172,12 +175,20 @@ func (o NodeOptions) Validate(args []string) error {
if o.IsRunFromConfig() {
return errors.New("--config may not be set if you're only writing the config")
}
if o.NodeArgs.WriteFlagsOnly {
return errors.New("--write-config and --write-flags are mutually exclusive")
}
}

// if we are starting up using a config file, run no validations here
if len(o.NodeArgs.BootstrapConfigName) > 0 && !o.IsRunFromConfig() {
if err := o.NodeArgs.Validate(); err != nil {
return err
if len(o.NodeArgs.BootstrapConfigName) > 0 {
if o.NodeArgs.WriteFlagsOnly {
return errors.New("--write-flags is mutually exclusive with --bootstrap-config-name")
}
if !o.IsRunFromConfig() {
if err := o.NodeArgs.Validate(); err != nil {
return err
}
}
}

Expand All @@ -201,7 +212,7 @@ func (o NodeOptions) StartNode() error {
return err
}

if o.IsWriteConfigOnly() {
if o.NodeArgs.WriteFlagsOnly || o.IsWriteConfigOnly() {
return nil
}

Expand All @@ -224,6 +235,17 @@ func (o NodeOptions) RunNode() error {
if addr := o.NodeArgs.ListenArg.ListenAddr; addr.Provided {
nodeConfig.ServingInfo.BindAddress = addr.HostPort(o.NodeArgs.ListenArg.ListenAddr.DefaultPort)
}
// do a local resolution of node config DNS IP, supports bootstrapping cases
if nodeConfig.DNSIP == "0.0.0.0" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is not a listen address, it is a connection address? Using 0.0.0.0 to do defaulting feels wrong. ***autodetect*** is an illegal value for a hostname or IP that will never be confused as something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.0.0.0 was never allowed before. We changed it in 3.7 to mean "the local address". It has to be an IP because of DNS. We can't change the value that means autodetect because 3.7 clusters are using 0.0.0.0

glog.V(4).Infof("Defaulting to the DNSIP config to the node's IP")
nodeConfig.DNSIP = nodeConfig.NodeIP
// TODO: the Kubelet should do this defaulting (to the IP it recognizes)
if len(nodeConfig.DNSIP) == 0 {
if ip, err := cmdutil.DefaultLocalIP4(); err == nil {
nodeConfig.DNSIP = ip.String()
}
}
}

var validationResults validation.ValidationResults
switch {
Expand Down Expand Up @@ -256,11 +278,11 @@ func (o NodeOptions) RunNode() error {
return nil
}

if err := StartNode(*nodeConfig, o.NodeArgs.Components); err != nil {
return err
if o.NodeArgs.WriteFlagsOnly {
return WriteKubeletFlags(*nodeConfig)
}

return nil
return StartNode(*nodeConfig, o.NodeArgs.Components)
}

// resolveNodeConfig creates a new configuration on disk by reading from the master, reads
Expand Down Expand Up @@ -371,41 +393,13 @@ func (o NodeOptions) IsRunFromConfig() bool {
}

// execKubelet attempts to call execve() for the kubelet with the configuration defined
// in server passed as flags. If the binary is not the same as the current file and
// the environment variable OPENSHIFT_ALLOW_UNSUPPORTED_KUBELET is unset, the method
// will return an error. The returned boolean indicates whether fallback to in-process
// is allowed.
func execKubelet(kubeletArgs []string) (bool, error) {
// verify the Kubelet binary to use
// in server passed as flags.
func execKubelet(kubeletArgs []string) error {
path := "kubelet"
requireSameBinary := true
if newPath := os.Getenv("OPENSHIFT_ALLOW_UNSUPPORTED_KUBELET"); len(newPath) > 0 {
requireSameBinary = false
path = newPath
}
kubeletPath, err := exec.LookPath(path)
if err != nil {
return requireSameBinary, err
}
kubeletFile, err := os.Stat(kubeletPath)
if err != nil {
return requireSameBinary, err
}
thisPath, err := exec.LookPath(os.Args[0])
if err != nil {
return true, err
}
thisFile, err := os.Stat(thisPath)
if err != nil {
return true, err
}
if !os.SameFile(thisFile, kubeletFile) {
if requireSameBinary {
return true, fmt.Errorf("binary at %q is not the same file as %q, cannot execute", thisPath, kubeletPath)
}
glog.Warningf("UNSUPPORTED: Executing a different Kubelet than the current binary is not supported: %s", kubeletPath)
return err
}

// convert current settings to flags
args := append([]string{kubeletPath}, kubeletArgs...)
for i := glog.Level(10); i > 0; i-- {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this actually doing? Why is it doing it? I couldn't sort it out and a few lines down on the --vmodule the sprintf looks broken anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glog doesn't let you get the glog value directly, you have to test each level on your own.

Expand All @@ -426,28 +420,57 @@ func execKubelet(kubeletArgs []string) (bool, error) {
break
}
}
// execve the child process, replacing this process
glog.V(3).Infof("Exec %s %s", kubeletPath, strings.Join(args, " "))
return false, syscall.Exec(kubeletPath, args, os.Environ())
return syscall.Exec(kubeletPath, args, os.Environ())
}

// safeArgRegexp matches only characters that are known safe. DO NOT add to this list
// without fully considering whether that new character can be used to break shell escaping
// rules.
var safeArgRegexp = regexp.MustCompile(`^[\da-zA-Z\-=_\.,/\:]+$`)

// shellEscapeArg quotes an argument if it contains characters that my cause a shell
// interpreter to split the single argument into multiple.
func shellEscapeArg(s string) string {
if safeArgRegexp.MatchString(s) {
return s
}
return strconv.Quote(s)
}

// WriteKubeletFlags writes the correct set of flags to start a Kubelet from the provided node config to
// stdout, instead of launching anything.
func WriteKubeletFlags(nodeConfig configapi.NodeConfig) error {
kubeletArgs, err := nodeoptions.ComputeKubeletFlags(nodeConfig.KubeletArguments, nodeConfig)
if err != nil {
return fmt.Errorf("cannot create kubelet args: %v", err)
}
if err := nodeoptions.CheckFlags(kubeletArgs); err != nil {
return err
}
var outputArgs []string
for _, s := range kubeletArgs {
outputArgs = append(outputArgs, shellEscapeArg(s))
}
fmt.Println(strings.Join(outputArgs, " "))
return nil
}

// StartNode launches the node processes.
func StartNode(nodeConfig configapi.NodeConfig, components *utilflags.ComponentFlag) error {
kubeletFlagsAsMap, err := nodeoptions.ComputeKubeletFlagsAsMap(nodeConfig.KubeletArguments, nodeConfig)
kubeletArgs, err := nodeoptions.ComputeKubeletFlags(nodeConfig.KubeletArguments, nodeConfig)
if err != nil {
return fmt.Errorf("cannot create kubelet args: %v", err)
}
kubeletArgs := nodeoptions.KubeletArgsMapToArgs(kubeletFlagsAsMap)
if err := nodeoptions.CheckFlags(kubeletArgs); err != nil {
return err
}

// as a step towards decomposing OpenShift into Kubernetes components, perform an execve
// to launch the Kubelet instead of loading in-process
if components.Calculated().Equal(sets.NewString(ComponentKubelet)) {
ok, err := execKubelet(kubeletArgs)
if !ok {
return err
}
if err != nil {
if err := execKubelet(kubeletArgs); err != nil {
utilruntime.HandleError(fmt.Errorf("Unable to call exec on kubelet, continuing with normal startup: %v", err))
}
}
Expand Down Expand Up @@ -475,9 +498,9 @@ func StartNode(nodeConfig configapi.NodeConfig, components *utilflags.ComponentF
glog.V(4).Infof("Unable to build network options: %v", err)
return err
}
clusterDomain := ""
if len(kubeletFlagsAsMap["cluster-domain"]) > 0 {
clusterDomain = kubeletFlagsAsMap["cluster-domain"][0]
clusterDomain := nodeConfig.DNSDomain
if len(nodeConfig.KubeletArguments["cluster-domain"]) > 0 {
clusterDomain = nodeConfig.KubeletArguments["cluster-domain"][0]
}
networkConfig, err := network.New(nodeConfig, clusterDomain, proxyConfig, components.Enabled(ComponentProxy), components.Enabled(ComponentDNS) && len(nodeConfig.DNSBindAddress) > 0)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading