aws / aws/amazon-network-policy-controller-k8s

Filter headless Services from the informer cache via spec.clusterIP field selector

Open
#258 0 comments 0 reactions 1 assignee Claimed by @nakshah87 View on GitHub
enhancement good first issue
Dominant language
Go
Stars
54
Forks
28
PR merge metrics
No merged PRs in 30d

Description

## What

Add a server-side field selector to the Service informer so headless Services are never listed, watched, or cached:

```go
&corev1.Service{}: {
Transform: k8s.StripDownServiceTransformFunc,
Field: fields.OneTermNotEqualSelector("spec.clusterIP", corev1.ClusterIPNone),
},
```

This is the same pattern the controller already uses for Pods (`podCacheFieldSelector` in `pkg/config/runtime_config.go`), and the same optimization kubelet adopted in Kubernetes 1.31 to stop watching headless Services (kubernetes/kubernetes#122541, motivated by kubernetes/kubernetes#122394).

## Why

Every consumer of the Service cache already ignores headless Services, so caching them is pure overhead:

- `pkg/resolvers/endpoints.go` — `getMatchingServiceClusterIPs` skips headless Services (no ClusterIP to resolve).
- `pkg/resolvers/policies_for_service.go` — NetworkPolicy and ApplicationNetworkPolicy service-reference resolution early-returns on headless Services.
- `internal/eventhandlers/service.go` — headless Service create/update/delete events still fire the handler, and `GetReferredClusterPoliciesForService` enqueues **all** ClusterNetworkPolicies for reconcile even though headless Services can never contribute endpoints.

Filtering server-side eliminates:

1. Cache memory for headless Services (can be a large fraction of total Services in StatefulSet-heavy clusters).
2. List/watch bandwidth on the apiserver connection.
3. Iteration overhead in `getMatchingServiceClusterIPs`, which lists Services per egress-peer per namespace.
4. Spurious full-CNP reconciles triggered by headless Service churn.

`spec.clusterIP` is immutable, so Services do not transition in/out of the filtered set. For the ExternalName↔ClusterIP type-change edge case, the apiserver synthesizes Added/Deleted watch events, which the existing handlers already handle.

## Constraint

The `spec.clusterIP` field selector for Services requires **Kubernetes 1.31+**. On older apiservers the list/watch fails with `field label not supported` and the informer never syncs. The README currently states a 1.25+ prerequisite, so this needs one of:

- a minimum-version bump to 1.31+, or
- server-version discovery at startup with conditional application of the selector (kubelet-style fallback).

Note: existing `IsServiceHeadless` checks should remain regardless — they cover the unfiltered fallback path and ExternalName Services (`clusterIP == ""`), which the `!= "None"` selector does not exclude.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.