mirror of https://github.com/k3s-io/k3s
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
commit
cf89c466cc
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
advertiseAddress: 192.168.2.2
|
||||
apiVersion: kubeadm.k8s.io/v1alpha2
|
||||
caCertPath: /etc/kubernetes/pki/ca.crt
|
||||
clusterName: kubernetes
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
advertiseAddress: 192.168.2.2
|
||||
apiVersion: kubeadm.k8s.io/v1alpha3
|
||||
caCertPath: /etc/kubernetes/pki/ca.crt
|
||||
clusterName: kubernetes
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
advertiseAddress: 192.168.2.2
|
||||
apiVersion: kubeadm.k8s.io/v1alpha3
|
||||
caCertPath: /etc/kubernetes/pki/ca.crt
|
||||
clusterName: kubernetes
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
advertiseAddress: 192.168.2.2
|
||||
apiVersion: kubeadm.k8s.io/v1alpha2
|
||||
kind: NodeConfiguration
|
||||
discoveryTokenAPIServers:
|
||||
|
|
Loading…
Reference in New Issue