mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-09 00:34:10 -04:00
fix(endpoint): avoid panic on services with empty IPFamilies
Accessing svc.Spec.IPFamilies[0] without a bounds check panics when a service reaches the controller with an empty IPFamilies field. This can happen via watch events: the apiserver's defaultOnRead decorator populates IPFamilies on GET/LIST but not on watch (cachingObject wrapping bypasses the type assertion). Restore the inference logic removed in #130101: fall back to ClusterIP for headful services and pod IP for headless services. Signed-off-by: Rahul <rahulbabu95@gmail.com>
This commit is contained in:
parent
33576824d7
commit
53361ff47e
2 changed files with 95 additions and 1 deletions
|
|
@ -220,8 +220,22 @@ func (e *Controller) Run(ctx context.Context, workers int) {
|
|||
|
||||
func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointAddress, error) {
|
||||
var endpointIP string
|
||||
ipFamily := v1.IPv4Protocol
|
||||
|
||||
wantIPv6 := svc.Spec.IPFamilies[0] == v1.IPv6Protocol
|
||||
// IPFamilies is expected to be populated by apiserver defaulting, but
|
||||
// some services may reach this controller with an empty IPFamilies via
|
||||
// watch events. Infer the family from ClusterIP or
|
||||
// pod IP so we never panic on IPFamilies[0].
|
||||
if len(svc.Spec.IPFamilies) > 0 {
|
||||
ipFamily = svc.Spec.IPFamilies[0]
|
||||
} else if len(svc.Spec.ClusterIP) > 0 && svc.Spec.ClusterIP != v1.ClusterIPNone {
|
||||
if utilnet.IsIPv6String(svc.Spec.ClusterIP) {
|
||||
ipFamily = v1.IPv6Protocol
|
||||
}
|
||||
} else if utilnet.IsIPv6String(pod.Status.PodIP) {
|
||||
ipFamily = v1.IPv6Protocol
|
||||
}
|
||||
wantIPv6 := ipFamily == v1.IPv6Protocol
|
||||
|
||||
// Find an IP that matches the family. We parse and restringify the IP in case the
|
||||
// value on the Pod is in an irregular format.
|
||||
|
|
|
|||
|
|
@ -3224,3 +3224,83 @@ func TestSyncEndpointsAddDeletePorts(t *testing.T) {
|
|||
t.Fatalf("incorrect endpoints after deleting first port:\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodToEndpointAddressForServiceEmptyIPFamilies(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
clusterIP string
|
||||
podIPs []v1.PodIP
|
||||
podIP string
|
||||
wantErr bool
|
||||
wantFamily v1.IPFamily
|
||||
}{
|
||||
{
|
||||
name: "headful IPv4, IPv4 pod",
|
||||
clusterIP: "10.0.0.1",
|
||||
podIPs: []v1.PodIP{{IP: "10.244.0.1"}},
|
||||
wantFamily: v1.IPv4Protocol,
|
||||
},
|
||||
{
|
||||
name: "headful IPv6, IPv6 pod",
|
||||
clusterIP: "fd00::1",
|
||||
podIPs: []v1.PodIP{{IP: "fd00::10"}},
|
||||
wantFamily: v1.IPv6Protocol,
|
||||
},
|
||||
{
|
||||
name: "headful IPv4, no matching pod IP",
|
||||
clusterIP: "10.0.0.1",
|
||||
podIPs: []v1.PodIP{{IP: "fd00::10"}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "headless, IPv4 pod",
|
||||
clusterIP: v1.ClusterIPNone,
|
||||
podIPs: []v1.PodIP{{IP: "10.244.0.1"}},
|
||||
podIP: "10.244.0.1",
|
||||
wantFamily: v1.IPv4Protocol,
|
||||
},
|
||||
{
|
||||
name: "headless, IPv6 pod",
|
||||
clusterIP: v1.ClusterIPNone,
|
||||
podIPs: []v1.PodIP{{IP: "fd00::10"}},
|
||||
podIP: "fd00::10",
|
||||
wantFamily: v1.IPv6Protocol,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
svc := &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
|
||||
Spec: v1.ServiceSpec{
|
||||
// Intentionally leave IPFamilies empty.
|
||||
ClusterIP: tc.clusterIP,
|
||||
},
|
||||
}
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-pod", Namespace: "bar", UID: "uid-1"},
|
||||
Spec: v1.PodSpec{NodeName: "node-1"},
|
||||
Status: v1.PodStatus{PodIP: tc.podIP, PodIPs: tc.podIPs},
|
||||
}
|
||||
|
||||
addr, err := podToEndpointAddressForService(svc, pod)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got addr=%v", addr)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if addr == nil {
|
||||
t.Fatal("expected an address but got nil")
|
||||
}
|
||||
isV6 := utilnet.IsIPv6String(addr.IP)
|
||||
wantV6 := tc.wantFamily == v1.IPv6Protocol
|
||||
if isV6 != wantV6 {
|
||||
t.Errorf("got IP %q (IPv6=%v), want family %v", addr.IP, isV6, tc.wantFamily)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue