Kubernetes Services: A Deep Dive with Examples

sheak imransheak imran
8 min read

Kubernetes has revolutionized the way we deploy and manage containerized applications. While Pods handle the actual workloads, they are ephemeral by nature - they come and go, and their IP addresses change. This is where Kubernetes Services come into play. In this comprehensive guide, we'll explore the inner workings of Kubernetes Services, uncover their different types, and provide practical examples to help you master this critical Kubernetes concept.

Understanding Kubernetes Services

In Kubernetes, a Service is an abstraction that defines a logical set of Pods and a policy to access them. Services provide stable network identities to applications running in Pods, enabling reliable communication between different components of your applications.

Think of a Service as a virtual IP address and port combination that routes traffic to the appropriate Pods. This abstraction simplifies service discovery and load balancing in your Kubernetes cluster, making it possible for different parts of your application to communicate with each other reliably.

yamlapiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

With this basic Service definition, any Pod with the label app: my-app will receive traffic from this Service on port 8080, while the Service itself is accessible within the cluster at my-service:80.

How Kubernetes Services Work

Kubernetes Services operate through a combination of labels, selectors, and kube-proxy:

  1. Label Selectors: Services use label selectors to identify which Pods should receive traffic.

  2. kube-proxy: This component runs on each node and implements the Service concept by maintaining network rules.

  3. Endpoint Objects: Kubernetes creates an Endpoint object (or EndpointSlice in newer versions) for each Service to track which Pod IPs are currently part of the Service.

When a client sends a request to a Service, kube-proxy intercepts the request and redirects it to one of the Pods backing the Service. This happens transparently to the client, which only needs to know about the stable Service name.

Let's look at the architecture in more detail:

The above diagram shows how kube-proxy intercepts Service requests and routes them to Pods.

Types of Kubernetes Services

Kubernetes offers several types of Services, each designed for different use cases:

ClusterIP Service

The default Service type in Kubernetes. ClusterIP exposes the Service on an internal IP address that is only accessible from within the cluster.

yamlapiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP  # This is the default, so can be omitted
  selector:
    app: backend
  ports:
    - port: 80      # Port exposed by the service
      targetPort: 8080  # Port the container is listening on

This Service can be accessed at backend-service or backend-service.default.svc.cluster.local from within the cluster.

NodePort Service

Exposes the Service on each Node's IP at a static port. This makes the Service accessible from outside the cluster by hitting <NodeIP>:<NodePort>.

yamlapiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80        # Cluster-internal port for the service
      targetPort: 8080  # Port the container is listening on
      nodePort: 30080   # Port exposed on each node (default range: 30000-32767)

With this configuration, the Service is available at:

  • Inside the cluster: web-service:80

  • Outside the cluster: <any-node-ip>:30080

LoadBalancer Service

Creates an external load balancer in the cloud provider (AWS, GCP, Azure, etc.) and assigns a fixed, external IP to the Service. This is typically used to expose services to the internet.

yamlapiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: LoadBalancer
  selector:
    app: frontend
  ports:
    - port: 80
      targetPort: 8080

Once created, the Service will be allocated an external IP by your cloud provider:

kubectl get svc frontend-service
NAME               TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
frontend-service   LoadBalancer   10.96.142.123   203.0.113.25     80:31234/TCP   2m

ExternalName Service

Maps a Service to a DNS name, without using selectors or endpoints. This is useful for creating a Service that points to external services outside the cluster.

yamlapiVersion: v1
kind: Service
metadata:
  name: external-database
spec:
  type: ExternalName
  externalName: database.example.com

When applications inside the cluster access external-database, the DNS resolves to database.example.com.

Headless Service

A special kind of Service that doesn't assign a cluster IP. This is useful when you want direct DNS resolution to Pod IPs rather than through a Service IP.

yamlapiVersion: v1
kind: Service
metadata:
  name: stateful-service
spec:
  clusterIP: None  # This makes it a headless service
  selector:
    app: stateful-app
  ports:
    - port: 80
      targetPort: 8080

With a headless Service, DNS queries for stateful-service return the IPs of all Pods selected by the Service.

Service Discovery in Kubernetes

Service discovery is the process by which applications can find and communicate with each other. Kubernetes provides two main mechanisms for service discovery:

  1. Environment Variables: When a Pod is created, Kubernetes adds environment variables for each active Service.

  2. DNS: Kubernetes maintains a DNS service that maps Service names to Service IPs.

The DNS-based discovery is the preferred method. Every Service defined in the cluster is assigned a DNS name of the form <service-name>.<namespace>.svc.cluster.local. For example, a Service named redis in the default namespace can be accessed at redis.default.svc.cluster.local, or simply redis from within the same namespace.

Working with Services

Creating a Service

Services can be created in multiple ways:

  1. Imperatively using kubectl:
bashkubectl expose deployment my-deployment --port=80 --target-port=8080 --name=my-service
  1. Declaratively using YAML:
yamlapiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - name: http
      port: 80
      targetPort: 8080

Apply this with:

bashkubectl apply -f service.yaml

Service Selectors

Selectors are key to how Services identify which Pods to route traffic to. Consider this example:

yamlapiVersion: v1
kind: Service
metadata:
  name: analytics-service
spec:
  selector:
    app: analytics
    tier: backend
  ports:
    - port: 80
      targetPort: 9376

This Service will only route traffic to Pods that have both labels: app: analytics and tier: backend.

Port Mappings

Services can expose multiple ports and map them to different target ports on the Pods:

yamlapiVersion: v1
kind: Service
metadata:
  name: multi-port-service
spec:
  selector:
    app: my-app
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: https
      port: 443
      targetPort: 8443
    - name: metrics
      port: 9100
      targetPort: 9100

This Service exposes three ports: HTTP, HTTPS, and metrics. Naming the ports is required when exposing multiple ports.

Real-World Examples

Microservices Communication

Let's consider a simple microservices architecture with a frontend, backend, and database:

yaml# Frontend Service
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  type: LoadBalancer
  selector:
    app: frontend
  ports:
    - port: 80
      targetPort: 80
---
# Backend Service
apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
    - port: 8080
      targetPort: 8080
---
# Database Service
apiVersion: v1
kind: Service
metadata:
  name: db
spec:
  type: ClusterIP
  selector:
    app: db
  ports:
    - port: 5432
      targetPort: 5432

In this example:

  • The frontend service is exposed externally via a LoadBalancer

  • The backend service is internal-only, accessed by the frontend

  • The database service is also internal-only, accessed by the backend

Database Access

For database access patterns, you often need a stable endpoint that doesn't change, even when the underlying Pods are replaced. This is perfect for a ClusterIP Service:

yamlapiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  type: ClusterIP
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

In your application code, you would connect to the database using the service name:

javaString dbUrl = "jdbc:postgresql://postgres:5432/mydb";

External API Integration

To integrate with an external API, you can use an ExternalName service:

yamlapiVersion: v1
kind: Service
metadata:
  name: weather-api
spec:
  type: ExternalName
  externalName: api.weather.com

Now, your application can make requests to weather-api instead of hardcoding the external domain:

pythonresponse = requests.get('http://weather-api/v1/current')

Advanced Service Configurations

Session Affinity

If your application requires session stickiness, you can configure session affinity on your Service:

yamlapiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800  # 3 hours
  ports:
    - port: 80
      targetPort: 8080

This configuration ensures that requests from the same client IP are directed to the same Pod.

Multi-Port Services

For applications that expose multiple ports, you can configure a Service to map to all of them:

yamlapiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  selector:
    app: web
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: https
      port: 443
      targetPort: 8443
    - name: admin
      port: 8001
      targetPort: 9001

Service Topology

Kubernetes v1.17+ introduced Service Topology, allowing you to route traffic based on node topology:

yamlapiVersion: v1
kind: Service
metadata:
  name: topology-service
spec:
  selector:
    app: my-app
  topologyKeys:
    - "kubernetes.io/hostname"
    - "topology.kubernetes.io/zone"
    - "topology.kubernetes.io/region"
    - "*"
  ports:
    - port: 80
      targetPort: 8080

This Service will preferentially route traffic to Pods on the same node, then in the same zone, then in the same region, before falling back to any Pod.

Troubleshooting Kubernetes Services

When troubleshooting Services, consider the following common issues:

  1. Service not routing traffic to Pods:

    • Check if the selector matches the Pod labels

    • Verify that the Pods are running and ready

    • Check if the targetPort matches the port the Pod is listening on

  2. Cannot access a Service from outside the cluster:

    • For NodePort Services, ensure the node's firewall allows traffic on the nodePort

    • For LoadBalancer Services, check if the cloud provider has successfully provisioned a load balancer

  3. DNS resolution not working:

    • Verify that the kube-dns/CoreDNS service is running

    • Check if the Pod's DNS configuration is correct

Here's a useful debug approach:

bash# Check the Service
kubectl get svc my-service

# Check the endpoints (should show Pod IPs if Service is correctly selecting Pods)
kubectl get endpoints my-service

# Test connectivity from another Pod
kubectl run test --image=busybox --rm -it -- wget -qO- http://my-service

Conclusion

Kubernetes Services are fundamental to building resilient, scalable applications on Kubernetes. They provide stable network endpoints for your applications, abstracting away the ephemeral nature of Pods.

In this article, we've explored:

  • Different Service types and when to use each

  • How Services work with selectors and kube-proxy

  • Service discovery mechanisms

  • Real-world examples of using Services

  • Advanced Service configurations

  • Troubleshooting techniques

By understanding and properly implementing Kubernetes Services, you can build robust networking patterns for your containerized applications, enabling reliable communication between components regardless of where they're running in the cluster.

Remember that Services are just one piece of the Kubernetes networking puzzle. For more complex routing requirements, you might want to explore Ingress resources, which provide HTTP-based routing capabilities beyond what Services offer.

Happy Kubernetes networking!

0
Subscribe to my newsletter

Read articles from sheak imran directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

sheak imran
sheak imran

System Administrator FM Associates BD | Network Specialist | RHCE, RHCSA, RHCVA certified.