The Service Object via API

In the previous chapter, we explored Pods – their network identity (podIP), their readiness state, and how to group them using labels. However, we also noted a key limitation: Pod IPs are ephemeral. If a Pod dies and is replaced by a Deployment, the new Pod gets a new IP address. Relying directly on Pod IPs for communication between different parts of your application (e.g., frontend to backend) would be fragile and require constant updates.

This is the primary problem solved by Kubernetes Services. A Service provides a stable, virtual IP address (called the ClusterIP) and DNS name within the cluster. When traffic hits the Service's IP and port, Kubernetes automatically load balances it across the set of healthy Pods that match the Service's selector. This provides a reliable abstraction layer for accessing groups of Pods.

In this chapter, we'll explore how Services work under the hood, focusing on their API representation and how client-go allows us to manage them programmatically. We'll also look at the crucial Endpoints object, which dynamically tracks the actual ready Pod IPs backing a Service.

The Service Object via API (v1.Service)

Just like Pods, Services are standard Kubernetes API resources. When you interact with them using client-go, you'll be working with the v1.Service struct from the k8s.io/api/core/v1 package. The core definition and behavior of a Service are primarily defined within its spec field.

Let's break down the most important fields within ServiceSpec:

spec.type (string)

This field defines how the Service is exposed. The main types are:

  1. ClusterIP (Default): Exposes the Service on a cluster-internal IP address (spec.clusterIP). This IP is only reachable from within the cluster. This is the most common type for internal communication between different microservices.

  2. NodePort: Exposes the Service on each Node's IP address at a static port (the spec.ports[].nodePort). A ClusterIP Service is automatically created as well, to which the NodePort traffic is routed. This allows external traffic to reach the Service via <NodeIP>:<NodePort>, often used for development, testing, or when you manage your own external load balancers. The nodePort is typically chosen from a configured range (e.g., 30000-32767).

  3. LoadBalancer: Exposes the Service externally using a cloud provider's load balancer (e.g., an AWS ELB, GCP Load Balancer). A NodePort and ClusterIP Service are automatically created, and the external load balancer routes traffic to the NodePort. This is the standard way to expose services directly to the internet in cloud environments. The cloud provider asynchronously provisions the load balancer and populates its external IP/hostname into the Service's status.loadBalancer.ingress.

  4. ExternalName: This is a special type that maps the Service to an external DNS name (specified in spec.externalName) instead of using selectors and Pods. It acts as a CNAME record within the cluster's DNS. We won't focus heavily on this type in this networking book.

The type field dictates the fundamental accessibility of the Service.

spec.selector (map[string]string)

This is arguably the most critical field for ClusterIP, NodePort, and LoadBalancer Services. It's a map of key-value pairs – exactly like the Pod labels we discussed. The Service continuously monitors the cluster for Pods whose labels match this selector exactly.

  • Link: Only Pods matching this selector and passing their readiness probes become endpoints for this Service.

  • Format: map[string]string{"app": "my-backend", "tier": "database"}

  • Absence: If the selector is omitted or empty, the Service doesn't automatically target Pods. The corresponding Endpoints object must be managed manually (an advanced use case).

# Example Service Spec Snippet
apiVersion: v1
kind: Service
metadata:
  name: my-backend-service
spec:
  selector:
    app: my-backend # Service targets Pods with label 'app=my-backend'
  ports:
  # ... ports defined below ...
  type: ClusterIP # Service only reachable within the cluster

spec.ports ([]v1.ServicePort)

This is a slice defining one or more ports the Service will expose. Each ServicePort object within the slice has several fields:

  • port (int32, required): The port number that the Service itself will listen on (on its clusterIP).

  • targetPort (intstr.IntOrString): The port on the target Pods (matching the selector) to which traffic arriving at the Service's port should be forwarded. This can be:

    • A specific port number (e.g., 8080).

    • The name of a port defined in the Pod's spec.containers[*].ports[*].name (e.g., "http-api"). Using a named port is often preferred as it decouples the Service from specific container port numbers. If omitted, defaults to the same value as the port field.

  • protocol (string): The network protocol. Defaults to TCP. Can be UDP, SCTP.

  • name (string, optional): A name for this specific service port definition. Useful if a Service exposes multiple ports. Required by some tools like Istio.

  • nodePort (int32, optional): Only relevant if spec.type is NodePort or LoadBalancer. This specifies the static port number on the Node where the Service will be exposed. If not specified for NodePort, Kubernetes allocates one from the configured range.

# Example Service Spec Snippet - Ports
spec:
  selector:
    app: my-app
  ports:
  - name: http      # Name for this port mapping
    protocol: TCP
    port: 80       # Service listens on port 80 (on its ClusterIP)
    targetPort: 8080 # Forwards traffic to container port 8080 on matching Pods
  - name: metrics
    protocol: TCP
    port: 9090     # Service listens on port 9090
    targetPort: metrics-port # Forwards traffic to the *named port* 'metrics-port' on Pods
  type: ClusterIP

Status Field

While spec defines the desired state, status reflects the runtime state, mainly relevant for LoadBalancer types:

  • status.loadBalancer.ingress: For type: LoadBalancer, this field is populated by the cloud provider with the external IP address or hostname of the provisioned load balancer once it's ready.

Understanding these fields within the v1.Service struct is essential for creating and managing Services programmatically. In the next sections, we'll use client-go to create Services and explore the Endpoints object that bridges the gap between the stable Service IP and the dynamic Pod IPs.

Last updated

Was this helpful?