Skip to content

Commit 6dfbd46

Browse files
authored
Merge pull request #16932 from rsafonseca/version_skew
Fix version upgrade kubelet support
2 parents 1ed2c8e + e4a0ef6 commit 6dfbd46

File tree

6 files changed

+104
-19
lines changed

6 files changed

+104
-19
lines changed

cmd/kops/update_cluster.go

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ import (
2727

2828
"github.com/spf13/cobra"
2929
"github.com/spf13/viper"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3031
"k8s.io/apimachinery/pkg/util/sets"
32+
"k8s.io/client-go/kubernetes"
3133
"k8s.io/client-go/tools/clientcmd"
3234
"k8s.io/klog/v2"
3335
"k8s.io/kops/cmd/kops/util"
3436
"k8s.io/kops/pkg/apis/kops"
37+
apisutil "k8s.io/kops/pkg/apis/kops/util"
3538
"k8s.io/kops/pkg/assets"
3639
"k8s.io/kops/pkg/commands/commandutils"
3740
"k8s.io/kops/pkg/kubeconfig"
@@ -67,6 +70,10 @@ type UpdateClusterOptions struct {
6770
SSHPublicKey string
6871
RunTasksOptions fi.RunTasksOptions
6972
AllowKopsDowngrade bool
73+
// Bypasses kubelet vs control plane version skew checks,
74+
// which by default prevent non-control plane instancegroups
75+
// from being updated to a version greater than the control plane
76+
IgnoreKubeletVersionSkew bool
7077
// GetAssets is whether this is invoked from the CmdGetAssets.
7178
GetAssets bool
7279

@@ -103,6 +110,8 @@ func (o *UpdateClusterOptions) InitDefaults() {
103110
o.Target = "direct"
104111
o.SSHPublicKey = ""
105112
o.OutDir = ""
113+
// By default we enforce the version skew between control plane and worker nodes
114+
o.IgnoreKubeletVersionSkew = false
106115

107116
// By default we export a kubecfg, but it doesn't have a static/eternal credential in it any more.
108117
o.CreateKubecfg = true
@@ -163,6 +172,7 @@ func NewCmdUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command {
163172
cmd.RegisterFlagCompletionFunc("lifecycle-overrides", completeLifecycleOverrides)
164173

165174
cmd.Flags().BoolVar(&options.Prune, "prune", options.Prune, "Delete old revisions of cloud resources that were needed during an upgrade")
175+
cmd.Flags().BoolVar(&options.IgnoreKubeletVersionSkew, "ignore-kubelet-version-skew", options.IgnoreKubeletVersionSkew, "Setting this to true will force updating the kubernetes version on all instance groups, regardles of which control plane version is running")
166176

167177
return cmd
168178
}
@@ -318,20 +328,30 @@ func RunUpdateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *Up
318328
return nil, err
319329
}
320330

331+
minControlPlaneRunningVersion := cluster.Spec.KubernetesVersion
332+
if !c.IgnoreKubeletVersionSkew {
333+
minControlPlaneRunningVersion, err = checkControlPlaneRunningVersion(ctx, cluster.ObjectMeta.Name, minControlPlaneRunningVersion)
334+
if err != nil {
335+
klog.Warningf("error checking control plane running version, assuming no k8s upgrade in progress: %v", err)
336+
} else {
337+
klog.V(2).Infof("successfully checked control plane running version: %v", minControlPlaneRunningVersion)
338+
}
339+
}
321340
applyCmd := &cloudup.ApplyClusterCmd{
322-
Cloud: cloud,
323-
Clientset: clientset,
324-
Cluster: cluster,
325-
DryRun: isDryrun,
326-
AllowKopsDowngrade: c.AllowKopsDowngrade,
327-
RunTasksOptions: &c.RunTasksOptions,
328-
OutDir: c.OutDir,
329-
InstanceGroupFilter: predicates.AllOf(instanceGroupFilters...),
330-
Phase: phase,
331-
TargetName: targetName,
332-
LifecycleOverrides: lifecycleOverrideMap,
333-
GetAssets: c.GetAssets,
334-
DeletionProcessing: deletionProcessing,
341+
Cloud: cloud,
342+
Clientset: clientset,
343+
Cluster: cluster,
344+
DryRun: isDryrun,
345+
AllowKopsDowngrade: c.AllowKopsDowngrade,
346+
RunTasksOptions: &c.RunTasksOptions,
347+
OutDir: c.OutDir,
348+
InstanceGroupFilter: predicates.AllOf(instanceGroupFilters...),
349+
Phase: phase,
350+
TargetName: targetName,
351+
LifecycleOverrides: lifecycleOverrideMap,
352+
GetAssets: c.GetAssets,
353+
DeletionProcessing: deletionProcessing,
354+
ControlPlaneRunningVersion: minControlPlaneRunningVersion,
335355
}
336356

337357
applyResults, err := applyCmd.Run(ctx)
@@ -575,3 +595,38 @@ func matchInstanceGroupRoles(roles []string) predicates.Predicate[*kops.Instance
575595
return false
576596
}
577597
}
598+
599+
// checkControlPlaneRunningVersion returns the minimum control plane running version
600+
func checkControlPlaneRunningVersion(ctx context.Context, clusterName string, version string) (string, error) {
601+
configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
602+
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
603+
configLoadingRules,
604+
&clientcmd.ConfigOverrides{CurrentContext: clusterName}).ClientConfig()
605+
if err != nil {
606+
return version, fmt.Errorf("cannot load kubecfg settings for %q: %v", clusterName, err)
607+
}
608+
609+
k8sClient, err := kubernetes.NewForConfig(config)
610+
if err != nil {
611+
return version, fmt.Errorf("cannot build kubernetes api client for %q: %v", clusterName, err)
612+
}
613+
614+
parsedVersion, err := apisutil.ParseKubernetesVersion(version)
615+
if err != nil {
616+
return version, fmt.Errorf("cannot parse kubernetes version %q: %v", clusterName, err)
617+
}
618+
nodeList, err := k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{
619+
LabelSelector: "node-role.kubernetes.io/control-plane",
620+
})
621+
if err != nil {
622+
return version, fmt.Errorf("cannot list nodes in cluster %q: %v", clusterName, err)
623+
}
624+
for _, node := range nodeList.Items {
625+
if apisutil.IsKubernetesGTE(node.Status.NodeInfo.KubeletVersion, *parsedVersion) {
626+
version = node.Status.NodeInfo.KubeletVersion
627+
parsedVersion, _ = apisutil.ParseKubernetesVersion(version)
628+
}
629+
630+
}
631+
return strings.TrimPrefix(version, "v"), nil
632+
}

docs/cli/kops_update_cluster.md

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apis/kops/model/instance_group.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,20 @@ type InstanceGroup interface {
3434
// RawClusterSpec returns the cluster spec for the instance group.
3535
// If possible, prefer abstracted methods over accessing this data directly.
3636
RawClusterSpec() *kops.ClusterSpec
37+
38+
// ForceKubernetesVersion overrides the Kubernetes version for this instance group.
39+
// (The default is to use the cluster-wide Kubernetes version, but this allows
40+
// us to override it for the nodes to respect the node skew policy.)
41+
ForceKubernetesVersion(version string) error
3742
}
3843

3944
// ForInstanceGroup creates an InstanceGroup model for the given cluster and instance group.
4045
func ForInstanceGroup(cluster *kops.Cluster, ig *kops.InstanceGroup) (InstanceGroup, error) {
41-
kubernetesVersionString := cluster.Spec.KubernetesVersion
42-
kubernetesVersion, err := ParseKubernetesVersion(kubernetesVersionString)
43-
if err != nil {
44-
return nil, fmt.Errorf("error parsing Kubernetes version %q: %v", kubernetesVersionString, err)
46+
m := &instanceGroupModel{cluster: cluster, ig: ig}
47+
if err := m.ForceKubernetesVersion(cluster.Spec.KubernetesVersion); err != nil {
48+
return nil, err
4549
}
46-
47-
return &instanceGroupModel{cluster: cluster, ig: ig, kubernetesVersion: kubernetesVersion}, nil
50+
return m, nil
4851
}
4952

5053
// instanceGroupModel is a concrete implementation of InstanceGroup.
@@ -67,3 +70,12 @@ func (m *instanceGroupModel) GetCloudProvider() kops.CloudProviderID {
6770
func (m *instanceGroupModel) RawClusterSpec() *kops.ClusterSpec {
6871
return &m.cluster.Spec
6972
}
73+
74+
func (m *instanceGroupModel) ForceKubernetesVersion(kubernetesVersionString string) error {
75+
kubernetesVersion, err := ParseKubernetesVersion(kubernetesVersionString)
76+
if err != nil {
77+
return fmt.Errorf("error parsing Kubernetes version %q: %v", kubernetesVersionString, err)
78+
}
79+
m.kubernetesVersion = kubernetesVersion
80+
return nil
81+
}

pkg/assets/builder.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ type AssetBuilder struct {
5151
// KubernetesVersion is the version of kubernetes we are installing
5252
KubernetesVersion semver.Version
5353

54+
// KubeletSupportedVersion is the max version of kubelet that we are currently allowed to run on worker nodes.
55+
// This is used to avoid violating the kubelet supported version skew policy,
56+
// (we are not allowed to run a newer kubelet on a worker node than the control plane)
57+
KubeletSupportedVersion string
58+
5459
// StaticManifests records manifests used by nodeup:
5560
// * e.g. sidecar manifests for static pods run by kubelet
5661
StaticManifests []*StaticManifest

pkg/nodemodel/nodeupconfigbuilder.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,12 @@ func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, wellKnownAddre
224224
return nil, nil, fmt.Errorf("building instance group model: %w", err)
225225
}
226226

227+
if !hasAPIServer && n.assetBuilder.KubeletSupportedVersion != "" {
228+
if err := igModel.ForceKubernetesVersion(n.assetBuilder.KubeletSupportedVersion); err != nil {
229+
return nil, nil, err
230+
}
231+
}
232+
227233
kubernetesAssets, err := BuildKubernetesFileAssets(igModel, n.assetBuilder)
228234
if err != nil {
229235
return nil, nil, err

upup/pkg/fi/cloudup/apply_cluster.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ type ApplyClusterCmd struct {
137137

138138
// InstanceGroupFilter is a predicate that restricts which instance groups we will update.
139139
InstanceGroupFilter predicates.Predicate[*kops.InstanceGroup]
140+
141+
// The current oldest version of control plane nodes, defaults to version defined in cluster spec if IgnoreVersionSkew was set
142+
ControlPlaneRunningVersion string
140143
}
141144

142145
// ApplyResults holds information about an ApplyClusterCmd operation.
@@ -239,6 +242,9 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) (*ApplyResults, error) {
239242
}
240243

241244
assetBuilder := assets.NewAssetBuilder(c.Clientset.VFSContext(), c.Cluster.Spec.Assets, c.Cluster.Spec.KubernetesVersion, c.GetAssets)
245+
if len(c.ControlPlaneRunningVersion) > 0 && c.ControlPlaneRunningVersion != c.Cluster.Spec.KubernetesVersion {
246+
assetBuilder.KubeletSupportedVersion = c.ControlPlaneRunningVersion
247+
}
242248
err = c.upgradeSpecs(ctx, assetBuilder)
243249
if err != nil {
244250
return nil, err

0 commit comments

Comments
 (0)