mirror of https://github.com/k3s-io/k3s
Refactor the kubeadm join command
parent
bf56c7be42
commit
fd842480d1
|
@ -88,8 +88,6 @@ go_test(
|
|||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
|
||||
"//cmd/kubeadm/app/cmd/options:go_default_library",
|
||||
|
|
|
@ -83,7 +83,7 @@ func NewKubeadmCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
|||
cmds.AddCommand(NewCmdCompletion(out, ""))
|
||||
cmds.AddCommand(NewCmdConfig(out))
|
||||
cmds.AddCommand(NewCmdInit(out, nil))
|
||||
cmds.AddCommand(NewCmdJoin(out))
|
||||
cmds.AddCommand(NewCmdJoin(out, nil))
|
||||
cmds.AddCommand(NewCmdReset(in, out))
|
||||
cmds.AddCommand(NewCmdVersion(out))
|
||||
cmds.AddCommand(NewCmdToken(out, err))
|
||||
|
|
|
@ -37,6 +37,7 @@ import (
|
|||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
|
||||
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
|
@ -152,206 +153,258 @@ var (
|
|||
`)
|
||||
)
|
||||
|
||||
// joinOptions defines all the options exposed via flags by kubeadm join.
|
||||
// Please note that this structure includes the public kubeadm config API, but only a subset of the options
|
||||
// supported by this api will be exposed as a flag.
|
||||
type joinOptions struct {
|
||||
cfgPath string
|
||||
token string
|
||||
controlPlane bool
|
||||
ignorePreflightErrors []string
|
||||
externalcfg *kubeadmapiv1beta1.JoinConfiguration
|
||||
}
|
||||
|
||||
// joinData defines all the runtime information used when running the kubeadm join worklow;
|
||||
// this data is shared across all the phases that are included in the workflow.
|
||||
type joinData struct {
|
||||
cfg *kubeadmapi.JoinConfiguration
|
||||
ignorePreflightErrors sets.String
|
||||
outputWriter io.Writer
|
||||
}
|
||||
|
||||
// NewCmdJoin returns "kubeadm join" command.
|
||||
func NewCmdJoin(out io.Writer) *cobra.Command {
|
||||
cfg := &kubeadmapiv1beta1.JoinConfiguration{}
|
||||
kubeadmscheme.Scheme.Default(cfg)
|
||||
|
||||
fd := &kubeadmapiv1beta1.FileDiscovery{}
|
||||
btd := &kubeadmapiv1beta1.BootstrapTokenDiscovery{}
|
||||
|
||||
var token string
|
||||
var cfgPath string
|
||||
var ignorePreflightErrors []string
|
||||
var controlPlane bool
|
||||
var advertiseAddress string
|
||||
var bindPort int32 = kubeadmapiv1beta1.DefaultAPIBindPort
|
||||
// NB. joinOptions is exposed as parameter for allowing unit testing of
|
||||
// the newJoinData method, that implements all the command options validation logic
|
||||
func NewCmdJoin(out io.Writer, joinOptions *joinOptions) *cobra.Command {
|
||||
if joinOptions == nil {
|
||||
joinOptions = newJoinOptions()
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "join",
|
||||
Short: "Run this on any machine you wish to join an existing cluster",
|
||||
Long: joinLongDescription,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
if len(fd.KubeConfigPath) != 0 {
|
||||
cfg.Discovery.File = fd
|
||||
} else {
|
||||
cfg.Discovery.BootstrapToken = btd
|
||||
if len(cfg.Discovery.BootstrapToken.Token) == 0 {
|
||||
cfg.Discovery.BootstrapToken.Token = token
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if len(cfgPath) == 0 && len(args) > 1 {
|
||||
klog.Warningf("[join] WARNING: More than one API server endpoint supplied on command line %v. Using the first one.", args)
|
||||
}
|
||||
cfg.Discovery.BootstrapToken.APIServerEndpoint = args[0]
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.Discovery.TLSBootstrapToken) == 0 {
|
||||
cfg.Discovery.TLSBootstrapToken = token
|
||||
}
|
||||
|
||||
if controlPlane {
|
||||
cfg.ControlPlane = &kubeadmapiv1beta1.JoinControlPlane{
|
||||
LocalAPIEndpoint: kubeadmapiv1beta1.APIEndpoint{
|
||||
AdvertiseAddress: advertiseAddress,
|
||||
BindPort: bindPort,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
j, err := NewValidJoin(cmd.PersistentFlags(), cfg, cfgPath, ignorePreflightErrors)
|
||||
joinData, err := newJoinData(cmd, args, joinOptions, out)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
err = joinData.Run()
|
||||
kubeadmutil.CheckErr(err)
|
||||
kubeadmutil.CheckErr(j.Run(out))
|
||||
},
|
||||
}
|
||||
|
||||
AddJoinConfigFlags(cmd.PersistentFlags(), cfg, &token)
|
||||
AddJoinBootstrapTokenDiscoveryFlags(cmd.PersistentFlags(), btd)
|
||||
AddJoinFileDiscoveryFlags(cmd.PersistentFlags(), fd)
|
||||
AddJoinOtherFlags(cmd.PersistentFlags(), &cfgPath, &ignorePreflightErrors, &controlPlane, &advertiseAddress, &bindPort)
|
||||
AddJoinConfigFlags(cmd.Flags(), joinOptions.externalcfg)
|
||||
AddJoinOtherFlags(cmd.Flags(), &joinOptions.cfgPath, &joinOptions.ignorePreflightErrors, &joinOptions.controlPlane, &joinOptions.token)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewValidJoin validates the command line that are passed to the cobra command
|
||||
func NewValidJoin(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguration, cfgPath string, ignorePreflightErrors []string) (*Join, error) {
|
||||
var err error
|
||||
|
||||
if err = validation.ValidateMixedArguments(flagSet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewJoin(cfgPath, cfg, ignorePreflightErrorsSet)
|
||||
}
|
||||
|
||||
// AddJoinConfigFlags adds join flags bound to the config to the specified flagset
|
||||
func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguration, token *string) {
|
||||
func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguration) {
|
||||
flagSet.StringVar(
|
||||
&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name,
|
||||
"Specify the node name.")
|
||||
&cfg.NodeRegistration.Name, options.NodeName, cfg.NodeRegistration.Name,
|
||||
`Specify the node name.`,
|
||||
)
|
||||
flagSet.StringVar(
|
||||
token, "token", "",
|
||||
"Use this token for both discovery-token and tls-bootstrap-token when those values are not provided.")
|
||||
flagSet.StringVar(
|
||||
&cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket,
|
||||
&cfg.NodeRegistration.CRISocket, options.NodeCRISocket, cfg.NodeRegistration.CRISocket,
|
||||
`Specify the CRI socket to connect to.`,
|
||||
)
|
||||
flagSet.StringVar(
|
||||
&cfg.Discovery.TLSBootstrapToken, options.TLSBootstrapToken, cfg.Discovery.TLSBootstrapToken,
|
||||
`Specify the token used to temporarily authenticate with the Kubernetes Master while joining the node.`,
|
||||
)
|
||||
AddControlPlaneFlags(flagSet, cfg.ControlPlane)
|
||||
AddJoinBootstrapTokenDiscoveryFlags(flagSet, cfg.Discovery.BootstrapToken)
|
||||
AddJoinFileDiscoveryFlags(flagSet, cfg.Discovery.File)
|
||||
}
|
||||
|
||||
// AddJoinBootstrapTokenDiscoveryFlags adds bootstrap token specific discovery flags to the specified flagset
|
||||
func AddJoinBootstrapTokenDiscoveryFlags(flagSet *flag.FlagSet, btd *kubeadmapiv1beta1.BootstrapTokenDiscovery) {
|
||||
flagSet.StringVar(
|
||||
&btd.Token, "discovery-token", "",
|
||||
"A token used to validate cluster information fetched from the API server.")
|
||||
&btd.Token, options.TokenDiscovery, "",
|
||||
"For token-based discovery, the token used to validate cluster information fetched from the API server.",
|
||||
)
|
||||
flagSet.StringSliceVar(
|
||||
&btd.CACertHashes, "discovery-token-ca-cert-hash", []string{},
|
||||
"For token-based discovery, validate that the root CA public key matches this hash (format: \"<type>:<value>\").")
|
||||
&btd.CACertHashes, options.TokenDiscoveryCAHash, []string{},
|
||||
"For token-based discovery, validate that the root CA public key matches this hash (format: \"<type>:<value>\").",
|
||||
)
|
||||
flagSet.BoolVar(
|
||||
&btd.UnsafeSkipCAVerification, "discovery-token-unsafe-skip-ca-verification", false,
|
||||
"For token-based discovery, allow joining without --discovery-token-ca-cert-hash pinning.")
|
||||
&btd.UnsafeSkipCAVerification, options.TokenDiscoverySkipCAHash, false,
|
||||
"For token-based discovery, allow joining without --discovery-token-ca-cert-hash pinning.",
|
||||
)
|
||||
}
|
||||
|
||||
// AddJoinFileDiscoveryFlags adds file discovery flags to the specified flagset
|
||||
func AddJoinFileDiscoveryFlags(flagSet *flag.FlagSet, fd *kubeadmapiv1beta1.FileDiscovery) {
|
||||
flagSet.StringVar(
|
||||
&fd.KubeConfigPath, "discovery-file", "",
|
||||
"A file or URL from which to load cluster information.")
|
||||
&fd.KubeConfigPath, options.FileDiscovery, "",
|
||||
"For file-based discovery, a file or URL from which to load cluster information.",
|
||||
)
|
||||
}
|
||||
|
||||
// AddControlPlaneFlags adds file control plane flags to the specified flagset
|
||||
func AddControlPlaneFlags(flagSet *flag.FlagSet, cp *kubeadmapiv1beta1.JoinControlPlane) {
|
||||
flagSet.StringVar(
|
||||
&cp.LocalAPIEndpoint.AdvertiseAddress, options.APIServerAdvertiseAddress, cp.LocalAPIEndpoint.AdvertiseAddress,
|
||||
"If the node should host a new control plane instance, the IP address the API Server will advertise it's listening on. If not set the default network interface will be used.",
|
||||
)
|
||||
flagSet.Int32Var(
|
||||
&cp.LocalAPIEndpoint.BindPort, options.APIServerBindPort, cp.LocalAPIEndpoint.BindPort,
|
||||
"If the node should host a new control plane instance, the port for the API Server to bind to.",
|
||||
)
|
||||
}
|
||||
|
||||
// AddJoinOtherFlags adds join flags that are not bound to a configuration file to the given flagset
|
||||
func AddJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, ignorePreflightErrors *[]string, controlPlane *bool, advertiseAddress *string, bindPort *int32) {
|
||||
func AddJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, ignorePreflightErrors *[]string, controlPlane *bool, token *string) {
|
||||
flagSet.StringVar(
|
||||
cfgPath, "config", *cfgPath,
|
||||
"Path to kubeadm config file.")
|
||||
cfgPath, options.CfgPath, *cfgPath,
|
||||
"Path to kubeadm config file.",
|
||||
)
|
||||
flagSet.StringSliceVar(
|
||||
ignorePreflightErrors, "ignore-preflight-errors", *ignorePreflightErrors,
|
||||
"A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.")
|
||||
flagSet.BoolVar(
|
||||
controlPlane, "experimental-control-plane", *controlPlane,
|
||||
"Create a new control plane instance on this node")
|
||||
ignorePreflightErrors, options.IgnorePreflightErrors, *ignorePreflightErrors,
|
||||
"A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.",
|
||||
)
|
||||
flagSet.StringVar(
|
||||
advertiseAddress, "apiserver-advertise-address", *advertiseAddress,
|
||||
"If the node should host a new control plane instance, the IP address the API Server will advertise it's listening on.")
|
||||
flagSet.Int32Var(
|
||||
bindPort, "apiserver-bind-port", *bindPort,
|
||||
"If the node should host a new control plane instance, the port for the API Server to bind to.")
|
||||
token, options.TokenStr, "",
|
||||
"Use this token for both discovery-token and tls-bootstrap-token when those values are not provided.",
|
||||
)
|
||||
flagSet.BoolVar(
|
||||
controlPlane, options.ControlPlane, *controlPlane,
|
||||
"Create a new control plane instance on this node",
|
||||
)
|
||||
}
|
||||
|
||||
// Join defines struct used by kubeadm join command
|
||||
type Join struct {
|
||||
cfg *kubeadmapi.JoinConfiguration
|
||||
initCfg *kubeadmapi.InitConfiguration
|
||||
tlsBootstrapCfg *clientcmdapi.Config
|
||||
ignorePreflightErrors sets.String
|
||||
// newJoinOptions returns a struct ready for being used for creating cmd join flags.
|
||||
func newJoinOptions() *joinOptions {
|
||||
// initialize the public kubeadm config API by appling defaults
|
||||
externalcfg := &kubeadmapiv1beta1.JoinConfiguration{}
|
||||
|
||||
// Add optional config objects to host flags.
|
||||
// un-set objects will be cleaned up afterwards (into newJoinData func)
|
||||
externalcfg.Discovery.File = &kubeadmapiv1beta1.FileDiscovery{}
|
||||
externalcfg.Discovery.BootstrapToken = &kubeadmapiv1beta1.BootstrapTokenDiscovery{}
|
||||
externalcfg.ControlPlane = &kubeadmapiv1beta1.JoinControlPlane{}
|
||||
|
||||
// Apply defaults
|
||||
kubeadmscheme.Scheme.Default(externalcfg)
|
||||
|
||||
return &joinOptions{
|
||||
externalcfg: externalcfg,
|
||||
}
|
||||
}
|
||||
|
||||
// NewJoin instantiates Join struct with given arguments
|
||||
func NewJoin(cfgPath string, defaultcfg *kubeadmapiv1beta1.JoinConfiguration, ignorePreflightErrors sets.String) (*Join, error) {
|
||||
// newJoinData returns a new joinData struct to be used for the execution of the kubeadm join workflow.
|
||||
// This func takes care of validating joinOptions passed to the command, and then it converts
|
||||
// options into the internal JoinConfiguration type that is used as input all the phases in the kubeadm join workflow
|
||||
func newJoinData(cmd *cobra.Command, args []string, options *joinOptions, out io.Writer) (joinData, error) {
|
||||
// Re-apply defaults to the public kubeadm API (this will set only values not exposed/not set as a flags)
|
||||
kubeadmscheme.Scheme.Default(options.externalcfg)
|
||||
|
||||
if defaultcfg.NodeRegistration.Name == "" {
|
||||
klog.V(1).Infoln("[join] found NodeName empty; using OS hostname as NodeName")
|
||||
}
|
||||
// Validate standalone flags values and/or combination of flags and then assigns
|
||||
// validated values to the public kubeadm config API when applicable
|
||||
|
||||
if defaultcfg.ControlPlane != nil && defaultcfg.ControlPlane.LocalAPIEndpoint.AdvertiseAddress == "" {
|
||||
klog.V(1).Infoln("[join] found advertiseAddress empty; using default interface's IP address as advertiseAddress")
|
||||
}
|
||||
|
||||
internalCfg, err := configutil.JoinConfigFileAndDefaultsToInternalConfig(cfgPath, defaultcfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// override node name and CRI socket from the command line options
|
||||
if defaultcfg.NodeRegistration.Name != "" {
|
||||
internalCfg.NodeRegistration.Name = defaultcfg.NodeRegistration.Name
|
||||
}
|
||||
if defaultcfg.NodeRegistration.CRISocket != kubeadmapiv1beta1.DefaultCRISocket {
|
||||
internalCfg.NodeRegistration.CRISocket = defaultcfg.NodeRegistration.CRISocket
|
||||
}
|
||||
|
||||
if internalCfg.ControlPlane != nil {
|
||||
if err := configutil.VerifyAPIServerBindAddress(internalCfg.ControlPlane.LocalAPIEndpoint.AdvertiseAddress); err != nil {
|
||||
return nil, err
|
||||
// if a token is provided, use this value for both discovery-token and tls-bootstrap-token when those values are not provided
|
||||
if len(options.token) > 0 {
|
||||
if len(options.externalcfg.Discovery.TLSBootstrapToken) == 0 {
|
||||
options.externalcfg.Discovery.TLSBootstrapToken = options.token
|
||||
}
|
||||
if len(options.externalcfg.Discovery.BootstrapToken.Token) == 0 {
|
||||
options.externalcfg.Discovery.BootstrapToken.Token = options.token
|
||||
}
|
||||
}
|
||||
|
||||
// if a file or URL from which to load cluster information was not provided, unset the Discovery.File object
|
||||
if len(options.externalcfg.Discovery.File.KubeConfigPath) == 0 {
|
||||
options.externalcfg.Discovery.File = nil
|
||||
}
|
||||
|
||||
// if an APIServerEndpoint from which to retrive cluster information was not provided, unset the Discovery.BootstrapToken object
|
||||
if len(args) == 0 {
|
||||
options.externalcfg.Discovery.BootstrapToken = nil
|
||||
} else {
|
||||
if len(options.cfgPath) == 0 && len(args) > 1 {
|
||||
klog.Warningf("[join] WARNING: More than one API server endpoint supplied on command line %v. Using the first one.", args)
|
||||
}
|
||||
options.externalcfg.Discovery.BootstrapToken.APIServerEndpoint = args[0]
|
||||
}
|
||||
|
||||
// if not joining a control plane, unset the ControlPlane object
|
||||
if !options.controlPlane {
|
||||
options.externalcfg.ControlPlane = nil
|
||||
}
|
||||
|
||||
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(options.ignorePreflightErrors)
|
||||
if err != nil {
|
||||
return joinData{}, err
|
||||
}
|
||||
|
||||
if err = validation.ValidateMixedArguments(cmd.Flags()); err != nil {
|
||||
return joinData{}, err
|
||||
}
|
||||
|
||||
// Either use the config file if specified, or convert public kubeadm API to the internal JoinConfiguration
|
||||
// and validates JoinConfiguration
|
||||
if options.externalcfg.NodeRegistration.Name == "" {
|
||||
klog.V(1).Infoln("[join] found NodeName empty; using OS hostname as NodeName")
|
||||
}
|
||||
|
||||
if options.externalcfg.ControlPlane != nil && options.externalcfg.ControlPlane.LocalAPIEndpoint.AdvertiseAddress == "" {
|
||||
klog.V(1).Infoln("[join] found advertiseAddress empty; using default interface's IP address as advertiseAddress")
|
||||
}
|
||||
|
||||
cfg, err := configutil.JoinConfigFileAndDefaultsToInternalConfig(options.cfgPath, options.externalcfg)
|
||||
if err != nil {
|
||||
return joinData{}, err
|
||||
}
|
||||
|
||||
// override node name and CRI socket from the command line options
|
||||
if options.externalcfg.NodeRegistration.Name != "" {
|
||||
cfg.NodeRegistration.Name = options.externalcfg.NodeRegistration.Name
|
||||
}
|
||||
if options.externalcfg.NodeRegistration.CRISocket != kubeadmapiv1beta1.DefaultCRISocket {
|
||||
cfg.NodeRegistration.CRISocket = options.externalcfg.NodeRegistration.CRISocket
|
||||
}
|
||||
|
||||
if cfg.ControlPlane != nil {
|
||||
if err := configutil.VerifyAPIServerBindAddress(cfg.ControlPlane.LocalAPIEndpoint.AdvertiseAddress); err != nil {
|
||||
return joinData{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return joinData{
|
||||
cfg: cfg,
|
||||
ignorePreflightErrors: ignorePreflightErrorsSet,
|
||||
outputWriter: out,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run executes worker node provisioning and tries to join an existing cluster.
|
||||
func (j *joinData) Run() error {
|
||||
fmt.Println("[preflight] Running pre-flight checks")
|
||||
|
||||
// Start with general checks
|
||||
klog.V(1).Infoln("[preflight] Running general checks")
|
||||
if err := preflight.RunJoinNodeChecks(utilsexec.New(), internalCfg, ignorePreflightErrors); err != nil {
|
||||
return nil, err
|
||||
if err := preflight.RunJoinNodeChecks(utilsexec.New(), j.cfg, j.ignorePreflightErrors); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch the init configuration based on the join configuration
|
||||
klog.V(1).Infoln("[preflight] Fetching init configuration")
|
||||
initCfg, tlsBootstrapCfg, err := fetchInitConfigurationFromJoinConfiguration(internalCfg)
|
||||
initCfg, tlsBootstrapCfg, err := fetchInitConfigurationFromJoinConfiguration(j.cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Continue with more specific checks based on the init configuration
|
||||
klog.V(1).Infoln("[preflight] Running configuration dependant checks")
|
||||
if err := preflight.RunOptionalJoinNodeChecks(utilsexec.New(), initCfg, ignorePreflightErrors); err != nil {
|
||||
return nil, err
|
||||
if err := preflight.RunOptionalJoinNodeChecks(utilsexec.New(), initCfg, j.ignorePreflightErrors); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return &Join{cfg: internalCfg, initCfg: initCfg, tlsBootstrapCfg: tlsBootstrapCfg, ignorePreflightErrors: ignorePreflightErrors}, nil
|
||||
}
|
||||
|
||||
// Run executes worker node provisioning and tries to join an existing cluster.
|
||||
func (j *Join) Run(out io.Writer) error {
|
||||
if j.cfg.ControlPlane != nil {
|
||||
// Checks if the cluster configuration supports
|
||||
// joining a new control plane instance and if all the necessary certificates are provided
|
||||
if err := j.CheckIfReadyForAdditionalControlPlane(j.initCfg); err != nil {
|
||||
if err := j.CheckIfReadyForAdditionalControlPlane(initCfg); err != nil {
|
||||
// outputs the not ready for hosting a new control plane instance message
|
||||
ctx := map[string]string{
|
||||
"Error": err.Error(),
|
||||
|
@ -364,11 +417,11 @@ func (j *Join) Run(out io.Writer) error {
|
|||
|
||||
// run kubeadm init preflight checks for checking all the prequisites
|
||||
fmt.Printf("[join] Running pre-flight checks before initializing the new control plane instance\n")
|
||||
preflight.RunInitMasterChecks(utilsexec.New(), j.initCfg, j.ignorePreflightErrors)
|
||||
preflight.RunInitMasterChecks(utilsexec.New(), initCfg, 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(j.initCfg); err != nil {
|
||||
if err := j.PrepareForHostingControlPlane(initCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -379,21 +432,21 @@ func (j *Join) Run(out io.Writer) error {
|
|||
// 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(); err != nil {
|
||||
if err := j.BootstrapKubelet(tlsBootstrapCfg, initCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the node is hosting a new control plane instance
|
||||
if j.cfg.ControlPlane != nil {
|
||||
// Completes the control plane setup
|
||||
if err := j.PostInstallControlPlane(j.initCfg); err != nil {
|
||||
if err := j.PostInstallControlPlane(initCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// outputs the join control plane done template and exits
|
||||
etcdMessage := ""
|
||||
// in case of local etcd
|
||||
if j.initCfg.Etcd.External == nil {
|
||||
if initCfg.Etcd.External == nil {
|
||||
etcdMessage = "* A new etcd member was added to the local/stacked etcd cluster."
|
||||
}
|
||||
|
||||
|
@ -401,19 +454,19 @@ func (j *Join) Run(out io.Writer) error {
|
|||
"KubeConfigPath": kubeadmconstants.GetAdminKubeConfigPath(),
|
||||
"etcdMessage": etcdMessage,
|
||||
}
|
||||
joinControPlaneDoneTemp.Execute(out, ctx)
|
||||
joinControPlaneDoneTemp.Execute(j.outputWriter, ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// otherwise, if the node joined as a worker node;
|
||||
// outputs the join done message and exits
|
||||
fmt.Fprintf(out, joinWorkerNodeDoneMsg)
|
||||
fmt.Fprintf(j.outputWriter, joinWorkerNodeDoneMsg)
|
||||
return 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(initConfiguration *kubeadmapi.InitConfiguration) error {
|
||||
func (j *joinData) CheckIfReadyForAdditionalControlPlane(initConfiguration *kubeadmapi.InitConfiguration) error {
|
||||
// blocks if the cluster was created without a stable control plane endpoint
|
||||
if initConfiguration.ControlPlaneEndpoint == "" {
|
||||
return errors.New("unable to add a new control plane instance a cluster that doesn't have a stable controlPlaneEndpoint address")
|
||||
|
@ -428,7 +481,7 @@ func (j *Join) CheckIfReadyForAdditionalControlPlane(initConfiguration *kubeadma
|
|||
}
|
||||
|
||||
// PrepareForHostingControlPlane makes all preparation activities require for a node hosting a new control plane instance
|
||||
func (j *Join) PrepareForHostingControlPlane(initConfiguration *kubeadmapi.InitConfiguration) error {
|
||||
func (j *joinData) PrepareForHostingControlPlane(initConfiguration *kubeadmapi.InitConfiguration) error {
|
||||
|
||||
// Generate missing certificates (if any)
|
||||
if err := certsphase.CreatePKIAssets(initConfiguration); err != nil {
|
||||
|
@ -471,19 +524,19 @@ func (j *Join) PrepareForHostingControlPlane(initConfiguration *kubeadmapi.InitC
|
|||
// 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() error {
|
||||
func (j *joinData) BootstrapKubelet(tlsBootstrapCfg *clientcmdapi.Config, initConfiguration *kubeadmapi.InitConfiguration) error {
|
||||
bootstrapKubeConfigFile := kubeadmconstants.GetBootstrapKubeletKubeConfigPath()
|
||||
|
||||
// Write the bootstrap kubelet config file or the TLS-Boostrapped kubelet config file down to disk
|
||||
klog.V(1).Infoln("[join] writing bootstrap kubelet config file at", bootstrapKubeConfigFile)
|
||||
if err := kubeconfigutil.WriteToDisk(bootstrapKubeConfigFile, j.tlsBootstrapCfg); err != nil {
|
||||
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 := j.tlsBootstrapCfg.Contexts[j.tlsBootstrapCfg.CurrentContext].Cluster
|
||||
cluster := tlsBootstrapCfg.Contexts[tlsBootstrapCfg.CurrentContext].Cluster
|
||||
if _, err := os.Stat(j.cfg.CACertPath); os.IsNotExist(err) {
|
||||
if err := certutil.WriteCert(j.cfg.CACertPath, j.tlsBootstrapCfg.Clusters[cluster].CertificateAuthorityData); err != nil {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -512,7 +565,7 @@ func (j *Join) BootstrapKubelet() error {
|
|||
// register the joining node with the specified taints if the node
|
||||
// is not a master. The markmaster phase will register the taints otherwise.
|
||||
registerTaintsUsingFlags := j.cfg.ControlPlane == nil
|
||||
if err := kubeletphase.WriteKubeletDynamicEnvFile(j.initCfg, registerTaintsUsingFlags, kubeadmconstants.KubeletRunDirectory); err != nil {
|
||||
if err := kubeletphase.WriteKubeletDynamicEnvFile(initConfiguration, registerTaintsUsingFlags, kubeadmconstants.KubeletRunDirectory); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -544,7 +597,7 @@ func (j *Join) BootstrapKubelet() error {
|
|||
}
|
||||
|
||||
// PostInstallControlPlane marks the new node as master and update the cluster status with information about current node
|
||||
func (j *Join) PostInstallControlPlane(initConfiguration *kubeadmapi.InitConfiguration) error {
|
||||
func (j *joinData) PostInstallControlPlane(initConfiguration *kubeadmapi.InitConfiguration) error {
|
||||
kubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName)
|
||||
|
||||
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile)
|
||||
|
|
|
@ -17,42 +17,29 @@ limitations under the License.
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||
)
|
||||
|
||||
const (
|
||||
testConfig = `apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data:
|
||||
server: localhost:9008
|
||||
name: prod
|
||||
contexts:
|
||||
- context:
|
||||
cluster: prod
|
||||
namespace: default
|
||||
user: default-service-account
|
||||
name: default
|
||||
current-context: default
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kubernetes-admin
|
||||
user:
|
||||
client-certificate-data:
|
||||
client-key-data:
|
||||
testJoinConfig = `apiVersion: kubeadm.k8s.io/v1beta1
|
||||
kind: JoinConfiguration
|
||||
discovery:
|
||||
bootstrapToken:
|
||||
token: abcdef.0123456789abcdef
|
||||
apiServerEndpoint: 1.2.3.4:6443
|
||||
unsafeSkipCAVerification: true
|
||||
nodeRegistration:
|
||||
criSocket: /run/containerd/containerd.sock
|
||||
name: someName
|
||||
`
|
||||
)
|
||||
|
||||
func TestNewValidJoin(t *testing.T) {
|
||||
func TestNewJoinData(t *testing.T) {
|
||||
// create temp directory
|
||||
tmpDir, err := ioutil.TempDir("", "kubeadm-join-test")
|
||||
if err != nil {
|
||||
|
@ -67,110 +54,192 @@ func TestNewValidJoin(t *testing.T) {
|
|||
t.Errorf("Unable to create file %q: %v", configFilePath, err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
if _, err = cfgFile.WriteString(testJoinConfig); err != nil {
|
||||
t.Fatalf("Unable to write file %q: %v", configFilePath, err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
skipPreFlight bool
|
||||
cfgPath string
|
||||
configToWrite string
|
||||
ignorePreflightErrors []string
|
||||
testJoinValidate bool
|
||||
testJoinRun bool
|
||||
cmdPersistentFlags map[string]string
|
||||
nodeConfig *kubeadm.JoinConfiguration
|
||||
expectedError bool
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
validate func(*testing.T, *joinData)
|
||||
expectError bool
|
||||
}{
|
||||
// Join data passed using flags
|
||||
{
|
||||
name: "invalid: missing config file",
|
||||
skipPreFlight: true,
|
||||
cfgPath: "missing-path-to-a-config",
|
||||
expectedError: true,
|
||||
name: "fails if no discovery method set",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: incorrect config file",
|
||||
skipPreFlight: true,
|
||||
cfgPath: configFilePath,
|
||||
configToWrite: "bad-config-contents",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: fail at preflight.RunJoinNodeChecks()",
|
||||
skipPreFlight: false,
|
||||
cfgPath: configFilePath,
|
||||
configToWrite: testConfig,
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: incorrect ignorePreflight argument",
|
||||
skipPreFlight: true,
|
||||
cfgPath: configFilePath,
|
||||
configToWrite: testConfig,
|
||||
ignorePreflightErrors: []string{"some-unsupported-preflight-arg"},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: fail Join.Validate() with wrong flags",
|
||||
skipPreFlight: true,
|
||||
cfgPath: configFilePath,
|
||||
configToWrite: testConfig,
|
||||
testJoinValidate: true,
|
||||
cmdPersistentFlags: map[string]string{
|
||||
"config": "some-config",
|
||||
"node-name": "some-node-name",
|
||||
name: "fails if both file and bootstrap discovery methods set",
|
||||
args: []string{"1.2.3.4:6443"},
|
||||
flags: map[string]string{
|
||||
options.FileDiscovery: "https://foo",
|
||||
options.TokenDiscovery: "abcdef.0123456789abcdef",
|
||||
options.TokenDiscoverySkipCAHash: "true",
|
||||
},
|
||||
expectedError: true,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: fail Join.Validate() with wrong node configuration",
|
||||
skipPreFlight: true,
|
||||
cfgPath: configFilePath,
|
||||
configToWrite: testConfig,
|
||||
testJoinValidate: true,
|
||||
expectedError: true,
|
||||
name: "pass if file discovery is set",
|
||||
flags: map[string]string{
|
||||
options.FileDiscovery: "https://foo",
|
||||
},
|
||||
validate: func(t *testing.T, data *joinData) {
|
||||
// validate that file discovery settings are set into join data
|
||||
if data.cfg.Discovery.File == nil || data.cfg.Discovery.File.KubeConfigPath != "https://foo" {
|
||||
t.Errorf("Invalid data.cfg.Discovery.File")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid: fail Join.Run() with invalid node config",
|
||||
skipPreFlight: true,
|
||||
cfgPath: configFilePath,
|
||||
configToWrite: testConfig,
|
||||
testJoinRun: true,
|
||||
expectedError: true,
|
||||
name: "pass if bootstrap discovery is set",
|
||||
args: []string{"1.2.3.4:6443", "5.6.7.8:6443"},
|
||||
flags: map[string]string{
|
||||
options.TokenDiscovery: "abcdef.0123456789abcdef",
|
||||
options.TokenDiscoverySkipCAHash: "true",
|
||||
},
|
||||
validate: func(t *testing.T, data *joinData) {
|
||||
// validate that bootstrap discovery settings are set into join data
|
||||
if data.cfg.Discovery.BootstrapToken == nil ||
|
||||
data.cfg.Discovery.BootstrapToken.APIServerEndpoint != "1.2.3.4:6443" || //only first arg should be kept as APIServerEndpoint
|
||||
data.cfg.Discovery.BootstrapToken.Token != "abcdef.0123456789abcdef" ||
|
||||
data.cfg.Discovery.BootstrapToken.UnsafeSkipCAVerification != true {
|
||||
t.Errorf("Invalid data.cfg.Discovery.BootstrapToken")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "--token sets TLSBootstrapToken and BootstrapToken.Token if unset",
|
||||
args: []string{"1.2.3.4:6443"},
|
||||
flags: map[string]string{
|
||||
options.TokenStr: "abcdef.0123456789abcdef",
|
||||
options.TokenDiscoverySkipCAHash: "true",
|
||||
},
|
||||
validate: func(t *testing.T, data *joinData) {
|
||||
// validate that token sets both TLSBootstrapToken and BootstrapToken.Token into join data
|
||||
if data.cfg.Discovery.TLSBootstrapToken != "abcdef.0123456789abcdef" ||
|
||||
data.cfg.Discovery.BootstrapToken == nil ||
|
||||
data.cfg.Discovery.BootstrapToken.Token != "abcdef.0123456789abcdef" {
|
||||
t.Errorf("Invalid TLSBootstrapToken or BootstrapToken.Token")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "--token doesn't override TLSBootstrapToken and BootstrapToken.Token if set",
|
||||
args: []string{"1.2.3.4:6443"},
|
||||
flags: map[string]string{
|
||||
options.TokenStr: "aaaaaa.0123456789aaaaaa",
|
||||
options.TLSBootstrapToken: "abcdef.0123456789abcdef",
|
||||
options.TokenDiscovery: "defghi.0123456789defghi",
|
||||
options.TokenDiscoverySkipCAHash: "true",
|
||||
},
|
||||
validate: func(t *testing.T, data *joinData) {
|
||||
// validate that TLSBootstrapToken and BootstrapToken.Token values are preserved into join data
|
||||
if data.cfg.Discovery.TLSBootstrapToken != "abcdef.0123456789abcdef" ||
|
||||
data.cfg.Discovery.BootstrapToken == nil ||
|
||||
data.cfg.Discovery.BootstrapToken.Token != "defghi.0123456789defghi" {
|
||||
t.Errorf("Invalid TLSBootstrapToken or BootstrapToken.Token")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "control plane setting are preserved if --control-plane flag is set",
|
||||
flags: map[string]string{
|
||||
options.ControlPlane: "true",
|
||||
options.APIServerAdvertiseAddress: "1.2.3.4",
|
||||
options.APIServerBindPort: "1234",
|
||||
options.FileDiscovery: "https://foo", //required only to pass discovery validation
|
||||
},
|
||||
validate: func(t *testing.T, data *joinData) {
|
||||
// validate that control plane attributes are set in join data
|
||||
if data.cfg.ControlPlane == nil ||
|
||||
data.cfg.ControlPlane.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" ||
|
||||
data.cfg.ControlPlane.LocalAPIEndpoint.BindPort != 1234 {
|
||||
t.Errorf("Invalid ControlPlane")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "control plane setting are cleaned up if --control-plane flag is not set",
|
||||
flags: map[string]string{
|
||||
options.ControlPlane: "false",
|
||||
options.APIServerAdvertiseAddress: "1.2.3.4",
|
||||
options.APIServerBindPort: "1.2.3.4",
|
||||
options.FileDiscovery: "https://foo", //required only to pass discovery validation
|
||||
},
|
||||
validate: func(t *testing.T, data *joinData) {
|
||||
// validate that control plane attributes are unset in join data
|
||||
if data.cfg.ControlPlane != nil {
|
||||
t.Errorf("Invalid ControlPlane")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fails if invalid preflight checks are provided",
|
||||
flags: map[string]string{
|
||||
options.IgnorePreflightErrors: "all,something-else",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
|
||||
// Join data passed using config file
|
||||
{
|
||||
name: "Pass with config from file",
|
||||
flags: map[string]string{
|
||||
options.CfgPath: configFilePath,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "--cri-socket and --node-name flags override config from file",
|
||||
flags: map[string]string{
|
||||
options.CfgPath: configFilePath,
|
||||
options.NodeCRISocket: "/var/run/crio/crio.sock",
|
||||
options.NodeName: "anotherName",
|
||||
},
|
||||
validate: func(t *testing.T, data *joinData) {
|
||||
// validate that cri-socket and node-name are overwritten
|
||||
if data.cfg.NodeRegistration.CRISocket != "/var/run/crio/crio.sock" {
|
||||
t.Errorf("Invalid NodeRegistration.CRISocket")
|
||||
}
|
||||
if data.cfg.NodeRegistration.Name != "anotherName" {
|
||||
t.Errorf("Invalid NodeRegistration.Name")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fail if mixedArguments are passed",
|
||||
flags: map[string]string{
|
||||
options.CfgPath: configFilePath,
|
||||
options.APIServerAdvertiseAddress: "1.2.3.4",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
cfg := &kubeadmapiv1beta1.JoinConfiguration{}
|
||||
kubeadmscheme.Scheme.Default(cfg)
|
||||
|
||||
errorFormat := "Test case %q: NewValidJoin expected error: %v, saw: %v, error: %v"
|
||||
|
||||
for _, tc := range testCases {
|
||||
if _, err = cfgFile.WriteString(tc.configToWrite); err != nil {
|
||||
t.Fatalf("Unable to write file %q: %v", tc.cfgPath, err)
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// initialize an external join option and inject it to the join cmd
|
||||
joinOptions := newJoinOptions()
|
||||
cmd := NewCmdJoin(nil, joinOptions)
|
||||
|
||||
cmd := NewCmdJoin(&out)
|
||||
if tc.cmdPersistentFlags != nil {
|
||||
for key, value := range tc.cmdPersistentFlags {
|
||||
cmd.PersistentFlags().Set(key, value)
|
||||
// sets cmd flags (that will be reflected on the join options)
|
||||
for f, v := range tc.flags {
|
||||
cmd.Flags().Set(f, v)
|
||||
}
|
||||
}
|
||||
|
||||
join, err := NewValidJoin(cmd.PersistentFlags(), cfg, tc.cfgPath, tc.ignorePreflightErrors)
|
||||
|
||||
if tc.nodeConfig != nil {
|
||||
join.cfg = tc.nodeConfig
|
||||
}
|
||||
|
||||
// test Join.Run()
|
||||
if err == nil && tc.testJoinRun {
|
||||
err = join.Run(&out)
|
||||
if (err != nil) != tc.expectedError {
|
||||
t.Fatalf(errorFormat, tc.name, tc.expectedError, (err != nil), err)
|
||||
// test newJoinData method
|
||||
data, err := newJoinData(cmd, tc.args, joinOptions, nil)
|
||||
if err != nil && !tc.expectError {
|
||||
t.Fatalf("newJoinData returned unexpected error: %v", err)
|
||||
}
|
||||
// check error for NewValidJoin()
|
||||
} else if (err != nil) != tc.expectedError {
|
||||
t.Fatalf(errorFormat, tc.name, tc.expectedError, (err != nil), err)
|
||||
}
|
||||
if err == nil && tc.expectError {
|
||||
t.Fatalf("newJoinData didn't return error when expected")
|
||||
}
|
||||
|
||||
// exec additional validation on the returned value
|
||||
if tc.validate != nil {
|
||||
tc.validate(t, &data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ const CSROnly = "csr-only"
|
|||
// CSRDir flag sets the location for CSRs and flags to be output
|
||||
const CSRDir = "csr-dir"
|
||||
|
||||
// TokenStr flag sets the token
|
||||
// TokenStr flags sets both the discovery-token and the tls-bootstrap-token when those values are not provided
|
||||
const TokenStr = "token"
|
||||
|
||||
// TokenTTL flag sets the time to live for token
|
||||
|
@ -99,3 +99,21 @@ const TokenGroups = "groups"
|
|||
|
||||
// TokenDescription flag sets the description of the token
|
||||
const TokenDescription = "description"
|
||||
|
||||
// TLSBootstrapToken flag sets the token used to temporarily authenticate with the Kubernetes Master to submit a certificate signing request (CSR) for a locally created key pair
|
||||
const TLSBootstrapToken = "tls-bootstrap-token"
|
||||
|
||||
// TokenDiscovery flag sets the token used to validate cluster information fetched from the API server (for token-based discovery)
|
||||
const TokenDiscovery = "discovery-token"
|
||||
|
||||
// TokenDiscoveryCAHash flag instruct kubeadm to validate that the root CA public key matches this hash (for token-based discovery)
|
||||
const TokenDiscoveryCAHash = "discovery-token-ca-cert-hash"
|
||||
|
||||
// TokenDiscoverySkipCAHash flag instruct kubeadm to skip CA hash verification (for token-based discovery)
|
||||
const TokenDiscoverySkipCAHash = "discovery-token-unsafe-skip-ca-verification"
|
||||
|
||||
// FileDiscovery flag sets the file or URL from which to load cluster information (for file-based discovery)
|
||||
const FileDiscovery = "discovery-file"
|
||||
|
||||
// ControlPlane flag instruct kubeadm to create a new control plane instance on this node
|
||||
const ControlPlane = "experimental-control-plane"
|
||||
|
|
Loading…
Reference in New Issue