Skip to content

Commit f6ef6bf

Browse files
committed
feat: reject Kubernetes < 1.34 for AzureContainerLinux
ACL is only supported on AKS 1.34+ (per the AKS ACL support matrix). Add a check in the nodeclass validation reconciler that sets ValidationSucceeded=False with reason IncompatibleKubernetesVersion and a specific message when a nodeclass with imageFamily=AzureContainerLinux is reconciled against a cluster on an older Kubernetes minor. The check runs after the KubernetesVersionReconciler has populated Status.KubernetesVersion. If the version is not yet ready, the reconciler silently skips the check and will re-run once it is. Drop the equivalent SKU-level useAzureContainerLinux() gate in isInstanceTypeSupportedByAzureContainerLinux (and its now-unused blang/semver import) since the reconciler check surfaces the condition with a clear message, whereas the SKU filter would have silently eliminated every SKU.
1 parent ee39c06 commit f6ef6bf

3 files changed

Lines changed: 50 additions & 13 deletions

File tree

pkg/controllers/nodeclass/status/validation.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package status
1919
import (
2020
"context"
2121
"fmt"
22+
"strings"
2223
"time"
2324

2425
sdkerrors "github.com/Azure/azure-sdk-for-go-extensions/pkg/errors"
@@ -27,6 +28,7 @@ import (
2728
"github.com/Azure/karpenter-provider-azure/pkg/consts"
2829
"github.com/Azure/karpenter-provider-azure/pkg/operator/options"
2930
"github.com/Azure/karpenter-provider-azure/pkg/providers/azclient/azapi"
31+
"github.com/blang/semver/v4"
3032
"github.com/samber/lo"
3133
"sigs.k8s.io/controller-runtime/pkg/log"
3234
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -35,6 +37,9 @@ import (
3537
const (
3638
DiskEncryptionSetRBACMissing = "DiskEncryptionSetRBACMissing"
3739
IncompatibleProvisionMode = "IncompatibleProvisionMode"
40+
IncompatibleKubernetesVersion = "IncompatibleKubernetesVersion"
41+
// AzureContainerLinuxMinKubernetesMinor is the minimum Kubernetes minor version supporting AzureContainerLinux.
42+
AzureContainerLinuxMinKubernetesMinor uint64 = 34
3843
// TODO: May want to rethink how we handle successful validation + potential for RBAC removal.
3944
// See this PR comment for considerations:
4045
// https://github.com/Azure/karpenter-provider-azure/pull/1372#discussion_r2795367386
@@ -78,6 +83,22 @@ func (r *ValidationReconciler) Reconcile(ctx context.Context, nodeClass *v1beta1
7883
return reconcile.Result{}, nil
7984
}
8085

86+
// ACL requires Kubernetes 1.34+ (per AKS ACL support matrix).
87+
if lo.FromPtr(nodeClass.Spec.ImageFamily) == v1beta1.AzureContainerLinuxImageFamily {
88+
k8sVersion, err := nodeClass.GetKubernetesVersion()
89+
if err == nil && k8sVersion != "" {
90+
parsed, parseErr := semver.ParseTolerant(strings.TrimPrefix(k8sVersion, "v"))
91+
if parseErr == nil && parsed.LT(semver.Version{Major: 1, Minor: AzureContainerLinuxMinKubernetesMinor}) {
92+
nodeClass.StatusConditions().SetFalse(
93+
v1beta1.ConditionTypeValidationSucceeded,
94+
IncompatibleKubernetesVersion,
95+
fmt.Sprintf("AzureContainerLinux requires Kubernetes 1.%d or later, cluster is at %s", AzureContainerLinuxMinKubernetesMinor, k8sVersion),
96+
)
97+
return reconcile.Result{}, nil
98+
}
99+
}
100+
}
101+
81102
// Check BYOK RBAC if DES ID is configured
82103
if r.parsedDiskEncryptionSetID != nil {
83104
logger.V(1).Info("validating Disk Encryption Set RBAC")

pkg/controllers/nodeclass/status/validation_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,35 @@ var _ = Describe("Validation Reconciler", func() {
107107
)
108108
})
109109

110+
Context("Kubernetes version compatibility", func() {
111+
DescribeTable("validates AzureContainerLinux against cluster Kubernetes version",
112+
func(imageFamily, k8sVersion string, valid bool) {
113+
nodeClass.Spec.ImageFamily = lo.ToPtr(imageFamily)
114+
nodeClass.Status.KubernetesVersion = lo.ToPtr(k8sVersion)
115+
nodeClass.StatusConditions().SetTrue(v1beta1.ConditionTypeKubernetesVersionReady)
116+
result, err := reconciler.Reconcile(ctx, nodeClass)
117+
Expect(err).ToNot(HaveOccurred())
118+
119+
condition := nodeClass.StatusConditions().Get(v1beta1.ConditionTypeValidationSucceeded)
120+
if valid {
121+
Expect(condition.IsTrue()).To(BeTrue())
122+
Expect(result.RequeueAfter).To(Equal(status.ValidationSuccessRequeueInterval))
123+
} else {
124+
Expect(condition.IsFalse()).To(BeTrue())
125+
Expect(condition.Reason).To(Equal(status.IncompatibleKubernetesVersion))
126+
Expect(condition.Message).To(ContainSubstring("requires Kubernetes 1.34"))
127+
Expect(result).To(Equal(reconcile.Result{}))
128+
}
129+
},
130+
Entry("ACL on 1.33", v1beta1.AzureContainerLinuxImageFamily, "1.33.5", false),
131+
Entry("ACL on 1.30", v1beta1.AzureContainerLinuxImageFamily, "1.30.0", false),
132+
Entry("ACL on 1.34", v1beta1.AzureContainerLinuxImageFamily, "1.34.0", true),
133+
Entry("ACL on 1.35", v1beta1.AzureContainerLinuxImageFamily, "1.35.2", true),
134+
Entry("Ubuntu on 1.30", v1beta1.UbuntuImageFamily, "1.30.0", true),
135+
Entry("Azure Linux on 1.30", v1beta1.AzureLinuxImageFamily, "1.30.0", true),
136+
)
137+
})
138+
110139
// All LocalDNS validations are now handled declaratively by CEL and kubebuilder markers.
111140
// The ValidationReconciler is a skeleton for future runtime validations that cannot be
112141
// expressed in the CRD schema (e.g., external API calls, cross-resource checks, etc.).

pkg/providers/instancetype/instancetypes.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"github.com/mitchellh/hashstructure/v2"
2828
"github.com/samber/lo"
2929

30-
"github.com/blang/semver/v4"
3130
corev1 "k8s.io/api/core/v1"
3231
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
3332

@@ -386,10 +385,6 @@ func (p *DefaultProvider) isInstanceTypeSupportedByAzureContainerLinux(sku *skew
386385
return true
387386
}
388387

389-
if !useAzureContainerLinux(params.KubernetesVersion) {
390-
return false
391-
}
392-
393388
if !supportsTrustedLaunch(sku) {
394389
return false
395390
}
@@ -445,14 +440,6 @@ func hasNVMeDisk(sku *skewer.SKU) bool {
445440
return err == nil && nvmeMiB > 0
446441
}
447442

448-
func useAzureContainerLinux(kubernetesVersion string) bool {
449-
version, err := semver.ParseTolerant(strings.TrimPrefix(kubernetesVersion, "v"))
450-
if err != nil {
451-
return false
452-
}
453-
return version.GE(semver.Version{Major: 1, Minor: 34})
454-
}
455-
456443
func (p *DefaultProvider) isInstanceTypeSupportedByEncryptionAtHost(sku *skewer.SKU, params *instanceTypeParameters) bool {
457444
// If EncryptionAtHost is not enabled in the nodeclass, all instance types are supported
458445
if !params.EncryptionAtHost {

0 commit comments

Comments
 (0)