Merge pull request #124598 from aroradaman/kubectl-describe-svc

Use endpointSlices for describing service endpoints
This commit is contained in:
Kubernetes Prow Robot 2024-05-06 09:06:14 -07:00 committed by GitHub
commit 44bd04c0cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 217 additions and 110 deletions

View File

@ -2603,7 +2603,9 @@ func (i *IngressDescriber) Describe(namespace, name string, describerSettings De
} }
func (i *IngressDescriber) describeBackendV1beta1(ns string, backend *networkingv1beta1.IngressBackend) string { func (i *IngressDescriber) describeBackendV1beta1(ns string, backend *networkingv1beta1.IngressBackend) string {
endpoints, err := i.client.CoreV1().Endpoints(ns).Get(context.TODO(), backend.ServiceName, metav1.GetOptions{}) endpointSliceList, err := i.client.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, backend.ServiceName),
})
if err != nil { if err != nil {
return fmt.Sprintf("<error: %v>", err) return fmt.Sprintf("<error: %v>", err)
} }
@ -2625,20 +2627,22 @@ func (i *IngressDescriber) describeBackendV1beta1(ns string, backend *networking
} }
} }
} }
return formatEndpoints(endpoints, sets.NewString(spName)) return formatEndpointSlices(endpointSliceList.Items, sets.New(spName))
} }
func (i *IngressDescriber) describeBackendV1(ns string, backend *networkingv1.IngressBackend) string { func (i *IngressDescriber) describeBackendV1(ns string, backend *networkingv1.IngressBackend) string {
if backend.Service != nil { if backend.Service != nil {
sb := serviceBackendStringer(backend.Service) sb := serviceBackendStringer(backend.Service)
endpoints, err := i.client.CoreV1().Endpoints(ns).Get(context.TODO(), backend.Service.Name, metav1.GetOptions{}) endpointSliceList, err := i.client.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, backend.Service.Name),
})
if err != nil { if err != nil {
return fmt.Sprintf("%v (<error: %v>)", sb, err) return fmt.Sprintf("%v (<error: %v>)", sb, err)
} }
service, err := i.client.CoreV1().Services(ns).Get(context.TODO(), backend.Service.Name, metav1.GetOptions{}) service, err := i.client.CoreV1().Services(ns).Get(context.TODO(), backend.Service.Name, metav1.GetOptions{})
if err != nil { if err != nil {
return fmt.Sprintf("%v(<error: %v>)", sb, err) return fmt.Sprintf("%v (<error: %v>)", sb, err)
} }
spName := "" spName := ""
for i := range service.Spec.Ports { for i := range service.Spec.Ports {
@ -2649,7 +2653,7 @@ func (i *IngressDescriber) describeBackendV1(ns string, backend *networkingv1.In
spName = sp.Name spName = sp.Name
} }
} }
ep := formatEndpoints(endpoints, sets.NewString(spName)) ep := formatEndpointSlices(endpointSliceList.Items, sets.New(spName))
return fmt.Sprintf("%s (%s)", sb, ep) return fmt.Sprintf("%s (%s)", sb, ep)
} }
if backend.Resource != nil { if backend.Resource != nil {
@ -2961,12 +2965,14 @@ func (d *ServiceDescriber) Describe(namespace, name string, describerSettings De
return "", err return "", err
} }
endpoints, _ := d.CoreV1().Endpoints(namespace).Get(context.TODO(), name, metav1.GetOptions{}) endpointSliceList, _ := d.DiscoveryV1().EndpointSlices(namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, name),
})
var events *corev1.EventList var events *corev1.EventList
if describerSettings.ShowEvents { if describerSettings.ShowEvents {
events, _ = searchEvents(d.CoreV1(), service, describerSettings.ChunkSize) events, _ = searchEvents(d.CoreV1(), service, describerSettings.ChunkSize)
} }
return describeService(service, endpoints, events) return describeService(service, endpointSliceList.Items, events)
} }
func buildIngressString(ingress []corev1.LoadBalancerIngress) string { func buildIngressString(ingress []corev1.LoadBalancerIngress) string {
@ -2985,10 +2991,7 @@ func buildIngressString(ingress []corev1.LoadBalancerIngress) string {
return buffer.String() return buffer.String()
} }
func describeService(service *corev1.Service, endpoints *corev1.Endpoints, events *corev1.EventList) (string, error) { func describeService(service *corev1.Service, endpointSlices []discoveryv1.EndpointSlice, events *corev1.EventList) (string, error) {
if endpoints == nil {
endpoints = &corev1.Endpoints{}
}
return tabbedString(func(out io.Writer) error { return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out) w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", service.Name) w.Write(LEVEL_0, "Name:\t%s\n", service.Name)
@ -3049,7 +3052,7 @@ func describeService(service *corev1.Service, endpoints *corev1.Endpoints, event
if sp.NodePort != 0 { if sp.NodePort != 0 {
w.Write(LEVEL_0, "NodePort:\t%s\t%d/%s\n", name, sp.NodePort, sp.Protocol) w.Write(LEVEL_0, "NodePort:\t%s\t%d/%s\n", name, sp.NodePort, sp.Protocol)
} }
w.Write(LEVEL_0, "Endpoints:\t%s\n", formatEndpoints(endpoints, sets.NewString(sp.Name))) w.Write(LEVEL_0, "Endpoints:\t%s\n", formatEndpointSlices(endpointSlices, sets.New(sp.Name)))
} }
w.Write(LEVEL_0, "Session Affinity:\t%s\n", service.Spec.SessionAffinity) w.Write(LEVEL_0, "Session Affinity:\t%s\n", service.Spec.SessionAffinity)
if service.Spec.ExternalTrafficPolicy != "" { if service.Spec.ExternalTrafficPolicy != "" {
@ -5419,39 +5422,38 @@ func translateTimestampSince(timestamp metav1.Time) string {
} }
// Pass ports=nil for all ports. // Pass ports=nil for all ports.
func formatEndpoints(endpoints *corev1.Endpoints, ports sets.String) string { func formatEndpointSlices(endpointSlices []discoveryv1.EndpointSlice, ports sets.Set[string]) string {
if len(endpoints.Subsets) == 0 { if len(endpointSlices) == 0 {
return "<none>" return "<none>"
} }
list := []string{} var list []string
max := 3 max := 3
more := false more := false
count := 0 count := 0
for i := range endpoints.Subsets { for i := range endpointSlices {
ss := &endpoints.Subsets[i] if len(endpointSlices[i].Ports) == 0 {
if len(ss.Ports) == 0 {
// It's possible to have headless services with no ports. // It's possible to have headless services with no ports.
for i := range ss.Addresses { for j := range endpointSlices[i].Endpoints {
if len(list) == max { if len(list) == max {
more = true more = true
} }
if !more { if !more {
list = append(list, ss.Addresses[i].IP) list = append(list, endpointSlices[i].Endpoints[j].Addresses[0])
} }
count++ count++
} }
} else { } else {
// "Normal" services with ports defined. // "Normal" services with ports defined.
for i := range ss.Ports { for j := range endpointSlices[i].Ports {
port := &ss.Ports[i] port := endpointSlices[i].Ports[j]
if ports == nil || ports.Has(port.Name) { if ports == nil || ports.Has(*port.Name) {
for i := range ss.Addresses { for k := range endpointSlices[i].Endpoints {
if len(list) == max { if len(list) == max {
more = true more = true
} }
addr := &ss.Addresses[i] addr := endpointSlices[i].Endpoints[k].Addresses[0]
if !more { if !more {
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port.Port))) hostPort := net.JoinHostPort(addr, strconv.Itoa(int(*port.Port)))
list = append(list, hostPort) list = append(list, hostPort)
} }
count++ count++

View File

@ -25,6 +25,9 @@ import (
"time" "time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/lithammer/dedent"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2" autoscalingv2 "k8s.io/api/autoscaling/v2"
@ -652,9 +655,10 @@ func getResourceList(cpu, memory string) corev1.ResourceList {
func TestDescribeService(t *testing.T) { func TestDescribeService(t *testing.T) {
singleStack := corev1.IPFamilyPolicySingleStack singleStack := corev1.IPFamilyPolicySingleStack
testCases := []struct { testCases := []struct {
name string name string
service *corev1.Service service *corev1.Service
expect []string endpointSlices []*discoveryv1.EndpointSlice
expected string
}{ }{
{ {
name: "test1", name: "test1",
@ -676,24 +680,50 @@ func TestDescribeService(t *testing.T) {
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
LoadBalancerIP: "5.6.7.8", LoadBalancerIP: "5.6.7.8",
SessionAffinity: "None", SessionAffinity: corev1.ServiceAffinityNone,
ExternalTrafficPolicy: "Local", ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
HealthCheckNodePort: 32222, HealthCheckNodePort: 32222,
}, },
}, },
expect: []string{ endpointSlices: []*discoveryv1.EndpointSlice{{
"Name", "bar", ObjectMeta: metav1.ObjectMeta{
"Namespace", "foo", Name: "bar-abcde",
"Selector", "blah=heh", Namespace: "foo",
"Type", "LoadBalancer", Labels: map[string]string{
"IP", "1.2.3.4", "kubernetes.io/service-name": "bar",
"Port", "port-tcp", "8080/TCP", },
"TargetPort", "9527/TCP", },
"NodePort", "port-tcp", "31111/TCP", Endpoints: []discoveryv1.Endpoint{
"Session Affinity", "None", {Addresses: []string{"10.244.0.1"}},
"External Traffic Policy", "Local", {Addresses: []string{"10.244.0.2"}},
"HealthCheck NodePort", "32222", {Addresses: []string{"10.244.0.3"}},
}, },
Ports: []discoveryv1.EndpointPort{{
Name: ptr.To("port-tcp"),
Port: ptr.To[int32](9527),
Protocol: ptr.To(corev1.ProtocolTCP),
}},
}},
expected: dedent.Dedent(`
Name: bar
Namespace: foo
Labels: <none>
Annotations: <none>
Selector: blah=heh
Type: LoadBalancer
IP Families: IPv4
IP: 1.2.3.4
IPs: <none>
IP: 5.6.7.8
Port: port-tcp 8080/TCP
TargetPort: 9527/TCP
NodePort: port-tcp 31111/TCP
Endpoints: 10.244.0.1:9527,10.244.0.2:9527,10.244.0.3:9527
Session Affinity: None
External Traffic Policy: Local
HealthCheck NodePort: 32222
Events: <none>
`)[1:],
}, },
{ {
name: "test2", name: "test2",
@ -715,24 +745,70 @@ func TestDescribeService(t *testing.T) {
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
LoadBalancerIP: "5.6.7.8", LoadBalancerIP: "5.6.7.8",
SessionAffinity: "None", SessionAffinity: corev1.ServiceAffinityNone,
ExternalTrafficPolicy: "Local", ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
HealthCheckNodePort: 32222, HealthCheckNodePort: 32222,
}, },
}, },
expect: []string{ endpointSlices: []*discoveryv1.EndpointSlice{
"Name", "bar", {
"Namespace", "foo", ObjectMeta: metav1.ObjectMeta{
"Selector", "blah=heh", Name: "bar-12345",
"Type", "LoadBalancer", Namespace: "foo",
"IP", "1.2.3.4", Labels: map[string]string{
"Port", "port-tcp", "8080/TCP", "kubernetes.io/service-name": "bar",
"TargetPort", "targetPort/TCP", },
"NodePort", "port-tcp", "31111/TCP", },
"Session Affinity", "None", Endpoints: []discoveryv1.Endpoint{
"External Traffic Policy", "Local", {Addresses: []string{"10.244.0.1"}},
"HealthCheck NodePort", "32222", {Addresses: []string{"10.244.0.2"}},
},
Ports: []discoveryv1.EndpointPort{{
Name: ptr.To("port-tcp"),
Port: ptr.To[int32](9527),
Protocol: ptr.To(corev1.ProtocolUDP),
}},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "bar-54321",
Namespace: "foo",
Labels: map[string]string{
"kubernetes.io/service-name": "bar",
},
},
Endpoints: []discoveryv1.Endpoint{
{Addresses: []string{"10.244.0.3"}},
{Addresses: []string{"10.244.0.4"}},
{Addresses: []string{"10.244.0.5"}},
},
Ports: []discoveryv1.EndpointPort{{
Name: ptr.To("port-tcp"),
Port: ptr.To[int32](9527),
Protocol: ptr.To(corev1.ProtocolUDP),
}},
},
}, },
expected: dedent.Dedent(`
Name: bar
Namespace: foo
Labels: <none>
Annotations: <none>
Selector: blah=heh
Type: LoadBalancer
IP Families: IPv4
IP: 1.2.3.4
IPs: <none>
IP: 5.6.7.8
Port: port-tcp 8080/TCP
TargetPort: targetPort/TCP
NodePort: port-tcp 31111/TCP
Endpoints: 10.244.0.1:9527,10.244.0.2:9527,10.244.0.3:9527 + 2 more...
Session Affinity: None
External Traffic Policy: Local
HealthCheck NodePort: 32222
Events: <none>
`)[1:],
}, },
{ {
name: "test-ServiceIPFamily", name: "test-ServiceIPFamily",
@ -754,25 +830,48 @@ func TestDescribeService(t *testing.T) {
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
LoadBalancerIP: "5.6.7.8", LoadBalancerIP: "5.6.7.8",
SessionAffinity: "None", SessionAffinity: corev1.ServiceAffinityNone,
ExternalTrafficPolicy: "Local", ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
HealthCheckNodePort: 32222, HealthCheckNodePort: 32222,
}, },
}, },
expect: []string{ endpointSlices: []*discoveryv1.EndpointSlice{{
"Name", "bar", ObjectMeta: metav1.ObjectMeta{
"Namespace", "foo", Name: "bar-123ab",
"Selector", "blah=heh", Namespace: "foo",
"Type", "LoadBalancer", Labels: map[string]string{
"IP", "1.2.3.4", "kubernetes.io/service-name": "bar",
"IP Families", "IPv4", },
"Port", "port-tcp", "8080/TCP", },
"TargetPort", "targetPort/TCP", Endpoints: []discoveryv1.Endpoint{
"NodePort", "port-tcp", "31111/TCP", {Addresses: []string{"10.244.0.1"}},
"Session Affinity", "None", },
"External Traffic Policy", "Local", Ports: []discoveryv1.EndpointPort{{
"HealthCheck NodePort", "32222", Name: ptr.To("port-tcp"),
}, Port: ptr.To[int32](9527),
Protocol: ptr.To(corev1.ProtocolTCP),
}},
}},
expected: dedent.Dedent(`
Name: bar
Namespace: foo
Labels: <none>
Annotations: <none>
Selector: blah=heh
Type: LoadBalancer
IP Families: IPv4
IP: 1.2.3.4
IPs: <none>
IP: 5.6.7.8
Port: port-tcp 8080/TCP
TargetPort: targetPort/TCP
NodePort: port-tcp 31111/TCP
Endpoints: 10.244.0.1:9527
Session Affinity: None
External Traffic Policy: Local
HealthCheck NodePort: 32222
Events: <none>
`)[1:],
}, },
{ {
name: "test-ServiceIPFamilyPolicy+ClusterIPs", name: "test-ServiceIPFamilyPolicy+ClusterIPs",
@ -796,43 +895,49 @@ func TestDescribeService(t *testing.T) {
IPFamilyPolicy: &singleStack, IPFamilyPolicy: &singleStack,
ClusterIPs: []string{"1.2.3.4"}, ClusterIPs: []string{"1.2.3.4"},
LoadBalancerIP: "5.6.7.8", LoadBalancerIP: "5.6.7.8",
SessionAffinity: "None", SessionAffinity: corev1.ServiceAffinityNone,
ExternalTrafficPolicy: "Local", ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
HealthCheckNodePort: 32222, HealthCheckNodePort: 32222,
}, },
}, },
expect: []string{ expected: dedent.Dedent(`
"Name", "bar", Name: bar
"Namespace", "foo", Namespace: foo
"Selector", "blah=heh", Labels: <none>
"Type", "LoadBalancer", Annotations: <none>
"IP", "1.2.3.4", Selector: blah=heh
"IP Families", "IPv4", Type: LoadBalancer
"IP Family Policy", "SingleStack", IP Family Policy: SingleStack
"IPs", "1.2.3.4", IP Families: IPv4
"Port", "port-tcp", "8080/TCP", IP: 1.2.3.4
"TargetPort", "targetPort/TCP", IPs: 1.2.3.4
"NodePort", "port-tcp", "31111/TCP", IP: 5.6.7.8
"Session Affinity", "None", Port: port-tcp 8080/TCP
"External Traffic Policy", "Local", TargetPort: targetPort/TCP
"HealthCheck NodePort", "32222", NodePort: port-tcp 31111/TCP
}, Endpoints: <none>
Session Affinity: None
External Traffic Policy: Local
HealthCheck NodePort: 32222
Events: <none>
`)[1:],
}, },
} }
for _, testCase := range testCases { for _, tc := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
fake := fake.NewSimpleClientset(testCase.service) objects := []runtime.Object{tc.service}
c := &describeClient{T: t, Namespace: "foo", Interface: fake} for i := range tc.endpointSlices {
objects = append(objects, tc.endpointSlices[i])
}
fakeClient := fake.NewSimpleClientset(objects...)
c := &describeClient{T: t, Namespace: "foo", Interface: fakeClient}
d := ServiceDescriber{c} d := ServiceDescriber{c}
out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true}) out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
for _, expected := range testCase.expect {
if !strings.Contains(out, expected) { assert.Equal(t, tc.expected, out)
t.Errorf("expected to find %q in output: %q", expected, out)
}
}
}) })
} }
} }
@ -3062,7 +3167,7 @@ Rules:
Host Path Backends Host Path Backends
---- ---- -------- ---- ---- --------
foo.bar.com foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>) /foo default-backend:80 (<error: services "default-backend" not found>)
Annotations: <none> Annotations: <none>
Events: <none>` + "\n", Events: <none>` + "\n",
}, },
@ -3078,7 +3183,7 @@ Rules:
Host Path Backends Host Path Backends
---- ---- -------- ---- ---- --------
foo.bar.com foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>) /foo default-backend:80 (<error: services "default-backend" not found>)
Annotations: <none> Annotations: <none>
Events: <none>` + "\n", Events: <none>` + "\n",
}, },
@ -3191,12 +3296,12 @@ Labels: <none>
Namespace: foo Namespace: foo
Address: Address:
Ingress Class: test Ingress Class: test
Default backend: default-backend:80 (<error: endpoints "default-backend" not found>) Default backend: default-backend:80 (<error: services "default-backend" not found>)
Rules: Rules:
Host Path Backends Host Path Backends
---- ---- -------- ---- ---- --------
foo.bar.com foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>) /foo default-backend:80 (<error: services "default-backend" not found>)
Annotations: <none> Annotations: <none>
Events: <none>` + "\n", Events: <none>` + "\n",
}, },
@ -3276,7 +3381,7 @@ Rules:
Host Path Backends Host Path Backends
---- ---- -------- ---- ---- --------
foo.bar.com foo.bar.com
/foo default-backend:80 (<error: endpoints "default-backend" not found>) /foo default-backend:80 (<error: services "default-backend" not found>)
Annotations: <none> Annotations: <none>
Events: <none>` + "\n", Events: <none>` + "\n",
}, },
@ -3296,11 +3401,11 @@ Labels: <none>
Namespace: foo Namespace: foo
Address: Address:
Ingress Class: test Ingress Class: test
Default backend: default-backend:80 (<error: endpoints "default-backend" not found>) Default backend: default-backend:80 (<error: services "default-backend" not found>)
Rules: Rules:
Host Path Backends Host Path Backends
---- ---- -------- ---- ---- --------
* * default-backend:80 (<error: endpoints "default-backend" not found>) * * default-backend:80 (<error: services "default-backend" not found>)
Annotations: <none> Annotations: <none>
Events: <none> Events: <none>
`, `,
@ -3344,11 +3449,11 @@ Labels: <none>
Namespace: foo Namespace: foo
Address: Address:
Ingress Class: <none> Ingress Class: <none>
Default backend: default-backend:80 (<error: endpoints "default-backend" not found>) Default backend: default-backend:80 (<error: services "default-backend" not found>)
Rules: Rules:
Host Path Backends Host Path Backends
---- ---- -------- ---- ---- --------
* * default-backend:80 (<error: endpoints "default-backend" not found>) * * default-backend:80 (<error: services "default-backend" not found>)
Annotations: <none> Annotations: <none>
Events: <none> Events: <none>
`, `,