...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/util/config/initconfiguration.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/util/config

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"bytes"
    21  	"net"
    22  	"os"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	netutil "k8s.io/apimachinery/pkg/util/net"
    32  	bootstraputil "k8s.io/cluster-bootstrap/token/util"
    33  	nodeutil "k8s.io/component-helpers/node/util"
    34  	"k8s.io/klog/v2"
    35  	netutils "k8s.io/utils/net"
    36  
    37  	bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
    38  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    39  	kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
    40  	kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
    41  	"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
    42  	"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
    43  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    44  	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
    45  	"k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict"
    46  	kubeadmruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
    47  )
    48  
    49  var (
    50  	// PlaceholderToken is only set statically to make kubeadm not randomize the token on every run
    51  	PlaceholderToken = bootstraptokenv1.BootstrapToken{
    52  		Token: &bootstraptokenv1.BootstrapTokenString{
    53  			ID:     "abcdef",
    54  			Secret: "0123456789abcdef",
    55  		},
    56  	}
    57  )
    58  
    59  // SetInitDynamicDefaults checks and sets configuration values for the InitConfiguration object
    60  func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration, skipCRIDetect bool) error {
    61  	if err := SetBootstrapTokensDynamicDefaults(&cfg.BootstrapTokens); err != nil {
    62  		return err
    63  	}
    64  	if err := SetNodeRegistrationDynamicDefaults(&cfg.NodeRegistration, true, skipCRIDetect); err != nil {
    65  		return err
    66  	}
    67  	if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil {
    68  		return err
    69  	}
    70  	return SetClusterDynamicDefaults(&cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, &cfg.NodeRegistration)
    71  }
    72  
    73  // SetBootstrapTokensDynamicDefaults checks and sets configuration values for the BootstrapTokens object
    74  func SetBootstrapTokensDynamicDefaults(cfg *[]bootstraptokenv1.BootstrapToken) error {
    75  	// Populate the .Token field with a random value if unset
    76  	// We do this at this layer, and not the API defaulting layer
    77  	// because of possible security concerns, and more practically
    78  	// because we can't return errors in the API object defaulting
    79  	// process but here we can.
    80  	for i, bt := range *cfg {
    81  		if bt.Token != nil && len(bt.Token.String()) > 0 {
    82  			continue
    83  		}
    84  
    85  		tokenStr, err := bootstraputil.GenerateBootstrapToken()
    86  		if err != nil {
    87  			return errors.Wrap(err, "couldn't generate random token")
    88  		}
    89  		token, err := bootstraptokenv1.NewBootstrapTokenString(tokenStr)
    90  		if err != nil {
    91  			return err
    92  		}
    93  		(*cfg)[i].Token = token
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  // SetNodeRegistrationDynamicDefaults checks and sets configuration values for the NodeRegistration object
   100  func SetNodeRegistrationDynamicDefaults(cfg *kubeadmapi.NodeRegistrationOptions, controlPlaneTaint, skipCRIDetect bool) error {
   101  	var err error
   102  	cfg.Name, err = nodeutil.GetHostname(cfg.Name)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	// Only if the slice is nil, we should append the control-plane taint. This allows the user to specify an empty slice for no default control-plane taint
   108  	if controlPlaneTaint && cfg.Taints == nil {
   109  		cfg.Taints = []v1.Taint{kubeadmconstants.ControlPlaneTaint}
   110  	}
   111  
   112  	if cfg.CRISocket == "" {
   113  		if skipCRIDetect {
   114  			klog.V(4).Infof("skip CRI socket detection, fill with the default CRI socket %s", kubeadmconstants.DefaultCRISocket)
   115  			cfg.CRISocket = kubeadmconstants.DefaultCRISocket
   116  			return nil
   117  		}
   118  		cfg.CRISocket, err = kubeadmruntime.DetectCRISocket()
   119  		if err != nil {
   120  			return err
   121  		}
   122  		klog.V(1).Infof("detected and using CRI socket: %s", cfg.CRISocket)
   123  	} else {
   124  		if !strings.HasPrefix(cfg.CRISocket, kubeadmapiv1.DefaultContainerRuntimeURLScheme) {
   125  			klog.Warningf("Usage of CRI endpoints without URL scheme is deprecated and can cause kubelet errors "+
   126  				"in the future. Automatically prepending scheme %q to the \"criSocket\" with value %q. "+
   127  				"Please update your configuration!", kubeadmapiv1.DefaultContainerRuntimeURLScheme, cfg.CRISocket)
   128  			cfg.CRISocket = kubeadmapiv1.DefaultContainerRuntimeURLScheme + "://" + cfg.CRISocket
   129  		}
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // SetAPIEndpointDynamicDefaults checks and sets configuration values for the APIEndpoint object
   136  func SetAPIEndpointDynamicDefaults(cfg *kubeadmapi.APIEndpoint) error {
   137  	// validate cfg.API.AdvertiseAddress.
   138  	addressIP := netutils.ParseIPSloppy(cfg.AdvertiseAddress)
   139  	if addressIP == nil && cfg.AdvertiseAddress != "" {
   140  		return errors.Errorf("couldn't use \"%s\" as \"apiserver-advertise-address\", must be ipv4 or ipv6 address", cfg.AdvertiseAddress)
   141  	}
   142  
   143  	// kubeadm allows users to specify address=Loopback as a selector for global unicast IP address that can be found on loopback interface.
   144  	// e.g. This is required for network setups where default routes are present, but network interfaces use only link-local addresses (e.g. as described in RFC5549).
   145  	if addressIP.IsLoopback() {
   146  		loopbackIP, err := netutil.ChooseBindAddressForInterface(netutil.LoopbackInterfaceName)
   147  		if err != nil {
   148  			return err
   149  		}
   150  		if loopbackIP != nil {
   151  			klog.V(4).Infof("Found active IP %v on loopback interface", loopbackIP.String())
   152  			cfg.AdvertiseAddress = loopbackIP.String()
   153  			return nil
   154  		}
   155  		return errors.New("unable to resolve link-local addresses")
   156  	}
   157  
   158  	// This is the same logic as the API Server uses, except that if no interface is found the address is set to 0.0.0.0, which is invalid and cannot be used
   159  	// for bootstrapping a cluster.
   160  	ip, err := ChooseAPIServerBindAddress(addressIP)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	cfg.AdvertiseAddress = ip.String()
   165  
   166  	return nil
   167  }
   168  
   169  // SetClusterDynamicDefaults checks and sets values for the ClusterConfiguration object
   170  func SetClusterDynamicDefaults(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint, nodeRegOpts *kubeadmapi.NodeRegistrationOptions) error {
   171  	// Default all the embedded ComponentConfig structs
   172  	componentconfigs.Default(cfg, localAPIEndpoint, nodeRegOpts)
   173  
   174  	// Resolve possible version labels and validate version string
   175  	if err := NormalizeKubernetesVersion(cfg); err != nil {
   176  		return err
   177  	}
   178  
   179  	// If ControlPlaneEndpoint is specified without a port number defaults it to
   180  	// the bindPort number of the APIEndpoint.
   181  	// This will allow join of additional control plane instances with different bindPort number
   182  	if cfg.ControlPlaneEndpoint != "" {
   183  		host, port, err := kubeadmutil.ParseHostPort(cfg.ControlPlaneEndpoint)
   184  		if err != nil {
   185  			return err
   186  		}
   187  		if port == "" {
   188  			cfg.ControlPlaneEndpoint = net.JoinHostPort(host, strconv.FormatInt(int64(localAPIEndpoint.BindPort), 10))
   189  		}
   190  	}
   191  
   192  	// Downcase SANs. Some domain names (like ELBs) have capitals in them.
   193  	LowercaseSANs(cfg.APIServer.CertSANs)
   194  	return nil
   195  }
   196  
   197  // DefaultedStaticInitConfiguration returns the internal InitConfiguration with static defaults.
   198  func DefaultedStaticInitConfiguration() (*kubeadmapi.InitConfiguration, error) {
   199  	versionedInitCfg := &kubeadmapiv1.InitConfiguration{
   200  		LocalAPIEndpoint: kubeadmapiv1.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
   201  		BootstrapTokens:  []bootstraptokenv1.BootstrapToken{PlaceholderToken},
   202  		NodeRegistration: kubeadmapiv1.NodeRegistrationOptions{
   203  			CRISocket: kubeadmconstants.DefaultCRISocket, // avoid CRI detection
   204  			Name:      "node",
   205  		},
   206  	}
   207  	versionedClusterCfg := &kubeadmapiv1.ClusterConfiguration{
   208  		KubernetesVersion: kubeadmconstants.CurrentKubernetesVersion.String(), // avoid going to the Internet for the current Kubernetes version
   209  	}
   210  
   211  	internalcfg := &kubeadmapi.InitConfiguration{}
   212  
   213  	// Takes passed flags into account; the defaulting is executed once again enforcing assignment of
   214  	// static default values to cfg only for values not provided with flags
   215  	kubeadmscheme.Scheme.Default(versionedInitCfg)
   216  	if err := kubeadmscheme.Scheme.Convert(versionedInitCfg, internalcfg, nil); err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	kubeadmscheme.Scheme.Default(versionedClusterCfg)
   221  	if err := kubeadmscheme.Scheme.Convert(versionedClusterCfg, &internalcfg.ClusterConfiguration, nil); err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	// Default all the embedded ComponentConfig structs
   226  	componentconfigs.Default(&internalcfg.ClusterConfiguration, &internalcfg.LocalAPIEndpoint, &internalcfg.NodeRegistration)
   227  
   228  	return internalcfg, nil
   229  }
   230  
   231  // DefaultedInitConfiguration takes a versioned init config (often populated by flags), defaults it and converts it into internal InitConfiguration
   232  func DefaultedInitConfiguration(versionedInitCfg *kubeadmapiv1.InitConfiguration, versionedClusterCfg *kubeadmapiv1.ClusterConfiguration, opts LoadOrDefaultConfigurationOptions) (*kubeadmapi.InitConfiguration, error) {
   233  	internalcfg := &kubeadmapi.InitConfiguration{}
   234  
   235  	// Takes passed flags into account; the defaulting is executed once again enforcing assignment of
   236  	// static default values to cfg only for values not provided with flags
   237  	kubeadmscheme.Scheme.Default(versionedInitCfg)
   238  	if err := kubeadmscheme.Scheme.Convert(versionedInitCfg, internalcfg, nil); err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	kubeadmscheme.Scheme.Default(versionedClusterCfg)
   243  	if err := kubeadmscheme.Scheme.Convert(versionedClusterCfg, &internalcfg.ClusterConfiguration, nil); err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	// Applies dynamic defaults to settings not provided with flags
   248  	if err := SetInitDynamicDefaults(internalcfg, opts.SkipCRIDetect); err != nil {
   249  		return nil, err
   250  	}
   251  	// Validates cfg (flags/configs + defaults + dynamic defaults)
   252  	if err := validation.ValidateInitConfiguration(internalcfg).ToAggregate(); err != nil {
   253  		return nil, err
   254  	}
   255  	return internalcfg, nil
   256  }
   257  
   258  // LoadInitConfigurationFromFile loads a supported versioned InitConfiguration from a file, converts it into internal config, defaults it and verifies it.
   259  func LoadInitConfigurationFromFile(cfgPath string, opts LoadOrDefaultConfigurationOptions) (*kubeadmapi.InitConfiguration, error) {
   260  	klog.V(1).Infof("loading configuration from %q", cfgPath)
   261  
   262  	b, err := os.ReadFile(cfgPath)
   263  	if err != nil {
   264  		return nil, errors.Wrapf(err, "unable to read config from %q ", cfgPath)
   265  	}
   266  
   267  	return BytesToInitConfiguration(b, opts.SkipCRIDetect)
   268  }
   269  
   270  // LoadOrDefaultInitConfiguration takes a path to a config file and a versioned configuration that can serve as the default config
   271  // If cfgPath is specified, the versioned configs will always get overridden with the one in the file (specified by cfgPath).
   272  // The external, versioned configuration is defaulted and converted to the internal type.
   273  // Right thereafter, the configuration is defaulted again with dynamic values (like IP addresses of a machine, etc)
   274  // Lastly, the internal config is validated and returned.
   275  func LoadOrDefaultInitConfiguration(cfgPath string, versionedInitCfg *kubeadmapiv1.InitConfiguration, versionedClusterCfg *kubeadmapiv1.ClusterConfiguration, opts LoadOrDefaultConfigurationOptions) (*kubeadmapi.InitConfiguration, error) {
   276  	var (
   277  		config *kubeadmapi.InitConfiguration
   278  		err    error
   279  	)
   280  	if cfgPath != "" {
   281  		// Loads configuration from config file, if provided
   282  		config, err = LoadInitConfigurationFromFile(cfgPath, opts)
   283  	} else {
   284  		config, err = DefaultedInitConfiguration(versionedInitCfg, versionedClusterCfg, opts)
   285  	}
   286  	if err == nil {
   287  		prepareStaticVariables(config)
   288  	}
   289  	return config, err
   290  }
   291  
   292  // BytesToInitConfiguration converts a byte slice to an internal, defaulted and validated InitConfiguration object.
   293  // The map may contain many different YAML documents. These YAML documents are parsed one-by-one
   294  // and well-known ComponentConfig GroupVersionKinds are stored inside of the internal InitConfiguration struct.
   295  // The resulting InitConfiguration is then dynamically defaulted and validated prior to return.
   296  func BytesToInitConfiguration(b []byte, skipCRIDetect bool) (*kubeadmapi.InitConfiguration, error) {
   297  	gvkmap, err := kubeadmutil.SplitYAMLDocuments(b)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  
   302  	return documentMapToInitConfiguration(gvkmap, false, false, false, skipCRIDetect)
   303  }
   304  
   305  // documentMapToInitConfiguration converts a map of GVKs and YAML documents to defaulted and validated configuration object.
   306  func documentMapToInitConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated, allowExperimental, strictErrors, skipCRIDetect bool) (*kubeadmapi.InitConfiguration, error) {
   307  	var initcfg *kubeadmapi.InitConfiguration
   308  	var clustercfg *kubeadmapi.ClusterConfiguration
   309  
   310  	for gvk, fileContent := range gvkmap {
   311  		// first, check if this GVK is supported and possibly not deprecated
   312  		if err := validateSupportedVersion(gvk.GroupVersion(), allowDeprecated, allowExperimental); err != nil {
   313  			return nil, err
   314  		}
   315  
   316  		// verify the validity of the YAML
   317  		if err := strict.VerifyUnmarshalStrict([]*runtime.Scheme{kubeadmscheme.Scheme, componentconfigs.Scheme}, gvk, fileContent); err != nil {
   318  			if !strictErrors {
   319  				klog.Warning(err.Error())
   320  			} else {
   321  				return nil, err
   322  			}
   323  		}
   324  
   325  		if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvk) {
   326  			// Set initcfg to an empty struct value the deserializer will populate
   327  			initcfg = &kubeadmapi.InitConfiguration{}
   328  			// Decode the bytes into the internal struct. Under the hood, the bytes will be unmarshalled into the
   329  			// right external version, defaulted, and converted into the internal version.
   330  			if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), fileContent, initcfg); err != nil {
   331  				return nil, err
   332  			}
   333  			continue
   334  		}
   335  		if kubeadmutil.GroupVersionKindsHasClusterConfiguration(gvk) {
   336  			// Set clustercfg to an empty struct value the deserializer will populate
   337  			clustercfg = &kubeadmapi.ClusterConfiguration{}
   338  			// Decode the bytes into the internal struct. Under the hood, the bytes will be unmarshalled into the
   339  			// right external version, defaulted, and converted into the internal version.
   340  			if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), fileContent, clustercfg); err != nil {
   341  				return nil, err
   342  			}
   343  			continue
   344  		}
   345  
   346  		// If the group is neither a kubeadm core type or of a supported component config group, we dump a warning about it being ignored
   347  		if !componentconfigs.Scheme.IsGroupRegistered(gvk.Group) {
   348  			klog.Warningf("[config] WARNING: Ignored YAML document with GroupVersionKind %v\n", gvk)
   349  		}
   350  	}
   351  
   352  	// Enforce that InitConfiguration and/or ClusterConfiguration has to exist among the YAML documents
   353  	if initcfg == nil && clustercfg == nil {
   354  		return nil, errors.New("no InitConfiguration or ClusterConfiguration kind was found in the YAML file")
   355  	}
   356  
   357  	// If InitConfiguration wasn't given, default it by creating an external struct instance, default it and convert into the internal type
   358  	if initcfg == nil {
   359  		extinitcfg := &kubeadmapiv1.InitConfiguration{}
   360  		kubeadmscheme.Scheme.Default(extinitcfg)
   361  		// Set initcfg to an empty struct value the deserializer will populate
   362  		initcfg = &kubeadmapi.InitConfiguration{}
   363  		if err := kubeadmscheme.Scheme.Convert(extinitcfg, initcfg, nil); err != nil {
   364  			return nil, err
   365  		}
   366  	}
   367  	// If ClusterConfiguration was given, populate it in the InitConfiguration struct
   368  	if clustercfg != nil {
   369  		initcfg.ClusterConfiguration = *clustercfg
   370  	} else {
   371  		// Populate the internal InitConfiguration.ClusterConfiguration with defaults
   372  		extclustercfg := &kubeadmapiv1.ClusterConfiguration{}
   373  		kubeadmscheme.Scheme.Default(extclustercfg)
   374  		if err := kubeadmscheme.Scheme.Convert(extclustercfg, &initcfg.ClusterConfiguration, nil); err != nil {
   375  			return nil, err
   376  		}
   377  	}
   378  
   379  	// Load any component configs
   380  	if err := componentconfigs.FetchFromDocumentMap(&initcfg.ClusterConfiguration, gvkmap); err != nil {
   381  		return nil, err
   382  	}
   383  
   384  	// Applies dynamic defaults to settings not provided with flags
   385  	if err := SetInitDynamicDefaults(initcfg, skipCRIDetect); err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	// Validates cfg (flags/configs + defaults + dynamic defaults)
   390  	if err := validation.ValidateInitConfiguration(initcfg).ToAggregate(); err != nil {
   391  		return nil, err
   392  	}
   393  
   394  	return initcfg, nil
   395  }
   396  
   397  // MarshalInitConfigurationToBytes marshals the internal InitConfiguration object to bytes. It writes the embedded
   398  // ClusterConfiguration object with ComponentConfigs out as separate YAML documents
   399  func MarshalInitConfigurationToBytes(cfg *kubeadmapi.InitConfiguration, gv schema.GroupVersion) ([]byte, error) {
   400  	initbytes, err := kubeadmutil.MarshalToYamlForCodecs(cfg, gv, kubeadmscheme.Codecs)
   401  	if err != nil {
   402  		return []byte{}, err
   403  	}
   404  	allFiles := [][]byte{initbytes}
   405  
   406  	// Exception: If the specified groupversion is targeting the internal type, don't print embedded ClusterConfiguration contents
   407  	// This is mostly used for unit testing. In a real scenario the internal version of the API is never marshalled as-is.
   408  	if gv.Version != runtime.APIVersionInternal {
   409  		clusterbytes, err := kubeadmutil.MarshalToYamlForCodecs(&cfg.ClusterConfiguration, gv, kubeadmscheme.Codecs)
   410  		if err != nil {
   411  			return []byte{}, err
   412  		}
   413  		allFiles = append(allFiles, clusterbytes)
   414  	}
   415  	return bytes.Join(allFiles, []byte(kubeadmconstants.YAMLDocumentSeparator)), nil
   416  }
   417  

View as plain text