Kubernetes Services: A Deep Dive with Examples


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:
Label Selectors: Services use label selectors to identify which Pods should receive traffic.
kube-proxy: This component runs on each node and implements the Service concept by maintaining network rules.
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:
Environment Variables: When a Pod is created, Kubernetes adds environment variables for each active Service.
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:
- Imperatively using kubectl:
bashkubectl expose deployment my-deployment --port=80 --target-port=8080 --name=my-service
- 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:
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
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
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!
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.