diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container.go b/pkg/kubelet/kuberuntime/kuberuntime_container.go index a1d9453f8918d..c837212d98c36 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container.go @@ -56,6 +56,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/events" + "k8s.io/kubernetes/pkg/kubelet/managed" proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/kubelet/util/format" @@ -625,6 +626,8 @@ func (m *kubeGenericRuntimeManager) getPodContainerStatuses(ctx context.Context, return nil, nil, err } + isManagedPod := managed.IsManagedPodFromRuntimeService(ctx, m.runtimeService, activePodSandboxID) + statuses := []*kubecontainer.Status{} activeContainerStatuses := []*kubecontainer.Status{} // TODO: optimization: set maximum number of containers per container name to examine. @@ -647,6 +650,9 @@ func (m *kubeGenericRuntimeManager) getPodContainerStatuses(ctx context.Context, return nil, nil, remote.ErrContainerStatusNil } cStatus := m.convertToKubeContainerStatus(ctx, status) + if isManagedPod && cStatus.Resources != nil { // Clear CPU resources for managed pods (workload-pinned) + cStatus.Resources.CPURequest, cStatus.Resources.CPULimit = nil, nil + } statuses = append(statuses, cStatus) if c.PodSandboxId == activePodSandboxID { activeContainerStatuses = append(activeContainerStatuses, cStatus) diff --git a/pkg/kubelet/managed/managed.go b/pkg/kubelet/managed/managed.go index d9266e440f0d8..bd78adeee1385 100644 --- a/pkg/kubelet/managed/managed.go +++ b/pkg/kubelet/managed/managed.go @@ -17,6 +17,7 @@ limitations under the License. package managed import ( + "context" "encoding/json" "fmt" "os" @@ -24,6 +25,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" ) var ( @@ -88,6 +90,40 @@ func IsPodManaged(pod *v1.Pod) (bool, string, string) { return false, "", "" } +// podSandboxStatusGetter is an interface for getting pod sandbox status +type podSandboxStatusGetter interface { + PodSandboxStatus(ctx context.Context, podSandboxID string, verbose bool) (*runtimeapi.PodSandboxStatusResponse, error) +} + +// IsPodSandboxManagedPod checks if a pod sandbox belongs to a managed pod +// by looking for workload pinning annotations. +func IsPodSandboxManagedPod(sandboxAnnotations map[string]string) bool { + if sandboxAnnotations == nil { + return false + } + for annotation := range sandboxAnnotations { + if strings.HasPrefix(annotation, WorkloadsAnnotationPrefix) { + return true + } + } + return false +} + +// IsManagedPodFromRuntimeService checks if a pod is managed by fetching the pod sandbox +// status and checking for workload pinning annotations. +func IsManagedPodFromRuntimeService(ctx context.Context, runtimeService podSandboxStatusGetter, podSandboxID string) bool { + if podSandboxID == "" { + return false + } + + sandboxResp, err := runtimeService.PodSandboxStatus(ctx, podSandboxID, false) + if err != nil || sandboxResp == nil || sandboxResp.GetStatus() == nil { + return false + } + + return IsPodSandboxManagedPod(sandboxResp.GetStatus().Annotations) +} + // ModifyStaticPodForPinnedManagement will modify a pod for pod management func ModifyStaticPodForPinnedManagement(pod *v1.Pod) (*v1.Pod, string, error) { pod = pod.DeepCopy() diff --git a/pkg/kubelet/managed/managed_test.go b/pkg/kubelet/managed/managed_test.go index 3a2f2b3a18f27..5e284036f0f1f 100644 --- a/pkg/kubelet/managed/managed_test.go +++ b/pkg/kubelet/managed/managed_test.go @@ -1,12 +1,14 @@ package managed import ( + "context" "fmt" "testing" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" ) func TestModifyStaticPodForPinnedManagementErrorStates(t *testing.T) { @@ -1007,3 +1009,173 @@ func createPod(annotations map[string]string, initContainer, container *v1.Conta return pod } + +func TestIsPodSandboxManagedPod(t *testing.T) { + testCases := []struct { + name string + annotations map[string]string + expected bool + }{ + { + name: "nil annotations", + annotations: nil, + expected: false, + }, + { + name: "empty annotations", + annotations: map[string]string{}, + expected: false, + }, + { + name: "regular pod annotations without workload annotations", + annotations: map[string]string{ + "some.annotation": "value", + "another.annotation": "value2", + "io.kubernetes.pod.id": "12345", + }, + expected: false, + }, + { + name: "managed pod with workload management annotation", + annotations: map[string]string{ + "some.annotation": "value", + WorkloadsAnnotationPrefix + "management": `{"effect": "PreferredDuringScheduling"}`, + }, + expected: true, + }, + { + name: "managed pod with workload throttle annotation", + annotations: map[string]string{ + WorkloadsAnnotationPrefix + "throttle": `{"effect": "PreferredDuringScheduling"}`, + }, + expected: true, + }, + { + name: "pod with annotation similar to workload prefix but not matching", + annotations: map[string]string{ + "target.workload.openshift.io": "value", // missing trailing slash + "some.other.annotation": "value2", + }, + expected: false, + }, + { + name: "managed pod with multiple annotations", + annotations: map[string]string{ + "io.kubernetes.pod.name": "test-pod", + "io.kubernetes.pod.namespace": "default", + WorkloadsAnnotationPrefix + "management": `{"effect": "PreferredDuringScheduling"}`, + "custom.annotation": "custom-value", + }, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := IsPodSandboxManagedPod(tc.annotations) + if result != tc.expected { + t.Errorf("IsPodSandboxManagedPod() = %v, expected %v for annotations: %v", + result, tc.expected, tc.annotations) + } + }) + } +} + +// mockRuntimeService is a simple mock for testing +type mockRuntimeService struct { + sandboxStatus *runtimeapi.PodSandboxStatusResponse + err error +} + +func (m *mockRuntimeService) PodSandboxStatus(ctx context.Context, podSandboxID string, verbose bool) (*runtimeapi.PodSandboxStatusResponse, error) { + return m.sandboxStatus, m.err +} + +func TestIsManagedPodFromRuntimeService(t *testing.T) { + ctx := context.Background() + + testCases := []struct { + name string + podSandboxID string + sandboxStatus *runtimeapi.PodSandboxStatusResponse + err error + expected bool + }{ + { + name: "empty sandbox ID", + podSandboxID: "", + expected: false, + }, + { + name: "runtime service returns error", + podSandboxID: "sandbox123", + err: fmt.Errorf("runtime error"), + expected: false, + }, + { + name: "nil sandbox response", + podSandboxID: "sandbox123", + sandboxStatus: nil, + expected: false, + }, + { + name: "nil status in response", + podSandboxID: "sandbox123", + sandboxStatus: &runtimeapi.PodSandboxStatusResponse{ + Status: nil, + }, + expected: false, + }, + { + name: "regular pod without workload annotations", + podSandboxID: "sandbox123", + sandboxStatus: &runtimeapi.PodSandboxStatusResponse{ + Status: &runtimeapi.PodSandboxStatus{ + Annotations: map[string]string{ + "some.annotation": "value", + }, + }, + }, + expected: false, + }, + { + name: "managed pod with workload annotations", + podSandboxID: "sandbox123", + sandboxStatus: &runtimeapi.PodSandboxStatusResponse{ + Status: &runtimeapi.PodSandboxStatus{ + Annotations: map[string]string{ + "some.annotation": "value", + WorkloadsAnnotationPrefix + "management": `{"effect": "PreferredDuringScheduling"}`, + }, + }, + }, + expected: true, + }, + { + name: "managed pod with empty annotations but has workload prefix", + podSandboxID: "sandbox123", + sandboxStatus: &runtimeapi.PodSandboxStatusResponse{ + Status: &runtimeapi.PodSandboxStatus{ + Annotations: map[string]string{ + WorkloadsAnnotationPrefix + "throttle": "", + }, + }, + }, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockService := &mockRuntimeService{ + sandboxStatus: tc.sandboxStatus, + err: tc.err, + } + + result := IsManagedPodFromRuntimeService(ctx, mockService, tc.podSandboxID) + if result != tc.expected { + t.Errorf("IsManagedPodFromRuntimeService() = %v, expected %v", result, tc.expected) + } + }) + } +}