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)
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:
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.NodePort: Exposes the Service on each Node's IP address at a static port (thespec.ports[].nodePort). AClusterIPService is automatically created as well, to which theNodePorttraffic 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. ThenodePortis typically chosen from a configured range (e.g., 30000-32767).LoadBalancer: Exposes the Service externally using a cloud provider's load balancer (e.g., an AWS ELB, GCP Load Balancer). ANodePortandClusterIPService are automatically created, and the external load balancer routes traffic to theNodePort. 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'sstatus.loadBalancer.ingress.ExternalName: This is a special type that maps the Service to an external DNS name (specified inspec.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
Endpointsobject must be managed manually (an advanced use case).
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 itsclusterIP).targetPort(intstr.IntOrString): The port on the target Pods (matching the selector) to which traffic arriving at the Service'sportshould 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 theportfield.
protocol(string): The network protocol. Defaults toTCP. Can beUDP,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 ifspec.typeisNodePortorLoadBalancer. This specifies the static port number on the Node where the Service will be exposed. If not specified forNodePort, Kubernetes allocates one from the configured range.
Status Field
While spec defines the desired state, status reflects the runtime state, mainly relevant for LoadBalancer types:
status.loadBalancer.ingress: Fortype: 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?