mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
Merge pull request #91980 from rosti/kubeadm-cc-manual-upgrade
kubeadm upgrade: Allow supplying hand migrated component configs
This commit is contained in:
commit
a463b25c9d
@ -254,7 +254,7 @@ func getInternalCfg(cfgPath string, kubeconfigPath string, cfg kubeadmapiv1beta2
|
|||||||
if cfgPath == "" {
|
if cfgPath == "" {
|
||||||
client, err := kubeconfigutil.ClientSetFromFile(kubeconfigPath)
|
client, err := kubeconfigutil.ClientSetFromFile(kubeconfigPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, out, logPrefix, false)
|
internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, out, logPrefix, false, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output
|
fmt.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output
|
||||||
return internalcfg, nil
|
return internalcfg, nil
|
||||||
|
@ -545,7 +545,7 @@ func fetchInitConfiguration(tlsBootstrapCfg *clientcmdapi.Config) (*kubeadmapi.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the init configuration
|
// Fetches the init configuration
|
||||||
initConfiguration, err := configutil.FetchInitConfigurationFromCluster(tlsClient, os.Stdout, "preflight", true)
|
initConfiguration, err := configutil.FetchInitConfigurationFromCluster(tlsClient, os.Stdout, "preflight", true, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
|
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out i
|
|||||||
client, err := getClientset(options.kubeconfigPath, false)
|
client, err := getClientset(options.kubeconfigPath, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", options.kubeconfigPath)
|
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", options.kubeconfigPath)
|
||||||
cfg, err = configutil.FetchInitConfigurationFromCluster(client, out, "reset", false)
|
cfg, err = configutil.FetchInitConfigurationFromCluster(client, out, "reset", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
|
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ go_library(
|
|||||||
"//cmd/kubeadm/app/cmd/phases/upgrade/node:go_default_library",
|
"//cmd/kubeadm/app/cmd/phases/upgrade/node:go_default_library",
|
||||||
"//cmd/kubeadm/app/cmd/phases/workflow:go_default_library",
|
"//cmd/kubeadm/app/cmd/phases/workflow:go_default_library",
|
||||||
"//cmd/kubeadm/app/cmd/util:go_default_library",
|
"//cmd/kubeadm/app/cmd/util:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/componentconfigs:go_default_library",
|
||||||
"//cmd/kubeadm/app/constants:go_default_library",
|
"//cmd/kubeadm/app/constants:go_default_library",
|
||||||
"//cmd/kubeadm/app/features:go_default_library",
|
"//cmd/kubeadm/app/features:go_default_library",
|
||||||
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
|
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
|
||||||
|
@ -73,12 +73,7 @@ func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
|
|||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Upgrade your Kubernetes cluster to the specified version",
|
Short: "Upgrade your Kubernetes cluster to the specified version",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
userVersion, err := getK8sVersionFromUserInput(flags.applyPlanFlags, args, true)
|
return runApply(flags, args)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return runApply(flags, userVersion)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,12 +105,12 @@ func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
|
|||||||
// - Creating the RBAC rules for the bootstrap tokens and the cluster-info ConfigMap
|
// - Creating the RBAC rules for the bootstrap tokens and the cluster-info ConfigMap
|
||||||
// - Applying new kube-dns and kube-proxy manifests
|
// - Applying new kube-dns and kube-proxy manifests
|
||||||
// - Uploads the newly used configuration to the cluster ConfigMap
|
// - Uploads the newly used configuration to the cluster ConfigMap
|
||||||
func runApply(flags *applyFlags, userVersion string) error {
|
func runApply(flags *applyFlags, args []string) error {
|
||||||
|
|
||||||
// Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap)
|
// Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap)
|
||||||
klog.V(1).Infoln("[upgrade/apply] verifying health of cluster")
|
klog.V(1).Infoln("[upgrade/apply] verifying health of cluster")
|
||||||
klog.V(1).Infoln("[upgrade/apply] retrieving configuration from cluster")
|
klog.V(1).Infoln("[upgrade/apply] retrieving configuration from cluster")
|
||||||
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, flags.dryRun, userVersion)
|
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, flags.dryRun, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -36,47 +37,91 @@ import (
|
|||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
||||||
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||||
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
|
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
|
||||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getK8sVersionFromUserInput(flags *applyPlanFlags, args []string, versionIsMandatory bool) (string, error) {
|
// isKubeadmConfigPresent checks if a kubeadm config type is found in the provided document map
|
||||||
var userVersion string
|
func isKubeadmConfigPresent(docmap kubeadmapi.DocumentMap) bool {
|
||||||
|
for gvk := range docmap {
|
||||||
// If the version is specified in config file, pick up that value.
|
if gvk.Group == kubeadmapi.GroupName {
|
||||||
if flags.cfgPath != "" {
|
return true
|
||||||
// Note that cfg isn't preserved here, it's just an one-off to populate userVersion based on --config
|
|
||||||
cfg, err := configutil.LoadInitConfigurationFromFile(flags.cfgPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
userVersion = cfg.KubernetesVersion
|
// loadConfig loads configuration from a file and/or the cluster. InitConfiguration, ClusterConfiguration and (optionally) component configs
|
||||||
|
// are loaded. This function allows the component configs to be loaded from a file that contains only them. If the file contains any kubeadm types
|
||||||
|
// in it (API group "kubeadm.kubernetes.io" present), then the supplied file is treaded as a legacy reconfiguration style "--config" use and the
|
||||||
|
// returned bool value is set to true (the only case to be done so).
|
||||||
|
func loadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, bool, error) {
|
||||||
|
// Used for info logs here
|
||||||
|
const logPrefix = "upgrade/config"
|
||||||
|
|
||||||
|
// The usual case here is to not have a config file, but rather load the config from the cluster.
|
||||||
|
// This is probably 90% of the time. So we handle it first.
|
||||||
|
if cfgPath == "" {
|
||||||
|
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, logPrefix, false, skipComponentConfigs)
|
||||||
|
return cfg, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// the version arg is mandatory unless version is specified in the config file
|
// Otherwise, we have a config file. Let's load it.
|
||||||
if versionIsMandatory && userVersion == "" {
|
configBytes, err := ioutil.ReadFile(cfgPath)
|
||||||
if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, false, errors.Wrapf(err, "unable to load config from file %q", cfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the YAML documents in the file into a DocumentMap
|
||||||
|
docmap, err := kubeadmutil.SplitYAMLDocuments(configBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are kubeadm types (API group kubeadm.kubernetes.io) present, we need to keep the existing behavior
|
||||||
|
// here. Basically, we have to load all of the configs from the file and none from the cluster. Configs that are
|
||||||
|
// missing from the file will be automatically regenerated by kubeadm even if they are present in the cluster.
|
||||||
|
// The resulting configs overwrite the existing cluster ones at the end of a successful upgrade apply operation.
|
||||||
|
if isKubeadmConfigPresent(docmap) {
|
||||||
|
klog.Warning("WARNING: Usage of the --config flag with kubeadm config types for reconfiguring the cluster during upgrade is not recommended!")
|
||||||
|
cfg, err := configutil.BytesToInitConfiguration(configBytes)
|
||||||
|
return cfg, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no kubeadm config types are present, we assume that there are manually upgraded component configs in the file.
|
||||||
|
// Hence, we load the kubeadm types from the cluster.
|
||||||
|
initCfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, logPrefix, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop here if the caller does not want us to load the component configs
|
||||||
|
if !skipComponentConfigs {
|
||||||
|
// Load the component configs with upgrades
|
||||||
|
if err := componentconfigs.FetchFromClusterWithLocalOverwrites(&initCfg.ClusterConfiguration, client, docmap); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now default and validate the configs
|
||||||
|
componentconfigs.Default(&initCfg.ClusterConfiguration, &initCfg.LocalAPIEndpoint, &initCfg.NodeRegistration)
|
||||||
|
if errs := componentconfigs.Validate(&initCfg.ClusterConfiguration); len(errs) != 0 {
|
||||||
|
return nil, false, errs.ToAggregate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If option was specified in both args and config file, args will overwrite the config file.
|
return initCfg, false, nil
|
||||||
if len(args) == 1 {
|
|
||||||
userVersion = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return userVersion, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure
|
// enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure
|
||||||
func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion string) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, error) {
|
func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgradeApply bool) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, error) {
|
||||||
client, err := getClient(flags.kubeConfigPath, dryRun)
|
client, err := getClient(flags.kubeConfigPath, dryRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)
|
return nil, nil, nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)
|
||||||
@ -90,14 +135,8 @@ func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion strin
|
|||||||
// Fetch the configuration from a file or ConfigMap and validate it
|
// Fetch the configuration from a file or ConfigMap and validate it
|
||||||
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
|
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
|
||||||
|
|
||||||
var cfg *kubeadmapi.InitConfiguration
|
var newK8sVersion string
|
||||||
if flags.cfgPath != "" {
|
cfg, legacyReconfigure, err := loadConfig(flags.cfgPath, client, !upgradeApply)
|
||||||
klog.Warning("WARNING: Usage of the --config flag for reconfiguring the cluster during upgrade is not recommended!")
|
|
||||||
cfg, err = configutil.LoadInitConfigurationFromFile(flags.cfgPath)
|
|
||||||
} else {
|
|
||||||
cfg, err = configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade/config", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apierrors.IsNotFound(err) {
|
if apierrors.IsNotFound(err) {
|
||||||
fmt.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
|
fmt.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
|
||||||
@ -111,6 +150,11 @@ func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion strin
|
|||||||
err = errors.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
|
err = errors.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
|
||||||
}
|
}
|
||||||
return nil, nil, nil, errors.Wrap(err, "[upgrade/config] FATAL")
|
return nil, nil, nil, errors.Wrap(err, "[upgrade/config] FATAL")
|
||||||
|
} else if legacyReconfigure {
|
||||||
|
// Set the newK8sVersion to the value in the ClusterConfiguration. This is done, so that users who use the --config option
|
||||||
|
// to supply a new ClusterConfiguration don't have to specify the Kubernetes version twice,
|
||||||
|
// if they don't want to upgrade but just change a setting.
|
||||||
|
newK8sVersion = cfg.KubernetesVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, cfg.NodeRegistration.IgnorePreflightErrors)
|
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, cfg.NodeRegistration.IgnorePreflightErrors)
|
||||||
@ -131,8 +175,16 @@ func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion strin
|
|||||||
return nil, nil, nil, errors.Wrap(err, "[upgrade/health] FATAL")
|
return nil, nil, nil, errors.Wrap(err, "[upgrade/health] FATAL")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a new k8s version should be set, apply the change before printing the config
|
// The version arg is mandatory, during upgrade apply, unless it's specified in the config file
|
||||||
if len(newK8sVersion) != 0 {
|
if upgradeApply && newK8sVersion == "" {
|
||||||
|
if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If option was specified in both args and config file, args will overwrite the config file.
|
||||||
|
if len(args) == 1 {
|
||||||
|
newK8sVersion = args[0]
|
||||||
cfg.KubernetesVersion = newK8sVersion
|
cfg.KubernetesVersion = newK8sVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,98 +18,11 @@ package upgrade
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetK8sVersionFromUserInput(t *testing.T) {
|
|
||||||
currentVersion := "v" + constants.CurrentKubernetesVersion.String()
|
|
||||||
validConfig := "apiVersion: kubeadm.k8s.io/v1beta2\n" +
|
|
||||||
"kind: ClusterConfiguration\n" +
|
|
||||||
"kubernetesVersion: " + currentVersion
|
|
||||||
|
|
||||||
var tcases = []struct {
|
|
||||||
name string
|
|
||||||
isVersionMandatory bool
|
|
||||||
clusterConfig string
|
|
||||||
args []string
|
|
||||||
expectedErr bool
|
|
||||||
expectedVersion string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "No config and version as an argument",
|
|
||||||
isVersionMandatory: true,
|
|
||||||
args: []string{"v1.13.1"},
|
|
||||||
expectedVersion: "v1.13.1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Neither config nor version specified",
|
|
||||||
isVersionMandatory: true,
|
|
||||||
expectedErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "No config and empty version as an argument",
|
|
||||||
isVersionMandatory: true,
|
|
||||||
args: []string{""},
|
|
||||||
expectedErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Valid config, but no version specified",
|
|
||||||
isVersionMandatory: true,
|
|
||||||
clusterConfig: validConfig,
|
|
||||||
expectedVersion: currentVersion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Valid config and different version specified",
|
|
||||||
isVersionMandatory: true,
|
|
||||||
clusterConfig: validConfig,
|
|
||||||
args: []string{"v1.13.1"},
|
|
||||||
expectedVersion: "v1.13.1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Version is optional",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tcases {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
flags := &applyPlanFlags{}
|
|
||||||
if len(tt.clusterConfig) > 0 {
|
|
||||||
file, err := ioutil.TempFile("", "kubeadm-upgrade-common-test-*.yaml")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create test config file: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpFileName := file.Name()
|
|
||||||
defer os.Remove(tmpFileName)
|
|
||||||
|
|
||||||
_, err = file.WriteString(tt.clusterConfig)
|
|
||||||
file.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write test config file contents: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags.cfgPath = tmpFileName
|
|
||||||
}
|
|
||||||
|
|
||||||
userVersion, err := getK8sVersionFromUserInput(flags, tt.args, tt.isVersionMandatory)
|
|
||||||
|
|
||||||
if err == nil && tt.expectedErr {
|
|
||||||
t.Error("Expected error, but got success")
|
|
||||||
}
|
|
||||||
if err != nil && !tt.expectedErr {
|
|
||||||
t.Errorf("Unexpected error: %+v", err)
|
|
||||||
}
|
|
||||||
if userVersion != tt.expectedVersion {
|
|
||||||
t.Errorf("Expected %q, but got %q", tt.expectedVersion, userVersion)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnforceRequirements(t *testing.T) {
|
func TestEnforceRequirements(t *testing.T) {
|
||||||
tcases := []struct {
|
tcases := []struct {
|
||||||
name string
|
name string
|
||||||
@ -139,7 +52,7 @@ func TestEnforceRequirements(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tcases {
|
for _, tt := range tcases {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
_, _, _, err := enforceRequirements(&tt.flags, tt.dryRun, tt.newK8sVersion)
|
_, _, _, err := enforceRequirements(&tt.flags, nil, tt.dryRun, false)
|
||||||
|
|
||||||
if err == nil && tt.expectedErr {
|
if err == nil && tt.expectedErr {
|
||||||
t.Error("Expected error, but got success")
|
t.Error("Expected error, but got success")
|
||||||
|
@ -91,7 +91,7 @@ func runDiff(flags *diffFlags, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)
|
return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)
|
||||||
}
|
}
|
||||||
cfg, err = configutil.FetchInitConfigurationFromCluster(client, flags.out, "upgrade/diff", false)
|
cfg, err = configutil.FetchInitConfigurationFromCluster(client, flags.out, "upgrade/diff", false, false)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -141,7 +141,7 @@ func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*node
|
|||||||
// Fetches the cluster configuration
|
// Fetches the cluster configuration
|
||||||
// NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node
|
// NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node
|
||||||
// (worker node), we are not reading local API address and the CRI socket from the node object
|
// (worker node), we are not reading local API address and the CRI socket from the node object
|
||||||
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", !isControlPlaneNode)
|
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", !isControlPlaneNode, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
|
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,7 @@ func NewCmdPlan(apf *applyPlanFlags) *cobra.Command {
|
|||||||
Use: "plan [version] [flags]",
|
Use: "plan [version] [flags]",
|
||||||
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter",
|
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter",
|
||||||
RunE: func(_ *cobra.Command, args []string) error {
|
RunE: func(_ *cobra.Command, args []string) error {
|
||||||
userVersion, err := getK8sVersionFromUserInput(flags.applyPlanFlags, args, false)
|
return runPlan(flags, args)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return runPlan(flags, userVersion)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,11 +58,11 @@ func NewCmdPlan(apf *applyPlanFlags) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runPlan takes care of outputting available versions to upgrade to for the user
|
// runPlan takes care of outputting available versions to upgrade to for the user
|
||||||
func runPlan(flags *planFlags, userVersion string) error {
|
func runPlan(flags *planFlags, args []string) error {
|
||||||
// Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning.
|
// Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning.
|
||||||
klog.V(1).Infoln("[upgrade/plan] verifying health of cluster")
|
klog.V(1).Infoln("[upgrade/plan] verifying health of cluster")
|
||||||
klog.V(1).Infoln("[upgrade/plan] retrieving configuration from cluster")
|
klog.V(1).Infoln("[upgrade/plan] retrieving configuration from cluster")
|
||||||
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, false, userVersion)
|
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -239,6 +239,49 @@ func FetchFromDocumentMap(clusterCfg *kubeadmapi.ClusterConfiguration, docmap ku
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FetchFromClusterWithLocalOverwrites fetches component configs from a cluster and overwrites them locally with
|
||||||
|
// the ones present in the supplied document map. If any UnsupportedConfigVersionError are not handled by the configs
|
||||||
|
// in the document map, the function returns them all as a single UnsupportedConfigVersionsErrorMap.
|
||||||
|
// This function is normally called only in some specific cases during upgrade.
|
||||||
|
func FetchFromClusterWithLocalOverwrites(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, docmap kubeadmapi.DocumentMap) error {
|
||||||
|
ensureInitializedComponentConfigs(clusterCfg)
|
||||||
|
|
||||||
|
oldVersionErrs := UnsupportedConfigVersionsErrorMap{}
|
||||||
|
|
||||||
|
for _, handler := range known {
|
||||||
|
componentCfg, err := handler.FromCluster(client, clusterCfg)
|
||||||
|
if err != nil {
|
||||||
|
if vererr, ok := err.(*UnsupportedConfigVersionError); ok {
|
||||||
|
oldVersionErrs[handler.GroupVersion.Group] = vererr
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if componentCfg != nil {
|
||||||
|
clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, handler := range known {
|
||||||
|
componentCfg, err := handler.FromDocumentMap(docmap)
|
||||||
|
if err != nil {
|
||||||
|
if vererr, ok := err.(*UnsupportedConfigVersionError); ok {
|
||||||
|
oldVersionErrs[handler.GroupVersion.Group] = vererr
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if componentCfg != nil {
|
||||||
|
clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg
|
||||||
|
delete(oldVersionErrs, handler.GroupVersion.Group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(oldVersionErrs) != 0 {
|
||||||
|
return oldVersionErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate is a placeholder for performing a validation on an already loaded component configs in a ClusterConfiguration
|
// Validate is a placeholder for performing a validation on an already loaded component configs in a ClusterConfiguration
|
||||||
// Currently it prints a warning that no validation was performed
|
// Currently it prints a warning that no validation was performed
|
||||||
func Validate(clusterCfg *kubeadmapi.ClusterConfiguration) field.ErrorList {
|
func Validate(clusterCfg *kubeadmapi.ClusterConfiguration) field.ErrorList {
|
||||||
|
@ -110,3 +110,139 @@ func TestFetchFromDocumentMap(t *testing.T) {
|
|||||||
t.Fatalf("missmatch between supplied and loaded type numbers:\n\tgot: %d\n\texpected: %d", len(clusterCfg.ComponentConfigs), len(gvkmap))
|
t.Fatalf("missmatch between supplied and loaded type numbers:\n\tgot: %d\n\texpected: %d", len(clusterCfg.ComponentConfigs), len(gvkmap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func kubeproxyConfigMap(contents string) *v1.ConfigMap {
|
||||||
|
return &v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: constants.KubeProxyConfigMap,
|
||||||
|
Namespace: metav1.NamespaceSystem,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
constants.KubeProxyConfigMapKey: dedent.Dedent(contents),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchFromClusterWithLocalUpgrades(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
obj runtime.Object
|
||||||
|
config string
|
||||||
|
expectedValue string
|
||||||
|
expectedErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "reconginzed cluster object without overwrite is used",
|
||||||
|
obj: kubeproxyConfigMap(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: foo
|
||||||
|
`),
|
||||||
|
expectedValue: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "reconginzed cluster object with overwrite is not used",
|
||||||
|
obj: kubeproxyConfigMap(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: foo
|
||||||
|
`),
|
||||||
|
config: dedent.Dedent(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: bar
|
||||||
|
`),
|
||||||
|
expectedValue: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "old config without overwrite returns an error",
|
||||||
|
obj: kubeproxyConfigMap(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: foo
|
||||||
|
`),
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "old config with recognized overwrite returns success",
|
||||||
|
obj: kubeproxyConfigMap(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: foo
|
||||||
|
`),
|
||||||
|
config: dedent.Dedent(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: bar
|
||||||
|
`),
|
||||||
|
expectedValue: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "old config with old overwrite returns an error",
|
||||||
|
obj: kubeproxyConfigMap(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: foo
|
||||||
|
`),
|
||||||
|
config: dedent.Dedent(`
|
||||||
|
apiVersion: kubeproxy.config.k8s.io/v1alpha0
|
||||||
|
kind: KubeProxyConfiguration
|
||||||
|
hostnameOverride: bar
|
||||||
|
`),
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range cases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
clusterCfg := &kubeadmapi.ClusterConfiguration{
|
||||||
|
KubernetesVersion: constants.CurrentKubernetesVersion.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
k8sVersion := version.MustParseGeneric(clusterCfg.KubernetesVersion)
|
||||||
|
|
||||||
|
client := clientsetfake.NewSimpleClientset(
|
||||||
|
test.obj,
|
||||||
|
&v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: constants.GetKubeletConfigMapName(k8sVersion),
|
||||||
|
Namespace: metav1.NamespaceSystem,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(`
|
||||||
|
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||||
|
kind: KubeletConfiguration
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
docmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test.config))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = FetchFromClusterWithLocalOverwrites(clusterCfg, client, docmap)
|
||||||
|
if err != nil {
|
||||||
|
if !test.expectedErr {
|
||||||
|
t.Errorf("unexpected failure: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if test.expectedErr {
|
||||||
|
t.Error("unexpected success")
|
||||||
|
} else {
|
||||||
|
kubeproxyCfg, ok := clusterCfg.ComponentConfigs[KubeProxyGroup]
|
||||||
|
if !ok {
|
||||||
|
t.Error("the config was reported as loaded, but was not in reality")
|
||||||
|
} else {
|
||||||
|
actualConfig, ok := kubeproxyCfg.(*kubeProxyConfig)
|
||||||
|
if !ok {
|
||||||
|
t.Error("the config is not of the expected type")
|
||||||
|
} else if actualConfig.config.HostnameOverride != test.expectedValue {
|
||||||
|
t.Errorf("unexpected value:\n\tgot: %q\n\texpected: %q", actualConfig.config.HostnameOverride, test.expectedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,8 @@ package componentconfigs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
@ -40,6 +42,27 @@ func (err *UnsupportedConfigVersionError) Error() string {
|
|||||||
return fmt.Sprintf("unsupported apiVersion %q, you may have to do manual conversion to %q and run kubeadm again", err.OldVersion, err.CurrentVersion)
|
return fmt.Sprintf("unsupported apiVersion %q, you may have to do manual conversion to %q and run kubeadm again", err.OldVersion, err.CurrentVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsupportedConfigVersionsErrorMap is a cumulative version of the UnsupportedConfigVersionError type
|
||||||
|
type UnsupportedConfigVersionsErrorMap map[string]*UnsupportedConfigVersionError
|
||||||
|
|
||||||
|
// Error implements the standard Golang error interface for UnsupportedConfigVersionsErrorMap
|
||||||
|
func (errs UnsupportedConfigVersionsErrorMap) Error() string {
|
||||||
|
// Make sure the error messages we print are predictable by sorting them by the group names involved
|
||||||
|
groups := make([]string, 0, len(errs))
|
||||||
|
for group := range errs {
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
sort.Strings(groups)
|
||||||
|
|
||||||
|
msgs := make([]string, 1, 1+len(errs))
|
||||||
|
msgs[0] = "multiple unsupported config version errors encountered:"
|
||||||
|
for _, group := range groups {
|
||||||
|
msgs = append(msgs, errs[group].Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(msgs, "\n\t- ")
|
||||||
|
}
|
||||||
|
|
||||||
// warnDefaultComponentConfigValue prints a warning if the user modified a field in a certain
|
// warnDefaultComponentConfigValue prints a warning if the user modified a field in a certain
|
||||||
// CompomentConfig from the default recommended value in kubeadm.
|
// CompomentConfig from the default recommended value in kubeadm.
|
||||||
func warnDefaultComponentConfigValue(componentConfigKind, paramName string, defaultValue, userValue interface{}) {
|
func warnDefaultComponentConfigValue(componentConfigKind, paramName string, defaultValue, userValue interface{}) {
|
||||||
|
@ -61,12 +61,12 @@ func (ue *unretriableError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FetchInitConfigurationFromCluster fetches configuration from a ConfigMap in the cluster
|
// FetchInitConfigurationFromCluster fetches configuration from a ConfigMap in the cluster
|
||||||
func FetchInitConfigurationFromCluster(client clientset.Interface, w io.Writer, logPrefix string, newControlPlane bool) (*kubeadmapi.InitConfiguration, error) {
|
func FetchInitConfigurationFromCluster(client clientset.Interface, w io.Writer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) {
|
||||||
fmt.Fprintf(w, "[%s] Reading configuration from the cluster...\n", logPrefix)
|
fmt.Fprintf(w, "[%s] Reading configuration from the cluster...\n", logPrefix)
|
||||||
fmt.Fprintf(w, "[%s] FYI: You can look at this config file with 'kubectl -n %s get cm %s -oyaml'\n", logPrefix, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap)
|
fmt.Fprintf(w, "[%s] FYI: You can look at this config file with 'kubectl -n %s get cm %s -oyaml'\n", logPrefix, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap)
|
||||||
|
|
||||||
// Fetch the actual config from cluster
|
// Fetch the actual config from cluster
|
||||||
cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane)
|
cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane, skipComponentConfigs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ func FetchInitConfigurationFromCluster(client clientset.Interface, w io.Writer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getInitConfigurationFromCluster is separate only for testing purposes, don't call it directly, use FetchInitConfigurationFromCluster instead
|
// getInitConfigurationFromCluster is separate only for testing purposes, don't call it directly, use FetchInitConfigurationFromCluster instead
|
||||||
func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Interface, newControlPlane bool) (*kubeadmapi.InitConfiguration, error) {
|
func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Interface, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) {
|
||||||
// Also, the config map really should be KubeadmConfigConfigMap...
|
// Also, the config map really should be KubeadmConfigConfigMap...
|
||||||
configMap, err := apiclient.GetConfigMapWithRetry(client, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap)
|
configMap, err := apiclient.GetConfigMapWithRetry(client, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -99,9 +99,11 @@ func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Inte
|
|||||||
return nil, errors.Wrap(err, "failed to decode cluster configuration data")
|
return nil, errors.Wrap(err, "failed to decode cluster configuration data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets the component configs from the corresponding config maps
|
if !skipComponentConfigs {
|
||||||
if err := componentconfigs.FetchFromCluster(&initcfg.ClusterConfiguration, client); err != nil {
|
// get the component configs from the corresponding config maps
|
||||||
return nil, errors.Wrap(err, "failed to get component configs")
|
if err := componentconfigs.FetchFromCluster(&initcfg.ClusterConfiguration, client); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get component configs")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this isn't a new controlplane instance (e.g. in case of kubeadm upgrades)
|
// if this isn't a new controlplane instance (e.g. in case of kubeadm upgrades)
|
||||||
|
@ -736,7 +736,7 @@ func TestGetInitConfigurationFromCluster(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.newControlPlane)
|
cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.newControlPlane, false)
|
||||||
if rt.expectedError != (err != nil) {
|
if rt.expectedError != (err != nil) {
|
||||||
t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
|
t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user