mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-06 11:42:14 +00:00
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. ```
This commit is contained in:
@@ -300,6 +300,14 @@ type JoinConfiguration struct {
|
|||||||
// the security of kubeadm since other nodes can impersonate the master.
|
// the security of kubeadm since other nodes can impersonate the master.
|
||||||
DiscoveryTokenUnsafeSkipCAVerification bool
|
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 enabled by the user.
|
||||||
FeatureGates map[string]bool
|
FeatureGates map[string]bool
|
||||||
}
|
}
|
||||||
|
@@ -277,6 +277,14 @@ type JoinConfiguration struct {
|
|||||||
// the security of kubeadm since other nodes can impersonate the master.
|
// the security of kubeadm since other nodes can impersonate the master.
|
||||||
DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"`
|
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 enabled by the user.
|
||||||
FeatureGates map[string]bool `json:"featureGates,omitempty"`
|
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.ClusterName = in.ClusterName
|
||||||
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
||||||
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
||||||
|
out.ControlPlane = in.ControlPlane
|
||||||
|
out.AdvertiseAddress = in.AdvertiseAddress
|
||||||
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -453,6 +455,8 @@ func autoConvert_kubeadm_JoinConfiguration_To_v1alpha2_JoinConfiguration(in *kub
|
|||||||
out.ClusterName = in.ClusterName
|
out.ClusterName = in.ClusterName
|
||||||
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
||||||
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
||||||
|
out.ControlPlane = in.ControlPlane
|
||||||
|
out.AdvertiseAddress = in.AdvertiseAddress
|
||||||
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -271,6 +271,14 @@ type JoinConfiguration struct {
|
|||||||
// the security of kubeadm since other nodes can impersonate the master.
|
// the security of kubeadm since other nodes can impersonate the master.
|
||||||
DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"`
|
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 enabled by the user.
|
||||||
FeatureGates map[string]bool `json:"featureGates,omitempty"`
|
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.ClusterName = in.ClusterName
|
||||||
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
||||||
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
||||||
|
out.ControlPlane = in.ControlPlane
|
||||||
|
out.AdvertiseAddress = in.AdvertiseAddress
|
||||||
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -451,6 +453,8 @@ func autoConvert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in *kub
|
|||||||
out.ClusterName = in.ClusterName
|
out.ClusterName = in.ClusterName
|
||||||
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes))
|
||||||
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification
|
||||||
|
out.ControlPlane = in.ControlPlane
|
||||||
|
out.AdvertiseAddress = in.AdvertiseAddress
|
||||||
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -61,6 +61,7 @@ func ValidateJoinConfiguration(c *kubeadm.JoinConfiguration) field.ErrorList {
|
|||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, ValidateDiscovery(c)...)
|
allErrs = append(allErrs, ValidateDiscovery(c)...)
|
||||||
allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...)
|
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") {
|
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"))
|
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/api:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/tools/bootstrap/token/util: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: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",
|
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
|
||||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||||
"//vendor/github.com/golang/glog: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/renstrom/dedent:go_default_library",
|
||||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||||
|
@@ -17,19 +17,23 @@ limitations under the License.
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/renstrom/dedent"
|
"github.com/renstrom/dedent"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
certutil "k8s.io/client-go/util/cert"
|
certutil "k8s.io/client-go/util/cert"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
@@ -38,7 +42,11 @@ import (
|
|||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
|
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
"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"
|
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"
|
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
@@ -49,15 +57,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
joinDoneMsgf = dedent.Dedent(`
|
joinWorkerNodeDoneMsg = dedent.Dedent(`
|
||||||
This node has joined the cluster:
|
This node has joined the cluster:
|
||||||
* Certificate signing request was sent to master and a response
|
* Certificate signing request was sent to apiserver and a response was received.
|
||||||
was received.
|
|
||||||
* The Kubelet was informed of the new secure connection details.
|
* The Kubelet was informed of the new secure connection details.
|
||||||
|
|
||||||
Run 'kubectl get nodes' on the master to see this node join the cluster.
|
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(`
|
joinLongDescription = dedent.Dedent(`
|
||||||
When joining a kubeadm initialized cluster, we need to establish
|
When joining a kubeadm initialized cluster, we need to establish
|
||||||
bidirectional trust. This is split into discovery (having the Node
|
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
|
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).
|
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
|
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.
|
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
|
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.
|
--token flag can be used instead of specifying each token individually.
|
||||||
`)
|
`)
|
||||||
|
|
||||||
kubeadmJoinFailMsgf = dedent.Dedent(`
|
kubeadmJoinFailMsg = dedent.Dedent(`
|
||||||
Unfortunately, an error has occurred:
|
Unfortunately, an error has occurred:
|
||||||
%v
|
%v
|
||||||
|
|
||||||
@@ -169,7 +207,7 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha3.JoinConfi
|
|||||||
"A file or url from which to load cluster information.")
|
"A file or url from which to load cluster information.")
|
||||||
flagSet.StringVar(
|
flagSet.StringVar(
|
||||||
&cfg.DiscoveryToken, "discovery-token", "",
|
&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(
|
flagSet.StringVar(
|
||||||
&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name,
|
&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name,
|
||||||
"Specify the node 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,
|
&cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket,
|
||||||
`Specify the CRI socket to connect to.`,
|
`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
|
// 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) {
|
func NewJoin(cfgPath string, args []string, defaultcfg *kubeadmapiv1alpha3.JoinConfiguration, ignorePreflightErrors sets.String) (*Join, error) {
|
||||||
|
|
||||||
if defaultcfg.NodeRegistration.Name == "" {
|
if defaultcfg.NodeRegistration.Name == "" {
|
||||||
glog.V(1).Infoln("[join] found NodeName empty")
|
glog.V(1).Infoln("[join] found NodeName empty; using OS hostname as NodeName")
|
||||||
glog.V(1).Infoln("[join] considered 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)
|
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.
|
// Run executes worker node provisioning and tries to join an existing cluster.
|
||||||
func (j *Join) Run(out io.Writer) error {
|
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
|
// 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.
|
// 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")
|
glog.V(1).Infoln("[join] discovering cluster-info")
|
||||||
cfg, err := discovery.For(j.cfg)
|
tlsBootstrapCfg, err := discovery.For(j.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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()
|
bootstrapKubeConfigFile := kubeadmconstants.GetBootstrapKubeletKubeConfigPath()
|
||||||
|
|
||||||
// Write the bootstrap kubelet config file or the TLS-Boostrapped kubelet config file down to disk
|
// 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)
|
glog.V(1).Infoln("[join] writing bootstrap kubelet config file at", bootstrapKubeConfigFile)
|
||||||
if err := kubeconfigutil.WriteToDisk(bootstrapKubeConfigFile, cfg); err != nil {
|
if err := kubeconfigutil.WriteToDisk(bootstrapKubeConfigFile, tlsBootstrapCfg); err != nil {
|
||||||
return fmt.Errorf("couldn't save bootstrap-kubelet.conf to disk: %v", err)
|
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
|
// Write the ca certificate to disk so kubelet can use it for authentication
|
||||||
cluster := cfg.Contexts[cfg.CurrentContext].Cluster
|
cluster := tlsBootstrapCfg.Contexts[tlsBootstrapCfg.CurrentContext].Cluster
|
||||||
if err := certutil.WriteCert(j.cfg.CACertPath, cfg.Clusters[cluster].CertificateAuthorityData); err != nil {
|
if _, err := os.Stat(j.cfg.CACertPath); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("couldn't save the CA certificate to disk: %v", 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())
|
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.
|
// times out, display a somewhat user-friendly message.
|
||||||
waiter := apiclient.NewKubeWaiter(nil, kubeadmconstants.TLSBootstrapTimeout, os.Stdout)
|
waiter := apiclient.NewKubeWaiter(nil, kubeadmconstants.TLSBootstrapTimeout, os.Stdout)
|
||||||
if err := waitForKubeletAndFunc(waiter, waitForTLSBootstrappedClient); err != nil {
|
if err := waitForKubeletAndFunc(waiter, waitForTLSBootstrappedClient); err != nil {
|
||||||
fmt.Printf(kubeadmJoinFailMsgf, err)
|
fmt.Printf(kubeadmJoinFailMsg, err)
|
||||||
return 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")
|
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 {
|
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
|
// This feature is disabled by default in kubeadm
|
||||||
if features.Enabled(j.cfg.FeatureGates, features.DynamicKubeletConfig) {
|
if features.Enabled(j.cfg.FeatureGates, features.DynamicKubeletConfig) {
|
||||||
if err := kubeletphase.EnableDynamicConfigForNode(client, j.cfg.NodeRegistration.Name, kubeletVersion); err != nil {
|
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
|
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
|
// CreateJoinControlPlaneKubeConfigFiles 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
|
// join --control-plane workflow, plus the admin kubeconfig file used by the administrator and kubeadm itself; the
|
||||||
// kubelet.conf file must not be created when joining master nodes because it will be created and signed by
|
// kubelet.conf file must not be created because it will be created and signed by the kubelet TLS bootstrap process.
|
||||||
// the kubelet TLS bootstrap process.
|
|
||||||
// If any kubeconfig files already exists, it used only if evaluated equal; otherwise an error is returned.
|
// 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(
|
return createKubeConfigFiles(
|
||||||
outDir,
|
outDir,
|
||||||
cfg,
|
cfg,
|
||||||
|
@@ -271,8 +271,8 @@ func TestCreateKubeconfigFilesAndWrappers(t *testing.T) {
|
|||||||
kubeadmconstants.SchedulerKubeConfigFileName,
|
kubeadmconstants.SchedulerKubeConfigFileName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ // Test CreateJoinMasterKubeConfigFiles (wrapper to createKubeConfigFile)
|
{ // Test CreateJoinControlPlaneKubeConfigFiles (wrapper to createKubeConfigFile)
|
||||||
createKubeConfigFunction: CreateJoinMasterKubeConfigFiles,
|
createKubeConfigFunction: CreateJoinControlPlaneKubeConfigFiles,
|
||||||
expectedFiles: []string{
|
expectedFiles: []string{
|
||||||
kubeadmconstants.AdminKubeConfigFileName,
|
kubeadmconstants.AdminKubeConfigFileName,
|
||||||
kubeadmconstants.ControllerManagerKubeConfigFileName,
|
kubeadmconstants.ControllerManagerKubeConfigFileName,
|
||||||
|
@@ -918,12 +918,14 @@ func RunJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.JoinConfigura
|
|||||||
|
|
||||||
checks := []Checker{
|
checks := []Checker{
|
||||||
DirAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)},
|
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.KubeletKubeConfigFileName)},
|
||||||
FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName)},
|
FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName)},
|
||||||
ipvsutil.RequiredIPVSKernelModulesAvailableCheck{Executor: execer},
|
ipvsutil.RequiredIPVSKernelModulesAvailableCheck{Executor: execer},
|
||||||
}
|
}
|
||||||
checks = addCommonChecks(execer, cfg, checks)
|
checks = addCommonChecks(execer, cfg, checks)
|
||||||
|
if !cfg.ControlPlane {
|
||||||
|
checks = append(checks, FileAvailableCheck{Path: cfg.CACertPath})
|
||||||
|
}
|
||||||
|
|
||||||
addIPv6Checks := false
|
addIPv6Checks := false
|
||||||
for _, server := range cfg.DiscoveryTokenAPIServers {
|
for _, server := range cfg.DiscoveryTokenAPIServers {
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
netutil "k8s.io/apimachinery/pkg/util/net"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3"
|
kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3"
|
||||||
@@ -37,6 +38,15 @@ func SetJoinDynamicDefaults(cfg *kubeadmapi.JoinConfiguration) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cfg.NodeRegistration.Name = nodeName
|
cfg.NodeRegistration.Name = nodeName
|
||||||
|
|
||||||
|
if cfg.AdvertiseAddress == "" {
|
||||||
|
ip, err := netutil.ChooseBindAddress(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.AdvertiseAddress = ip.String()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
|
AdvertiseAddress: 192.168.2.2
|
||||||
CACertPath: /etc/kubernetes/pki/ca.crt
|
CACertPath: /etc/kubernetes/pki/ca.crt
|
||||||
ClusterName: kubernetes
|
ClusterName: kubernetes
|
||||||
|
ControlPlane: false
|
||||||
DiscoveryFile: ""
|
DiscoveryFile: ""
|
||||||
DiscoveryTimeout: 5m0s
|
DiscoveryTimeout: 5m0s
|
||||||
DiscoveryToken: abcdef.0123456789abcdef
|
DiscoveryToken: abcdef.0123456789abcdef
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
advertiseAddress: 192.168.2.2
|
||||||
apiVersion: kubeadm.k8s.io/v1alpha2
|
apiVersion: kubeadm.k8s.io/v1alpha2
|
||||||
caCertPath: /etc/kubernetes/pki/ca.crt
|
caCertPath: /etc/kubernetes/pki/ca.crt
|
||||||
clusterName: kubernetes
|
clusterName: kubernetes
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
advertiseAddress: 192.168.2.2
|
||||||
apiVersion: kubeadm.k8s.io/v1alpha3
|
apiVersion: kubeadm.k8s.io/v1alpha3
|
||||||
caCertPath: /etc/kubernetes/pki/ca.crt
|
caCertPath: /etc/kubernetes/pki/ca.crt
|
||||||
clusterName: kubernetes
|
clusterName: kubernetes
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
advertiseAddress: 192.168.2.2
|
||||||
apiVersion: kubeadm.k8s.io/v1alpha3
|
apiVersion: kubeadm.k8s.io/v1alpha3
|
||||||
caCertPath: /etc/kubernetes/pki/ca.crt
|
caCertPath: /etc/kubernetes/pki/ca.crt
|
||||||
clusterName: kubernetes
|
clusterName: kubernetes
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
advertiseAddress: 192.168.2.2
|
||||||
apiVersion: kubeadm.k8s.io/v1alpha2
|
apiVersion: kubeadm.k8s.io/v1alpha2
|
||||||
kind: NodeConfiguration
|
kind: NodeConfiguration
|
||||||
discoveryTokenAPIServers:
|
discoveryTokenAPIServers:
|
||||||
|
Reference in New Issue
Block a user