From 7d2ac1dbd696bf4945b5ac38023356333d209125 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Thu, 9 Aug 2018 19:03:56 +0200 Subject: [PATCH] kubeadm-ha-join-controlplane --- cmd/kubeadm/app/apis/kubeadm/types.go | 8 + .../app/apis/kubeadm/v1alpha2/types.go | 8 + .../app/apis/kubeadm/v1alpha3/types.go | 8 + .../app/apis/kubeadm/validation/validation.go | 1 + cmd/kubeadm/app/cmd/join.go | 258 ++++++++++++++++-- .../app/phases/kubeconfig/kubeconfig.go | 9 +- .../app/phases/kubeconfig/kubeconfig_test.go | 4 +- cmd/kubeadm/app/preflight/checks.go | 4 +- cmd/kubeadm/app/util/config/nodeconfig.go | 10 + 9 files changed, 282 insertions(+), 28 deletions(-) diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index fe8509e20f0..8b46a0450d7 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -300,6 +300,14 @@ type JoinConfiguration struct { // the security of kubeadm since other nodes can impersonate the master. DiscoveryTokenUnsafeSkipCAVerification bool + // ControlPlane flag specifies that the joining node should host an additional + // control plane instance. + ControlPlane bool + + // AdvertiseAddress sets the IP address for the API server to advertise; the + // API server will be installed only on nodes hosting an additional control plane instance. + AdvertiseAddress string + // FeatureGates enabled by the user. FeatureGates map[string]bool } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go index 613e66371af..2003e5e019d 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go @@ -277,6 +277,14 @@ type JoinConfiguration struct { // the security of kubeadm since other nodes can impersonate the master. DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"` + // ControlPlane flag specifies that the joining node should host an additional + // control plane instance. + ControlPlane bool `json:"controlPlane,omitempty"` + + // AdvertiseAddress sets the IP address for the API server to advertise; the + // API server will be installed only on nodes hosting an additional control plane instance. + AdvertiseAddress string `json:"advertiseAddress,omitempty"` + // FeatureGates enabled by the user. FeatureGates map[string]bool `json:"featureGates,omitempty"` } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha3/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/types.go index 17442a406e1..39d072b7946 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha3/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/types.go @@ -271,6 +271,14 @@ type JoinConfiguration struct { // the security of kubeadm since other nodes can impersonate the master. DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"` + // ControlPlane flag specifies that the joining node should host an additional + // control plane instance. + ControlPlane bool `json:"controlPlane,omitempty"` + + // AdvertiseAddress sets the IP address for the API server to advertise; the + // API server will be installed only on nodes hosting an additional control plane instance. + AdvertiseAddress string `json:"advertiseAddress,omitempty"` + // FeatureGates enabled by the user. FeatureGates map[string]bool `json:"featureGates,omitempty"` } diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index d8470b7d005..bd2aee684e5 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -61,6 +61,7 @@ func ValidateJoinConfiguration(c *kubeadm.JoinConfiguration) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, ValidateDiscovery(c)...) allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...) + allErrs = append(allErrs, ValidateIPFromString(c.AdvertiseAddress, field.NewPath("advertiseAddress"))...) if !filepath.IsAbs(c.CACertPath) || !strings.HasSuffix(c.CACertPath, ".crt") { allErrs = append(allErrs, field.Invalid(field.NewPath("caCertPath"), c.CACertPath, "the ca certificate path must be an absolute path")) diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 64195f18e2c..f879f731ef6 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -17,19 +17,23 @@ limitations under the License. package cmd import ( + "bytes" "fmt" "io" "os" "path/filepath" "strings" + "text/template" "github.com/golang/glog" + "github.com/pkg/errors" "github.com/renstrom/dedent" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" @@ -38,7 +42,11 @@ import ( kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/discovery" "k8s.io/kubernetes/cmd/kubeadm/app/features" + certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" + controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" + kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" + markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster" patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" @@ -49,15 +57,45 @@ import ( ) var ( - joinDoneMsgf = dedent.Dedent(` + joinWorkerNodeDoneMsg = dedent.Dedent(` This node has joined the cluster: - * Certificate signing request was sent to master and a response - was received. + * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run 'kubectl get nodes' on the master to see this node join the cluster. + `) + notReadyToJoinControPlaneTemp = template.Must(template.New("join").Parse(dedent.Dedent(` + One or more conditions for hosting a new control plane instance is not satisfied. + + {{.Error}} + + Please ensure that: + * The cluster has a stable api.controlPlaneEndpoint address. + * The cluster uses an external etcd. + * The certificates that must be shared among control plane instances are provided. + + `))) + + joinControPlaneDoneTemp = template.Must(template.New("join").Parse(dedent.Dedent(` + This node has joined the cluster and a new control plane instance was created: + + * Certificate signing request was sent to apiserver and approval was received. + * The Kubelet was informed of the new secure connection details. + * Master label and taint were applied to the new node. + * The kubernetes control plane instances scaled up. + + To start administering your cluster from this node, you need to run the following as a regular user: + + mkdir -p $HOME/.kube + sudo cp -i {{.KubeConfigPath}} $HOME/.kube/config + sudo chown $(id -u):$(id -g) $HOME/.kube/config + + Run 'kubectl get nodes' to see this node join the cluster. + + `))) + joinLongDescription = dedent.Dedent(` When joining a kubeadm initialized cluster, we need to establish bidirectional trust. This is split into discovery (having the Node @@ -82,7 +120,7 @@ var ( where the supported hash type is "sha256". The hash is calculated over the bytes of the Subject Public Key Info (SPKI) object (as in RFC7469). This value is available in the output of "kubeadm init" or can be - calcuated using standard tools. The --discovery-token-ca-cert-hash flag + calculated using standard tools. The --discovery-token-ca-cert-hash flag may be repeated multiple times to allow more than one public key. If you cannot know the CA public key hash ahead of time, you can pass @@ -101,7 +139,7 @@ var ( --token flag can be used instead of specifying each token individually. `) - kubeadmJoinFailMsgf = dedent.Dedent(` + kubeadmJoinFailMsg = dedent.Dedent(` Unfortunately, an error has occurred: %v @@ -169,7 +207,7 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha3.JoinConfi "A file or url from which to load cluster information.") flagSet.StringVar( &cfg.DiscoveryToken, "discovery-token", "", - "A token used to validate cluster information fetched from the master.") + "A token used to validate cluster information fetched from the api server.") flagSet.StringVar( &cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name, "Specify the node name.") @@ -193,6 +231,13 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha3.JoinConfi &cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket, `Specify the CRI socket to connect to.`, ) + flagSet.BoolVar( + &cfg.ControlPlane, "experimental-control-plane", cfg.ControlPlane, + "Create a new control plane instance on this node") + flagSet.StringVar( + &cfg.AdvertiseAddress, "apiserver-advertise-address", cfg.AdvertiseAddress, + "If the node should host a new control plane instance, the IP address the API Server will advertise it's listening on.", + ) } // AddJoinOtherFlags adds join flags that are not bound to a configuration file to the given flagset @@ -217,8 +262,11 @@ type Join struct { func NewJoin(cfgPath string, args []string, defaultcfg *kubeadmapiv1alpha3.JoinConfiguration, ignorePreflightErrors sets.String) (*Join, error) { if defaultcfg.NodeRegistration.Name == "" { - glog.V(1).Infoln("[join] found NodeName empty") - glog.V(1).Infoln("[join] considered OS hostname as NodeName") + glog.V(1).Infoln("[join] found NodeName empty; using OS hostname as NodeName") + } + + if defaultcfg.AdvertiseAddress == "" { + glog.V(1).Infoln("[join] found advertiseAddress empty; using default interface's IP address as advertiseAddress") } internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(cfgPath, defaultcfg) @@ -239,27 +287,181 @@ func NewJoin(cfgPath string, args []string, defaultcfg *kubeadmapiv1alpha3.JoinC // Run executes worker node provisioning and tries to join an existing cluster. func (j *Join) Run(out io.Writer) error { - // Perform the Discovery, which turns a Bootstrap Token and optionally (and preferably) a CA cert hash into a KubeConfig // file that may be used for the TLS Bootstrapping process the kubelet performs using the Certificates API. - glog.V(1).Infoln("[join] retrieving KubeConfig objects") - cfg, err := discovery.For(j.cfg) + glog.V(1).Infoln("[join] discovering cluster-info") + tlsBootstrapCfg, err := discovery.For(j.cfg) if err != nil { return err } + // If the node should host a new control plane instance + if j.cfg.ControlPlane == true { + // Retrives the kubeadm configuration used during kubeadm init + glog.V(1).Infoln("[join] retrieving KubeConfig objects") + clusterConfiguration, err := j.FetchInitClusterConfiguration(tlsBootstrapCfg) + if err != nil { + return err + } + + // injects into the kubeadm configuration used for init the information about the joining node + clusterConfiguration.NodeRegistration = j.cfg.NodeRegistration + clusterConfiguration.API.AdvertiseAddress = j.cfg.AdvertiseAddress + + // Checks if the cluster configuration supports + // joining a new control plane instance and if all the necessary certificates are provided + if err = j.CheckIfReadyForAdditionalControlPlane(clusterConfiguration); err != nil { + // outputs the not ready for hosting a new control plane instance message + ctx := map[string]string{ + "Error": err.Error(), + } + + var msg bytes.Buffer + notReadyToJoinControPlaneTemp.Execute(&msg, ctx) + return errors.New(msg.String()) + } + + // run kubeadm init preflight checks for checking all the prequisites + glog.Infoln("[join] running pre-flight checks before initializing the new control plane instance") + preflight.RunInitMasterChecks(utilsexec.New(), clusterConfiguration, j.ignorePreflightErrors) + + // Prepares the node for hosting a new control plane instance by writing necessary + // KubeConfig files, and static pod manifests + if err = j.PrepareForHostingControlPlane(clusterConfiguration); err != nil { + return err + } + } + + // Executes the kubelet TLS bootstrap process, that completes with the node + // joining the cluster with a dedicates set of credentials as required by + // the node authorizer. + // if the node is hosting a new control plane instance, since it uses static pods for the control plane, + // as soon as the kubelet starts it will take charge of creating control plane + // components on the node. + if err = j.BootstrapKubelet(tlsBootstrapCfg); err != nil { + return err + } + + // if the node is hosting a new control plane instance + if j.cfg.ControlPlane == true { + // Marks the node with master taint and label. + if err := j.MarkMaster(); err != nil { + return err + } + + // outputs the join control plane done template and exits + ctx := map[string]string{ + "KubeConfigPath": kubeadmconstants.GetAdminKubeConfigPath(), + } + joinControPlaneDoneTemp.Execute(out, ctx) + return nil + } + + // otherwise, if the node joined as a worker node; + // outputs the join done message and exits + fmt.Fprintf(out, joinWorkerNodeDoneMsg) + return nil +} + +// FetchInitClusterConfiguration reads the cluster configuration from the kubeadm-admin configMap, +func (j *Join) FetchInitClusterConfiguration(tlsBootstrapCfg *clientcmdapi.Config) (*kubeadmapi.InitConfiguration, error) { + // creates a client to access the cluster using the bootstrap token identity + tlsClient, err := kubeconfigutil.ToClientSet(tlsBootstrapCfg) + if err != nil { + return nil, errors.Wrap(err, "Unable to access the cluster") + } + + // Fetches the cluster configuration + kubeadmConfig, err := configutil.FetchConfigFromFileOrCluster(tlsClient, os.Stdout, "join", "") + if err != nil { + return nil, errors.Wrap(err, "Unable to fetch the kubeadm-config ConfigMap") + } + + // Converts public API struct to internal API + clusterConfiguration := &kubeadmapi.InitConfiguration{} + kubeadmscheme.Scheme.Convert(kubeadmConfig, clusterConfiguration, nil) + + return clusterConfiguration, nil +} + +// CheckIfReadyForAdditionalControlPlane ensures that the cluster is in a state that supports +// joining an additional control plane instance and if the node is ready to join +func (j *Join) CheckIfReadyForAdditionalControlPlane(clusterConfiguration *kubeadmapi.InitConfiguration) error { + // blocks if the cluster was created without a stable control plane endpoint + if clusterConfiguration.API.ControlPlaneEndpoint == "" { + return fmt.Errorf("unable to add a new control plane instance a cluster that doesn't have a stable api.controlPlaneEndpoint address") + } + + // blocks if the cluster was created without an external etcd cluster + if clusterConfiguration.Etcd.External == nil { + return fmt.Errorf("unable to add a new control plane instance on a cluster that doesn't use an external etcd") + } + + // blocks if control plane is self-hosted + if features.Enabled(clusterConfiguration.FeatureGates, features.SelfHosting) { + return fmt.Errorf("self-hosted clusters are deprecated and won't be supported by `kubeadm join --experimental-control-plane`") + } + + // blocks if the certificates for the control plane are stored in secrets (instead of the local pki folder) + if features.Enabled(clusterConfiguration.FeatureGates, features.StoreCertsInSecrets) { + return fmt.Errorf("certificates stored in secrets, as well as self-hosted clusters are deprecated and won't be supported by `kubeadm join --experimental-control-plane`") + } + + // checks if the certificates that must be equal across contolplane instances are provided + if ret, err := certsphase.SharedCertificateExists(clusterConfiguration); !ret { + return err + } + + return nil +} + +// PrepareForHostingControlPlane makes all preparation activities require for a node hosting a new control plane instance +func (j *Join) PrepareForHostingControlPlane(clusterConfiguration *kubeadmapi.InitConfiguration) error { + + // Creates the admin kubeconfig file for the admin and for kubeadm itself. + if err := kubeconfigphase.CreateAdminKubeConfigFile(kubeadmconstants.KubernetesDir, clusterConfiguration); err != nil { + return errors.Wrap(err, "error generating the admin kubeconfig file") + } + + // Generate missing certificates (if any) + if err := certsphase.CreatePKIAssets(clusterConfiguration); err != nil { + return err + } + + // Generate kubeconfig files for controller manager, scheduler and for the admin/kubeadm itself + // NB. The kubeconfig file for kubelet will be generated by the TLS bootstrap process in + // following steps of the join --experimental-control plane workflow + if err := kubeconfigphase.CreateJoinControlPlaneKubeConfigFiles(kubeadmconstants.KubernetesDir, clusterConfiguration); err != nil { + return errors.Wrap(err, "error generating kubeconfig files") + } + + // Creates static pod manifests file for the control plane components to be deployed on this node + // Static pods will be created and managed by the kubelet as soon as it starts + if err := controlplanephase.CreateInitStaticPodManifestFiles(kubeadmconstants.GetStaticPodDirectory(), clusterConfiguration); err != nil { + return errors.Wrap(err, "error creating static pod manifest files for the control plane components") + } + + return nil +} + +// BootstrapKubelet executes the kubelet TLS bootstrap process. +// This process is executed by the kubelet and completes with the node joining the cluster +// with a dedicates set of credentials as required by the node authorizer +func (j *Join) BootstrapKubelet(tlsBootstrapCfg *clientcmdapi.Config) error { bootstrapKubeConfigFile := kubeadmconstants.GetBootstrapKubeletKubeConfigPath() // Write the bootstrap kubelet config file or the TLS-Boostrapped kubelet config file down to disk glog.V(1).Infoln("[join] writing bootstrap kubelet config file at", bootstrapKubeConfigFile) - if err := kubeconfigutil.WriteToDisk(bootstrapKubeConfigFile, cfg); err != nil { - return fmt.Errorf("couldn't save bootstrap-kubelet.conf to disk: %v", err) + if err := kubeconfigutil.WriteToDisk(bootstrapKubeConfigFile, tlsBootstrapCfg); err != nil { + return errors.Wrap(err, "couldn't save bootstrap-kubelet.conf to disk") } // Write the ca certificate to disk so kubelet can use it for authentication - cluster := cfg.Contexts[cfg.CurrentContext].Cluster - if err := certutil.WriteCert(j.cfg.CACertPath, cfg.Clusters[cluster].CertificateAuthorityData); err != nil { - return fmt.Errorf("couldn't save the CA certificate to disk: %v", err) + cluster := tlsBootstrapCfg.Contexts[tlsBootstrapCfg.CurrentContext].Cluster + if _, err := os.Stat(j.cfg.CACertPath); os.IsNotExist(err) { + if err := certutil.WriteCert(j.cfg.CACertPath, tlsBootstrapCfg.Clusters[cluster].CertificateAuthorityData); err != nil { + return errors.Wrap(err, "couldn't save the CA certificate to disk") + } } kubeletVersion, err := preflight.GetKubeletVersion(utilsexec.New()) @@ -296,7 +498,7 @@ func (j *Join) Run(out io.Writer) error { // times out, display a somewhat user-friendly message. waiter := apiclient.NewKubeWaiter(nil, kubeadmconstants.TLSBootstrapTimeout, os.Stdout) if err := waitForKubeletAndFunc(waiter, waitForTLSBootstrappedClient); err != nil { - fmt.Printf(kubeadmJoinFailMsgf, err) + fmt.Printf(kubeadmJoinFailMsg, err) return err } @@ -308,17 +510,33 @@ func (j *Join) Run(out io.Writer) error { glog.V(1).Infof("[join] preserving the crisocket information for the node") if err := patchnodephase.AnnotateCRISocket(client, j.cfg.NodeRegistration.Name, j.cfg.NodeRegistration.CRISocket); err != nil { - return fmt.Errorf("error uploading crisocket: %v", err) + return errors.Wrap(err, "error uploading crisocket") } // This feature is disabled by default in kubeadm if features.Enabled(j.cfg.FeatureGates, features.DynamicKubeletConfig) { if err := kubeletphase.EnableDynamicConfigForNode(client, j.cfg.NodeRegistration.Name, kubeletVersion); err != nil { - return fmt.Errorf("error consuming base kubelet configuration: %v", err) + return errors.Wrap(err, "error consuming base kubelet configuration") } } - fmt.Fprintf(out, joinDoneMsgf) + return nil +} + +// MarkMaster marks the new node as master +func (j *Join) MarkMaster() error { + kubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName) + + client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) + if err != nil { + return errors.Wrap(err, "couldn't create kubernetes client") + } + + err = markmasterphase.MarkMaster(client, j.cfg.NodeRegistration.Name, j.cfg.NodeRegistration.Taints) + if err != nil { + return errors.Wrap(err, "error applying master label and taints") + } + return nil } diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index 8c5335ae86c..ccc3e4a9acb 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -73,12 +73,11 @@ func CreateInitKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration) ) } -// CreateJoinMasterKubeConfigFiles will create and write to disk the kubeconfig files required by kubeadm -// join --master workflow, plus the admin kubeconfig file to be deployed on the new master; the -// kubelet.conf file must not be created when joining master nodes because it will be created and signed by -// the kubelet TLS bootstrap process. +// CreateJoinControlPlaneKubeConfigFiles will create and write to disk the kubeconfig files required by kubeadm +// join --control-plane workflow, plus the admin kubeconfig file used by the administrator and kubeadm itself; the +// kubelet.conf file must not be created because it will be created and signed by the kubelet TLS bootstrap process. // If any kubeconfig files already exists, it used only if evaluated equal; otherwise an error is returned. -func CreateJoinMasterKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration) error { +func CreateJoinControlPlaneKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration) error { return createKubeConfigFiles( outDir, cfg, diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go index fe14d5ecb95..0c065688bf5 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go @@ -271,8 +271,8 @@ func TestCreateKubeconfigFilesAndWrappers(t *testing.T) { kubeadmconstants.SchedulerKubeConfigFileName, }, }, - { // Test CreateJoinMasterKubeConfigFiles (wrapper to createKubeConfigFile) - createKubeConfigFunction: CreateJoinMasterKubeConfigFiles, + { // Test CreateJoinControlPlaneKubeConfigFiles (wrapper to createKubeConfigFile) + createKubeConfigFunction: CreateJoinControlPlaneKubeConfigFiles, expectedFiles: []string{ kubeadmconstants.AdminKubeConfigFileName, kubeadmconstants.ControllerManagerKubeConfigFileName, diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 9dbafd4ae47..96f5c52a5c5 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -918,12 +918,14 @@ func RunJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.JoinConfigura checks := []Checker{ DirAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)}, - FileAvailableCheck{Path: cfg.CACertPath}, FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)}, FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName)}, ipvsutil.RequiredIPVSKernelModulesAvailableCheck{Executor: execer}, } checks = addCommonChecks(execer, cfg, checks) + if !cfg.ControlPlane { + checks = append(checks, FileAvailableCheck{Path: cfg.CACertPath}) + } addIPv6Checks := false for _, server := range cfg.DiscoveryTokenAPIServers { diff --git a/cmd/kubeadm/app/util/config/nodeconfig.go b/cmd/kubeadm/app/util/config/nodeconfig.go index 9bdca874c1f..a3d94503d29 100644 --- a/cmd/kubeadm/app/util/config/nodeconfig.go +++ b/cmd/kubeadm/app/util/config/nodeconfig.go @@ -23,6 +23,7 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/runtime" + netutil "k8s.io/apimachinery/pkg/util/net" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" @@ -37,6 +38,15 @@ func SetJoinDynamicDefaults(cfg *kubeadmapi.JoinConfiguration) error { return err } cfg.NodeRegistration.Name = nodeName + + if cfg.AdvertiseAddress == "" { + ip, err := netutil.ChooseBindAddress(nil) + if err != nil { + return err + } + cfg.AdvertiseAddress = ip.String() + } + return nil }