The Endpoints Object: The Real Connection Behind Services

We've established that a Service provides a stable IP (ClusterIP) and uses a selector to identify target Pods. But how does traffic actually get from the Service IP to one of those potentially many, dynamically changing Pod IPs? This is where the Endpoints object (API type v1.Endpoints) comes into play.

For every Service in Kubernetes (unless it's type: ExternalName or explicitly configured otherwise), the Kubernetes control plane automatically creates and manages a corresponding Endpoints object. This Endpoints object has the same name and resides in the same namespace as its parent Service.

The Core Function:

The primary role of the Endpoints object is to maintain a dynamic list of IP address and port combinations that represent the actual, ready Pods matching the Service's selector at that moment in time.

Think of it like this:

  1. Service: Defines the stable entry point (ClusterIP, Port) and the desired group of backends (Selector).

  2. Endpoints Controller: A part of the Kubernetes control plane constantly watches:

    • Services and their selectors.

    • Pods and their labels.

    • Pods and their readiness status (from Readiness Probes).

  3. Endpoints Object: The result of the Endpoints Controller's work. It lists the IP:TargetPort pairs for only those Pods that match the Service's selector AND are currently Ready.

How kube-proxy Uses Endpoints:

Components like kube-proxy (which run on each Node) watch these Endpoints objects. When traffic arrives destined for a Service's ClusterIP:Port, kube-proxy uses the list of IPs and ports from the corresponding Endpoints object to select one actual backend Pod IP and forward the traffic there, performing the load balancing. (Note: The exact mechanism depends on the kube-proxy mode - iptables, IPVS, etc. - but the reliance on the Endpoints data is fundamental).

The v1.Endpoints Structure:

Let's look at the key fields within an Endpoints object when retrieved via the API:

  • metadata: Contains the name and namespace, which match the associated Service.

  • subsets ([]v1.EndpointSubset): This is the most important field. It's a list of subsets, where each subset typically corresponds to a unique combination of ports defined in the parent Service. Often, there's just one subset if the Service defines only one port mapping. Each EndpointSubset contains:

    • addresses ([]v1.EndpointAddress): A list of IP addresses for Pods that are ready to serve traffic for the ports defined in this subset. Each EndpointAddress has:

      • ip (string): The actual podIP of a ready backend Pod.

      • nodeName (string, optional): The name of the Node where this Pod is running.

      • targetRef (*v1.ObjectReference, optional): A reference back to the specific Pod object this IP belongs to.

    • notReadyAddresses ([]v1.EndpointAddress): A list of IP addresses for Pods that match the Service selector but are not ready (e.g., failing readiness probes, terminating). These IPs are generally not used for routing normal Service traffic but can be useful for debugging or specific scenarios. The structure is the same as addresses.

    • ports ([]v1.EndpointPort): A list describing the ports exposed by the Pods in this subset. Each EndpointPort has:

      • port (int32): The targetPort (the port number on the Pod) that traffic should be sent to.

      • protocol (string): TCP, UDP, SCTP.

      • name (string, optional): The name of the port, matching the ServicePort name if defined.

# Example Endpoints Object Structure (Simplified YAML representation)
apiVersion: v1
kind: Endpoints
metadata:
  name: my-backend-service # Matches the Service name
  namespace: default        # Matches the Service namespace
subsets:
- addresses: # IPs of READY Pods matching the selector
  - ip: 10.244.1.5 # Ready Pod 1 IP
    nodeName: worker-node-1
    targetRef:
      kind: Pod
      name: my-backend-pod-abc
      namespace: default
      uid: ...
  - ip: 10.244.2.8 # Ready Pod 2 IP
    nodeName: worker-node-2
    targetRef:
      kind: Pod
      name: my-backend-pod-xyz
      namespace: default
      uid: ...
  notReadyAddresses: # IPs of Pods matching selector but NOT Ready
  - ip: 10.244.1.6 # Not Ready Pod 3 IP
    nodeName: worker-node-1
    targetRef:
      kind: Pod
      name: my-backend-pod-def # Maybe terminating or failing probe
      namespace: default
      uid: ...
  ports: # Ports these Pods expose (matching Service targetPort)
  - name: http-backend
    port: 8080 # The targetPort number on the Pods
    protocol: TCP
# - another subset if the service has multiple port definitions

Inspecting Endpoints with client-go:

You can retrieve and inspect Endpoints objects just like any other resource:

// Assuming 'clientset' and 'serviceName', 'namespace' are defined
endpointsClient := clientset.CoreV1().Endpoints(namespace)

endpoints, err := endpointsClient.Get(context.TODO(), serviceName, metav1.GetOptions{})
if err != nil {
    if k8serrors.IsNotFound(err) {
        log.Printf("Endpoints for Service '%s' not found in namespace '%s'. Is the Service defined correctly?\n", serviceName, namespace)
    } else {
        log.Fatalf("Error getting endpoints '%s': %s\n", serviceName, err.Error())
    }
    // Handle error or exit
    return // Or os.Exit(1) depending on desired behavior
}

fmt.Printf("Endpoints for Service '%s':\n", endpoints.Name)
if len(endpoints.Subsets) == 0 {
    fmt.Println("  No subsets found (Service might have no matching ready pods or manual endpoints configuration).")
}

for i, subset := range endpoints.Subsets {
    fmt.Printf("  Subset %d:\n", i+1)
    fmt.Println("    Ready Addresses:")
    if len(subset.Addresses) == 0 {
        fmt.Println("      <none>")
    }
    for _, addr := range subset.Addresses {
        // Check if NodeName and TargetRef are non-nil before dereferencing
        nodeName := "<unknown>"
        if addr.NodeName != nil {
            nodeName = *addr.NodeName
        }
        podName := "<unknown>"
        if addr.TargetRef != nil {
             podName = addr.TargetRef.Name
        }
        fmt.Printf("      - IP: %s (Node: %s, Pod: %s)\n", addr.IP, nodeName, podName)
    }

    fmt.Println("    Not Ready Addresses:")
    if len(subset.NotReadyAddresses) == 0 {
        fmt.Println("      <none>")
    }
    for _, addr := range subset.NotReadyAddresses {
         // Check if NodeName and TargetRef are non-nil before dereferencing
        nodeName := "<unknown>"
        if addr.NodeName != nil {
            nodeName = *addr.NodeName
        }
        podName := "<unknown>"
        if addr.TargetRef != nil {
             podName = addr.TargetRef.Name
        }
        fmt.Printf("      - IP: %s (Node: %s, Pod: %s)\n", addr.IP, nodeName, podName)
    }

    fmt.Println("    Ports:")
     if len(subset.Ports) == 0 {
        fmt.Println("      <none>")
    }
    for _, port := range subset.Ports {
         fmt.Printf("      - Name: %s, Port: %d, Protocol: %s\n", port.Name, port.Port, port.Protocol)
    }
}
fmt.Println("---------------")

Why Inspect Endpoints?

While Endpoints are usually managed automatically, inspecting them programmatically is incredibly useful for:

  • Debugging Service Issues: If a Service isn't working, checking its corresponding Endpoints object is often the first step. Are there any addresses listed? Are the IPs and ports correct? Are expected Pods perhaps in the notReadyAddresses list instead?

  • Building Custom Logic: Sometimes you might need direct access to the list of ready backend IPs for specific load balancing strategies or service discovery mechanisms outside of kube-proxy.

  • Understanding Service Behavior: Seeing the dynamic updates to the Endpoints object as Pods become ready or unready solidifies the understanding of how Services connect to Pods.

The automatic management of Endpoints based on Service selectors and Pod readiness is a cornerstone of Kubernetes networking, providing the dynamic link between stable service abstractions and ephemeral Pod instances. Next, we'll briefly touch upon how kube-proxy conceptually uses this information.

Last updated

Was this helpful?