Skip to content

fix: create IPv6 EndpointSlice for kubelet Service on dual-stack clusters#8682

Open
ckrause wants to merge 7 commits into
prometheus-operator:mainfrom
ckrause:main
Open

fix: create IPv6 EndpointSlice for kubelet Service on dual-stack clusters#8682
ckrause wants to merge 7 commits into
prometheus-operator:mainfrom
ckrause:main

Conversation

@ckrause

@ckrause ckrause commented Jul 10, 2026

Copy link
Copy Markdown

Description

getNodeAddresses() was discarding the full address map returned by nodeAddress() and only emitting a single IP per node, which always resolved to IPv4 on dual-stack clusters. This prevented the creation of an IPv6 EndpointSlice for the kubelet Service.

With Prometheus 3.11 (prometheus/prometheus#18192), EndpointSlices whose addressType does not match svc.Spec.IPFamilies[0] are skipped to avoid duplicate scraping. On IPv6-primary dual-stack clusters this caused all kubelet targets to be dropped, as only the IPv4 EndpointSlice existed.

Fix by iterating all InternalIP (or ExternalIP when priority is "external") addresses per node so that syncEndpointSlice() — which already has the dual-stack infrastructure — creates separate IPv4 and IPv6 EndpointSlices.

Closes: #8681

Type of change

What type of changes does your code introduce to the Prometheus operator? Put an x in the box that apply.

  • CHANGE (fix or feature that would cause existing functionality to not work as expected)
  • FEATURE (non-breaking change which adds functionality)
  • BUGFIX (non-breaking change which fixes an issue)
  • ENHANCEMENT (non-breaking change which improves existing functionality)
  • NONE (if none of the other choices apply. Example, tooling, build system, CI, docs, etc.)

Verification

Tested manually in a development cluster. Unit tests extended for dual-stack.

Changelog entry

- Fix kubelet scraping on dual-stack clusters (IPv6,IPv4)

…ters

getNodeAddresses() was discarding the full address map returned by
nodeAddress() and only emitting a single IP per node, which always
resolved to IPv4 on dual-stack clusters. This prevented the creation of
an IPv6 EndpointSlice for the kubelet Service.

With Prometheus 3.11 (prometheus/prometheus#18192), EndpointSlices whose
addressType does not match svc.Spec.IPFamilies[0] are skipped to avoid
duplicate scraping. On IPv6-primary dual-stack clusters this caused all
kubelet targets to be dropped, as only the IPv4 EndpointSlice existed.

Fix by iterating all InternalIP (or ExternalIP when priority is
"external") addresses per node so that syncEndpointSlice() — which
already has the dual-stack infrastructure — creates separate IPv4 and
IPv6 EndpointSlices. Also set IPFamilyPolicy: PreferDualStack on the
kubelet Service so Kubernetes assigns both ClusterIPs on dual-stack
clusters; the policy degrades gracefully to single-stack on clusters
that only support one IP family.

Fixes prometheus-operator#8681
@ckrause
ckrause requested a review from a team as a code owner July 10, 2026 08:45
CreateOrUpdateService always copies IPFamilies and IPFamilyPolicy from
the existing Service on update, so setting them in syncService() has no
effect after initial creation. On first creation Kubernetes assigns the
correct values based on the cluster's network configuration.
@ckrause

ckrause commented Jul 14, 2026

Copy link
Copy Markdown
Author

Update: I removed the redundant IPFamilyPolicy from syncService.
CreateOrUpdateService always copies IPFamilies and IPFamilyPolicy from the existing Service on update, so setting them in syncService() has no effect after initial creation. On first creation, Kubernetes assigns the
correct values based on the cluster's network configuration.
I successfully tested the new version in a cluster; it works as expected.

Comment thread pkg/kubelet/controller.go Outdated

for _, n := range nodes {
address, _, err := c.nodeAddress(n)
_, addrMap, err := c.nodeAddress(n)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we change nodeAddress() to return the map only? If yes, we can also rename it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread pkg/kubelet/controller.go Outdated
continue
// Select addresses by priority: use ExternalIP if priority is "external"
// and external addresses exist, otherwise fall back to InternalIP.
addrType := corev1.NodeInternalIP

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use the NodeExternalIP addresses if the NodeInternalIP list is empty?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

ckrause added 2 commits July 17, 2026 10:55
…p only

The single string return value is no longer used after the fix to emit
all InternalIP addresses per node. Simplify the signature to return the
address map only and rename the function to better reflect its purpose.
…sesByType()

Move the priority/fallback logic into a dedicated primaryAddressType()
helper so getNodeAddresses() can use it to select the correct address
type for dual-stack nodes. nodeAddressesByType() is simplified to just
build and return the address map without any error return.

Also restores the ExternalIP fallback that was missing after the initial
dual-stack fix.
@pull-request-size pull-request-size Bot added size/L and removed size/M labels Jul 17, 2026
@ckrause

ckrause commented Jul 17, 2026

Copy link
Copy Markdown
Author

Hi @simonpasquier, thanks for your comments. I addressed both of them. Can you please check the new version?

@simonpasquier simonpasquier left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/should we combine nodeAddressesByType and primaryAddressType in a single function? It would be good to add unit tests too.

Comment thread pkg/kubelet/controller.go Outdated
}

return "", m, fmt.Errorf("host address unknown")
return "", fmt.Errorf("host address unknown")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unreachable since we have a "default:" now.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, the trailing return is still reachable: if a node has neither NodeInternalIP nor NodeExternalIP addresses, neither if condition in the matching branch will be true, and execution falls through to the return after the switch. This covers both the "external" case and the default: case when no addresses are present. Moving the error into each branch would duplicate code.

@simonpasquier

Copy link
Copy Markdown
Contributor

It would be good to add unit tests too.

sorry I missed that you already added them 👍

@ckrause

ckrause commented Jul 17, 2026

Copy link
Copy Markdown
Author

Thanks for the suggestion. I kept nodeAddressesByType and primaryAddressType as separate functions because primaryAddressType takes a plain map, which makes it easier to test in isolation without constructing full Node objects — as you can see in the new TestPrimaryAddressType I just added. Happy to combine them if you prefer though.

@simonpasquier

Copy link
Copy Markdown
Contributor

Happy to combine them if you prefer though.

yes please

@ckrause

ckrause commented Jul 17, 2026

Copy link
Copy Markdown
Author

Sure, done.

@ckrause

ckrause commented Jul 22, 2026

Copy link
Copy Markdown
Author

Hi @simonpasquier , is it ready to merge from your side now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kubelet EndpointSlice only contains IPv4 addresses on dual-stack clusters where IPv6 is the primary IP family

2 participants