Skip to content

Commit a463b25

Browse files
authored
Merge pull request kubernetes#91980 from rosti/kubeadm-cc-manual-upgrade
kubeadm upgrade: Allow supplying hand migrated component configs
2 parents 0072d8a + 1d2d15e commit a463b25

File tree

15 files changed

+306
-146
lines changed

15 files changed

+306
-146
lines changed

cmd/kubeadm/app/cmd/alpha/certs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ func getInternalCfg(cfgPath string, kubeconfigPath string, cfg kubeadmapiv1beta2
254254
if cfgPath == "" {
255255
client, err := kubeconfigutil.ClientSetFromFile(kubeconfigPath)
256256
if err == nil {
257-
internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, out, logPrefix, false)
257+
internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, out, logPrefix, false, false)
258258
if err == nil {
259259
fmt.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output
260260
return internalcfg, nil

cmd/kubeadm/app/cmd/join.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ func fetchInitConfiguration(tlsBootstrapCfg *clientcmdapi.Config) (*kubeadmapi.I
545545
}
546546

547547
// Fetches the init configuration
548-
initConfiguration, err := configutil.FetchInitConfigurationFromCluster(tlsClient, os.Stdout, "preflight", true)
548+
initConfiguration, err := configutil.FetchInitConfigurationFromCluster(tlsClient, os.Stdout, "preflight", true, false)
549549
if err != nil {
550550
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
551551
}

cmd/kubeadm/app/cmd/reset.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out i
9494
client, err := getClientset(options.kubeconfigPath, false)
9595
if err == nil {
9696
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", options.kubeconfigPath)
97-
cfg, err = configutil.FetchInitConfigurationFromCluster(client, out, "reset", false)
97+
cfg, err = configutil.FetchInitConfigurationFromCluster(client, out, "reset", false, false)
9898
if err != nil {
9999
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
100100
}

cmd/kubeadm/app/cmd/upgrade/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ go_library(
2020
"//cmd/kubeadm/app/cmd/phases/upgrade/node:go_default_library",
2121
"//cmd/kubeadm/app/cmd/phases/workflow:go_default_library",
2222
"//cmd/kubeadm/app/cmd/util:go_default_library",
23+
"//cmd/kubeadm/app/componentconfigs:go_default_library",
2324
"//cmd/kubeadm/app/constants:go_default_library",
2425
"//cmd/kubeadm/app/features:go_default_library",
2526
"//cmd/kubeadm/app/phases/controlplane:go_default_library",

cmd/kubeadm/app/cmd/upgrade/apply.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,7 @@ func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
7373
DisableFlagsInUseLine: true,
7474
Short: "Upgrade your Kubernetes cluster to the specified version",
7575
RunE: func(cmd *cobra.Command, args []string) error {
76-
userVersion, err := getK8sVersionFromUserInput(flags.applyPlanFlags, args, true)
77-
if err != nil {
78-
return err
79-
}
80-
81-
return runApply(flags, userVersion)
76+
return runApply(flags, args)
8277
},
8378
}
8479

@@ -110,12 +105,12 @@ func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
110105
// - Creating the RBAC rules for the bootstrap tokens and the cluster-info ConfigMap
111106
// - Applying new kube-dns and kube-proxy manifests
112107
// - Uploads the newly used configuration to the cluster ConfigMap
113-
func runApply(flags *applyFlags, userVersion string) error {
108+
func runApply(flags *applyFlags, args []string) error {
114109

115110
// Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap)
116111
klog.V(1).Infoln("[upgrade/apply] verifying health of cluster")
117112
klog.V(1).Infoln("[upgrade/apply] retrieving configuration from cluster")
118-
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, flags.dryRun, userVersion)
113+
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, flags.dryRun, true)
119114
if err != nil {
120115
return err
121116
}

cmd/kubeadm/app/cmd/upgrade/common.go

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"bytes"
2222
"fmt"
2323
"io"
24+
"io/ioutil"
2425
"os"
2526
"strings"
2627
"time"
@@ -36,47 +37,91 @@ import (
3637
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
3738
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
3839
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
40+
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
3941
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
4042
"k8s.io/kubernetes/cmd/kubeadm/app/features"
4143
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
4244
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
45+
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
4346
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
4447
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
4548
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
4649
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
4750
)
4851

49-
func getK8sVersionFromUserInput(flags *applyPlanFlags, args []string, versionIsMandatory bool) (string, error) {
50-
var userVersion string
51-
52-
// If the version is specified in config file, pick up that value.
53-
if flags.cfgPath != "" {
54-
// Note that cfg isn't preserved here, it's just an one-off to populate userVersion based on --config
55-
cfg, err := configutil.LoadInitConfigurationFromFile(flags.cfgPath)
56-
if err != nil {
57-
return "", err
52+
// isKubeadmConfigPresent checks if a kubeadm config type is found in the provided document map
53+
func isKubeadmConfigPresent(docmap kubeadmapi.DocumentMap) bool {
54+
for gvk := range docmap {
55+
if gvk.Group == kubeadmapi.GroupName {
56+
return true
5857
}
58+
}
59+
return false
60+
}
5961

60-
userVersion = cfg.KubernetesVersion
62+
// loadConfig loads configuration from a file and/or the cluster. InitConfiguration, ClusterConfiguration and (optionally) component configs
63+
// 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
64+
// in it (API group "kubeadm.kubernetes.io" present), then the supplied file is treaded as a legacy reconfiguration style "--config" use and the
65+
// returned bool value is set to true (the only case to be done so).
66+
func loadConfig(cfgPath string, client clientset.Interface, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, bool, error) {
67+
// Used for info logs here
68+
const logPrefix = "upgrade/config"
69+
70+
// The usual case here is to not have a config file, but rather load the config from the cluster.
71+
// This is probably 90% of the time. So we handle it first.
72+
if cfgPath == "" {
73+
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, logPrefix, false, skipComponentConfigs)
74+
return cfg, false, err
6175
}
6276

63-
// the version arg is mandatory unless version is specified in the config file
64-
if versionIsMandatory && userVersion == "" {
65-
if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil {
66-
return "", err
67-
}
77+
// Otherwise, we have a config file. Let's load it.
78+
configBytes, err := ioutil.ReadFile(cfgPath)
79+
if err != nil {
80+
return nil, false, errors.Wrapf(err, "unable to load config from file %q", cfgPath)
6881
}
6982

70-
// If option was specified in both args and config file, args will overwrite the config file.
71-
if len(args) == 1 {
72-
userVersion = args[0]
83+
// Split the YAML documents in the file into a DocumentMap
84+
docmap, err := kubeadmutil.SplitYAMLDocuments(configBytes)
85+
if err != nil {
86+
return nil, false, err
87+
}
88+
89+
// If there are kubeadm types (API group kubeadm.kubernetes.io) present, we need to keep the existing behavior
90+
// here. Basically, we have to load all of the configs from the file and none from the cluster. Configs that are
91+
// missing from the file will be automatically regenerated by kubeadm even if they are present in the cluster.
92+
// The resulting configs overwrite the existing cluster ones at the end of a successful upgrade apply operation.
93+
if isKubeadmConfigPresent(docmap) {
94+
klog.Warning("WARNING: Usage of the --config flag with kubeadm config types for reconfiguring the cluster during upgrade is not recommended!")
95+
cfg, err := configutil.BytesToInitConfiguration(configBytes)
96+
return cfg, true, err
97+
}
98+
99+
// If no kubeadm config types are present, we assume that there are manually upgraded component configs in the file.
100+
// Hence, we load the kubeadm types from the cluster.
101+
initCfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, logPrefix, false, true)
102+
if err != nil {
103+
return nil, false, err
73104
}
74105

75-
return userVersion, nil
106+
// Stop here if the caller does not want us to load the component configs
107+
if !skipComponentConfigs {
108+
// Load the component configs with upgrades
109+
if err := componentconfigs.FetchFromClusterWithLocalOverwrites(&initCfg.ClusterConfiguration, client, docmap); err != nil {
110+
return nil, false, err
111+
}
112+
113+
// Now default and validate the configs
114+
componentconfigs.Default(&initCfg.ClusterConfiguration, &initCfg.LocalAPIEndpoint, &initCfg.NodeRegistration)
115+
if errs := componentconfigs.Validate(&initCfg.ClusterConfiguration); len(errs) != 0 {
116+
return nil, false, errs.ToAggregate()
117+
}
118+
}
119+
120+
return initCfg, false, nil
76121
}
77122

78123
// enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure
79-
func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion string) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, error) {
124+
func enforceRequirements(flags *applyPlanFlags, args []string, dryRun bool, upgradeApply bool) (clientset.Interface, upgrade.VersionGetter, *kubeadmapi.InitConfiguration, error) {
80125
client, err := getClient(flags.kubeConfigPath, dryRun)
81126
if err != nil {
82127
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
90135
// Fetch the configuration from a file or ConfigMap and validate it
91136
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
92137

93-
var cfg *kubeadmapi.InitConfiguration
94-
if flags.cfgPath != "" {
95-
klog.Warning("WARNING: Usage of the --config flag for reconfiguring the cluster during upgrade is not recommended!")
96-
cfg, err = configutil.LoadInitConfigurationFromFile(flags.cfgPath)
97-
} else {
98-
cfg, err = configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade/config", false)
99-
}
100-
138+
var newK8sVersion string
139+
cfg, legacyReconfigure, err := loadConfig(flags.cfgPath, client, !upgradeApply)
101140
if err != nil {
102141
if apierrors.IsNotFound(err) {
103142
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
111150
err = errors.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
112151
}
113152
return nil, nil, nil, errors.Wrap(err, "[upgrade/config] FATAL")
153+
} else if legacyReconfigure {
154+
// Set the newK8sVersion to the value in the ClusterConfiguration. This is done, so that users who use the --config option
155+
// to supply a new ClusterConfiguration don't have to specify the Kubernetes version twice,
156+
// if they don't want to upgrade but just change a setting.
157+
newK8sVersion = cfg.KubernetesVersion
114158
}
115159

116160
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, cfg.NodeRegistration.IgnorePreflightErrors)
@@ -131,8 +175,16 @@ func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion strin
131175
return nil, nil, nil, errors.Wrap(err, "[upgrade/health] FATAL")
132176
}
133177

134-
// If a new k8s version should be set, apply the change before printing the config
135-
if len(newK8sVersion) != 0 {
178+
// The version arg is mandatory, during upgrade apply, unless it's specified in the config file
179+
if upgradeApply && newK8sVersion == "" {
180+
if err := cmdutil.ValidateExactArgNumber(args, []string{"version"}); err != nil {
181+
return nil, nil, nil, err
182+
}
183+
}
184+
185+
// If option was specified in both args and config file, args will overwrite the config file.
186+
if len(args) == 1 {
187+
newK8sVersion = args[0]
136188
cfg.KubernetesVersion = newK8sVersion
137189
}
138190

cmd/kubeadm/app/cmd/upgrade/common_test.go

Lines changed: 1 addition & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -18,98 +18,11 @@ package upgrade
1818

1919
import (
2020
"bytes"
21-
"io/ioutil"
22-
"os"
2321
"testing"
2422

2523
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
26-
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
2724
)
2825

29-
func TestGetK8sVersionFromUserInput(t *testing.T) {
30-
currentVersion := "v" + constants.CurrentKubernetesVersion.String()
31-
validConfig := "apiVersion: kubeadm.k8s.io/v1beta2\n" +
32-
"kind: ClusterConfiguration\n" +
33-
"kubernetesVersion: " + currentVersion
34-
35-
var tcases = []struct {
36-
name string
37-
isVersionMandatory bool
38-
clusterConfig string
39-
args []string
40-
expectedErr bool
41-
expectedVersion string
42-
}{
43-
{
44-
name: "No config and version as an argument",
45-
isVersionMandatory: true,
46-
args: []string{"v1.13.1"},
47-
expectedVersion: "v1.13.1",
48-
},
49-
{
50-
name: "Neither config nor version specified",
51-
isVersionMandatory: true,
52-
expectedErr: true,
53-
},
54-
{
55-
name: "No config and empty version as an argument",
56-
isVersionMandatory: true,
57-
args: []string{""},
58-
expectedErr: true,
59-
},
60-
{
61-
name: "Valid config, but no version specified",
62-
isVersionMandatory: true,
63-
clusterConfig: validConfig,
64-
expectedVersion: currentVersion,
65-
},
66-
{
67-
name: "Valid config and different version specified",
68-
isVersionMandatory: true,
69-
clusterConfig: validConfig,
70-
args: []string{"v1.13.1"},
71-
expectedVersion: "v1.13.1",
72-
},
73-
{
74-
name: "Version is optional",
75-
},
76-
}
77-
for _, tt := range tcases {
78-
t.Run(tt.name, func(t *testing.T) {
79-
flags := &applyPlanFlags{}
80-
if len(tt.clusterConfig) > 0 {
81-
file, err := ioutil.TempFile("", "kubeadm-upgrade-common-test-*.yaml")
82-
if err != nil {
83-
t.Fatalf("Failed to create test config file: %+v", err)
84-
}
85-
86-
tmpFileName := file.Name()
87-
defer os.Remove(tmpFileName)
88-
89-
_, err = file.WriteString(tt.clusterConfig)
90-
file.Close()
91-
if err != nil {
92-
t.Fatalf("Failed to write test config file contents: %+v", err)
93-
}
94-
95-
flags.cfgPath = tmpFileName
96-
}
97-
98-
userVersion, err := getK8sVersionFromUserInput(flags, tt.args, tt.isVersionMandatory)
99-
100-
if err == nil && tt.expectedErr {
101-
t.Error("Expected error, but got success")
102-
}
103-
if err != nil && !tt.expectedErr {
104-
t.Errorf("Unexpected error: %+v", err)
105-
}
106-
if userVersion != tt.expectedVersion {
107-
t.Errorf("Expected %q, but got %q", tt.expectedVersion, userVersion)
108-
}
109-
})
110-
}
111-
}
112-
11326
func TestEnforceRequirements(t *testing.T) {
11427
tcases := []struct {
11528
name string
@@ -139,7 +52,7 @@ func TestEnforceRequirements(t *testing.T) {
13952
}
14053
for _, tt := range tcases {
14154
t.Run(tt.name, func(t *testing.T) {
142-
_, _, _, err := enforceRequirements(&tt.flags, tt.dryRun, tt.newK8sVersion)
55+
_, _, _, err := enforceRequirements(&tt.flags, nil, tt.dryRun, false)
14356

14457
if err == nil && tt.expectedErr {
14558
t.Error("Expected error, but got success")

cmd/kubeadm/app/cmd/upgrade/diff.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func runDiff(flags *diffFlags, args []string) error {
9191
if err != nil {
9292
return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)
9393
}
94-
cfg, err = configutil.FetchInitConfigurationFromCluster(client, flags.out, "upgrade/diff", false)
94+
cfg, err = configutil.FetchInitConfigurationFromCluster(client, flags.out, "upgrade/diff", false, false)
9595
}
9696
if err != nil {
9797
return err

cmd/kubeadm/app/cmd/upgrade/node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*node
141141
// Fetches the cluster configuration
142142
// NB in case of control-plane node, we are reading all the info for the node; in case of NOT control-plane node
143143
// (worker node), we are not reading local API address and the CRI socket from the node object
144-
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", !isControlPlaneNode)
144+
cfg, err := configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "upgrade", !isControlPlaneNode, false)
145145
if err != nil {
146146
return nil, errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
147147
}

cmd/kubeadm/app/cmd/upgrade/plan.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,7 @@ func NewCmdPlan(apf *applyPlanFlags) *cobra.Command {
4848
Use: "plan [version] [flags]",
4949
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",
5050
RunE: func(_ *cobra.Command, args []string) error {
51-
userVersion, err := getK8sVersionFromUserInput(flags.applyPlanFlags, args, false)
52-
if err != nil {
53-
return err
54-
}
55-
56-
return runPlan(flags, userVersion)
51+
return runPlan(flags, args)
5752
},
5853
}
5954

@@ -63,11 +58,11 @@ func NewCmdPlan(apf *applyPlanFlags) *cobra.Command {
6358
}
6459

6560
// runPlan takes care of outputting available versions to upgrade to for the user
66-
func runPlan(flags *planFlags, userVersion string) error {
61+
func runPlan(flags *planFlags, args []string) error {
6762
// Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning.
6863
klog.V(1).Infoln("[upgrade/plan] verifying health of cluster")
6964
klog.V(1).Infoln("[upgrade/plan] retrieving configuration from cluster")
70-
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, false, userVersion)
65+
client, versionGetter, cfg, err := enforceRequirements(flags.applyPlanFlags, args, false, false)
7166
if err != nil {
7267
return err
7368
}

0 commit comments

Comments
 (0)