diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index e26b70d34a0..8062dc7481a 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -248,6 +248,7 @@ var ( lifecycle.OutOfMemory, lifecycle.OutOfEphemeralStorage, lifecycle.OutOfPods, + lifecycle.PodLevelResourcesNotAdmittedReason, tainttoleration.ErrReasonNotMatch, eviction.Reason, sysctl.ForbiddenReason, @@ -1011,6 +1012,8 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, handlers = append(handlers, lifecycle.NewAppArmorAdmitHandler(klet.appArmorValidator)) } + handlers = append(handlers, lifecycle.NewPodFeaturesAdmitHandler()) + leaseDuration := time.Duration(kubeCfg.NodeLeaseDurationSeconds) * time.Second renewInterval := time.Duration(float64(leaseDuration) * nodeLeaseRenewIntervalFraction) klet.nodeLeaseController = lease.NewController( diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 79d2de3ef0c..9652e98c8fe 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -3373,6 +3373,15 @@ func TestRecordAdmissionRejection(t *testing.T) { kubelet_admission_rejections_total{reason="OutOfExtendedResources"} 1 `, }, + { + name: "PodLevelResources", + reason: lifecycle.PodLevelResourcesNotAdmittedReason, + wants: ` + # HELP kubelet_admission_rejections_total [ALPHA] Cumulative number pod admission rejections by the Kubelet. + # TYPE kubelet_admission_rejections_total counter + kubelet_admission_rejections_total{reason="PodLevelResourcesNotSupported"} 1 + `, + }, { name: "OtherReason", reason: "OtherReason", diff --git a/pkg/kubelet/lifecycle/features_linux.go b/pkg/kubelet/lifecycle/features_linux.go new file mode 100644 index 00000000000..137d652d1a8 --- /dev/null +++ b/pkg/kubelet/lifecycle/features_linux.go @@ -0,0 +1,39 @@ +//go:build linux +// +build linux + +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package lifecycle + +import ( + v1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-helpers/resource" + "k8s.io/kubernetes/pkg/features" +) + +func isPodLevelResourcesSupported(pod *v1.Pod) PodAdmitResult { + podLevelResourcesEnabled := utilfeature.DefaultFeatureGate.Enabled(features.PodLevelResources) + if resource.IsPodLevelResourcesSet(pod) && !podLevelResourcesEnabled { + return PodAdmitResult{ + Admit: false, + Reason: PodLevelResourcesNotAdmittedReason, + Message: "PodLevelResources feature gate is disabled", + } + } + return PodAdmitResult{Admit: true} +} diff --git a/pkg/kubelet/lifecycle/features_unsupported.go b/pkg/kubelet/lifecycle/features_unsupported.go new file mode 100644 index 00000000000..3ece076ba38 --- /dev/null +++ b/pkg/kubelet/lifecycle/features_unsupported.go @@ -0,0 +1,36 @@ +//go:build !linux && !windows +// +build !linux,!windows + +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package lifecycle + +import ( + v1 "k8s.io/api/core/v1" + "k8s.io/component-helpers/resource" +) + +func isPodLevelResourcesSupported(pod *v1.Pod) PodAdmitResult { + if resource.IsPodLevelResourcesSet(pod) { + return PodAdmitResult{ + Admit: false, + Reason: PodLevelResourcesNotAdmittedReason, + Message: "pod-level resources are not supported on the node", + } + } + return PodAdmitResult{Admit: true} +} diff --git a/pkg/kubelet/lifecycle/features_windows.go b/pkg/kubelet/lifecycle/features_windows.go new file mode 100644 index 00000000000..1db1b527b3b --- /dev/null +++ b/pkg/kubelet/lifecycle/features_windows.go @@ -0,0 +1,36 @@ +//go:build windows +// +build windows + +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package lifecycle + +import ( + v1 "k8s.io/api/core/v1" + "k8s.io/component-helpers/resource" +) + +func isPodLevelResourcesSupported(pod *v1.Pod) PodAdmitResult { + if resource.IsPodLevelResourcesSet(pod) { + return PodAdmitResult{ + Admit: false, + Reason: PodLevelResourcesNotAdmittedReason, + Message: "pod-level resources are not supported on Windows", + } + } + return PodAdmitResult{Admit: true} +} diff --git a/pkg/kubelet/lifecycle/handlers.go b/pkg/kubelet/lifecycle/handlers.go index b0c22a78db2..5aa2523fd9c 100644 --- a/pkg/kubelet/lifecycle/handlers.go +++ b/pkg/kubelet/lifecycle/handlers.go @@ -45,7 +45,8 @@ import ( const ( maxRespBodyLength = 10 * 1 << 10 // 10KB - AppArmorNotAdmittedReason = "AppArmor" + AppArmorNotAdmittedReason = "AppArmor" + PodLevelResourcesNotAdmittedReason = "PodLevelResourcesNotSupported" ) type handlerRunner struct { @@ -241,3 +242,15 @@ func isHTTPResponseError(err error) bool { } return strings.Contains(urlErr.Err.Error(), "server gave HTTP response to HTTPS client") } + +// NewPodFeaturesAdmitHandler returns a PodAdmitHandler which is used to evaluate +// if a pod can be admitted from the perspective of pod features compatibility. +func NewPodFeaturesAdmitHandler() PodAdmitHandler { + return &podFeaturesAdmitHandler{} +} + +type podFeaturesAdmitHandler struct{} + +func (h *podFeaturesAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult { + return isPodLevelResourcesSupported(attrs.Pod) +}