Merge pull request #66873 from fabriziopandini/kubeadm-ha-join-master

Automatic merge from submit-queue (batch tested with PRs 67017, 67190, 67110, 67140, 66873). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Kubeadm join --control-plane main workflow

**What this PR does / why we need it**:
This PR implements one of the actions defined by https://github.com/kubernetes/kubeadm/issues/751 (checklist form implementing HA in kubeadm).

With this PR, kubeadm implements the `kubeadm join --control-plane`workflow, as described in the [KEP 0015-kubeadm-join-master.md](https://github.com/kubernetes/community/blob/master/keps/sig-cluster-lifecycle/0015-kubeadm-join-master.md) with the exception of the update of the `kubeadm-config` ConfigMap that will be completed in a following PR as soon as the implementation in the config file will allow it. 

**Special notes for your reviewer**:
/CC @timothysc @luxas @chuckha  @kubernetes/sig-cluster-lifecycle-pr-reviews 

**Release note**:
```
`kubeadm join` now has the --experimental-control-plane flag that triggers deploy of a new control plane instance on the joining node. 
```
pull/8/head
Kubernetes Submit Queue 2018-08-10 17:01:20 -07:00 committed by GitHub
commit cf89c466cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 298 additions and 28 deletions

View File

@ -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
}

View File

@ -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"`
}

View File

@ -430,6 +430,8 @@ func autoConvert_v1alpha2_JoinConfiguration_To_kubeadm_JoinConfiguration(in *Joi
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.ControlPlane = in.ControlPlane
out.AdvertiseAddress = in.AdvertiseAddress
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
@ -453,6 +455,8 @@ func autoConvert_kubeadm_JoinConfiguration_To_v1alpha2_JoinConfiguration(in *kub
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.ControlPlane = in.ControlPlane
out.AdvertiseAddress = in.AdvertiseAddress
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}

View File

@ -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"`
}

View File

@ -428,6 +428,8 @@ func autoConvert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(in *Joi
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.ControlPlane = in.ControlPlane
out.AdvertiseAddress = in.AdvertiseAddress
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}
@ -451,6 +453,8 @@ func autoConvert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in *kub
out.ClusterName = in.ClusterName
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
out.ControlPlane = in.ControlPlane
out.AdvertiseAddress = in.AdvertiseAddress
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil
}

View File

@ -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"))

View File

@ -67,9 +67,11 @@ go_library(
"//staging/src/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
"//staging/src/k8s.io/client-go/tools/bootstrap/token/util:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/renstrom/dedent:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",

View File

@ -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
}

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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
}

View File

@ -1,5 +1,7 @@
AdvertiseAddress: 192.168.2.2
CACertPath: /etc/kubernetes/pki/ca.crt
ClusterName: kubernetes
ControlPlane: false
DiscoveryFile: ""
DiscoveryTimeout: 5m0s
DiscoveryToken: abcdef.0123456789abcdef

View File

@ -1,3 +1,4 @@
advertiseAddress: 192.168.2.2
apiVersion: kubeadm.k8s.io/v1alpha2
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes

View File

@ -1,3 +1,4 @@
advertiseAddress: 192.168.2.2
apiVersion: kubeadm.k8s.io/v1alpha3
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes

View File

@ -1,3 +1,4 @@
advertiseAddress: 192.168.2.2
apiVersion: kubeadm.k8s.io/v1alpha3
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes

View File

@ -1,3 +1,4 @@
advertiseAddress: 192.168.2.2
apiVersion: kubeadm.k8s.io/v1alpha2
kind: NodeConfiguration
discoveryTokenAPIServers: