Understanding Kubernetes ClusterIP Services
Introduction
When deploying applications in Kubernetes, a critical question arises: How do different parts of your application communicate with each other? This is where Kubernetes Services come into play, with ClusterIP being the default and most common service type.
ClusterIP creates a stable, internal IP address that enables communication between different components of your application within the cluster. Think of it as an internal load balancer - it routes traffic to a set of pods based on label selectors.
In this tutorial, we'll dive deep into ClusterIP services, understand how they work, and explore practical examples to solidify your understanding.
What is a ClusterIP Service?
A ClusterIP service:
- Creates a virtual IP within the cluster
- Is only accessible within the cluster
- Routes traffic to a set of pods using label selectors
- Provides a stable endpoint for pod-to-pod communication
- Automatically load balances requests across all pods matching the selector
How ClusterIP Works
When you create a ClusterIP service, Kubernetes:
- Assigns a unique virtual IP address from the service CIDR range
- Updates the internal DNS to map the service name to this IP address
- Configures
kube-proxy
on all nodes to create forwarding rules - Distributes traffic to all pods matching the service's selector
Here's a visual representation:
Creating a ClusterIP Service
Let's create a simple example to demonstrate ClusterIP in action.
First, let's deploy a basic web application with multiple replicas:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Now, let's create a ClusterIP service to make this application accessible within the cluster:
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: ClusterIP # This is actually optional as ClusterIP is the default
selector:
app: web # This targets pods with label app=web
ports:
- port: 80 # Port exposed by the service
targetPort: 80 # Port on the pods to forward to
Apply these configurations to your cluster:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
Verifying Your ClusterIP Service
After creating the service, you can verify it with:
kubectl get services
Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24h
web-service ClusterIP 10.104.132.179 <none> 80/TCP 10s
Notice that:
- Your service has been assigned a ClusterIP (in this example, 10.104.132.179)
- The
EXTERNAL-IP
is<none>
because ClusterIP services are only accessible within the cluster
Accessing the ClusterIP Service
You can access your ClusterIP service from any pod in the cluster in several ways:
1. Using the ClusterIP directly
# From any pod in the cluster
curl 10.104.132.179
2. Using the service name (recommended)
# From any pod in the cluster
curl web-service
Kubernetes automatically sets up DNS resolution for services, so you can use the service name instead of the IP address.
3. Using the fully qualified domain name (FQDN)
# From any pod in the cluster
curl web-service.default.svc.cluster.local
This format follows: service-name.namespace.svc.cluster.local
Practical Example: Multi-Tier Application
Let's see a more practical example of using ClusterIP in a multi-tier application with a frontend, backend, and database.
First, let's create the database tier:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
ports:
- containerPort: 3306
---
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
Next, let's create the backend service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: my-backend:latest
env:
- name: DB_HOST
value: "mysql-service" # Using the database service name
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 8080
Finally, let's create the frontend:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: my-frontend:latest
env:
- name: API_URL
value: "http://backend-service" # Using the backend service name
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
type: LoadBalancer # This would be different - typically ClusterIP+Ingress or LoadBalancer
Notice how:
- The backend uses
mysql-service
as the database hostname - The frontend uses
backend-service
as the API URL - We use ClusterIP for internal communication (database and backend services)
- Only the frontend service would typically be exposed externally
Targeting Specific Ports
You can expose multiple ports with a single ClusterIP service:
apiVersion: 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: 9000
targetPort: 9090
When addressing multiple ports, you need to use the port name when forwarding:
kubectl port-forward service/multi-port-service 8080:http
Headless Services - A Special Type of ClusterIP
A headless service is a ClusterIP service with clusterIP: None
. Instead of load balancing, it returns the IP addresses of all pods directly.
This is useful when:
- You need direct pod-to-pod communication
- You're using a stateful application that manages its own load balancing
- You need to discover all pods in a service
apiVersion: v1
kind: Service
metadata:
name: headless-service
spec:
clusterIP: None # This makes it headless
selector:
app: stateful-app
ports:
- port: 80
targetPort: 80
With a headless service, a DNS lookup will return multiple A records, one for each pod.
Common Issues and Troubleshooting
1. Service not routing traffic
Check that:
- The service selector matches your pod labels
- Pods are running and ready
- The targetPort matches the containerPort
# Check if pods match the selector
kubectl get pods --selector=app=web
# Check the endpoints created by the service
kubectl get endpoints web-service
2. Service is accessible from some pods but not others
This might indicate a network policy issue. Check if any NetworkPolicy resources are restricting traffic.
3. Cannot resolve service by name
Verify that:
- The Kubernetes DNS service is running
- You're using the correct name and namespace
# Check if CoreDNS is running
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Test DNS resolution from a debugging pod
kubectl run -it --rm debug --image=busybox -- nslookup web-service
When to Use ClusterIP
Use ClusterIP when:
- You need pod-to-pod communication within the cluster
- You're building a multi-tier application
- You want to expose a service to other services but not externally
- You need a stable endpoint regardless of pod changes
Do NOT use ClusterIP when:
- You need to access the service from outside the cluster (use NodePort, LoadBalancer, or Ingress instead)
- You need direct pod-to-pod communication without load balancing (use a headless service)
Summary
ClusterIP services are the foundation of Kubernetes networking. They provide:
- Stable internal endpoints for your applications
- Automatic load balancing across pods
- Service discovery through DNS
- Isolation from external networks
By understanding ClusterIP services, you've taken a significant step in mastering Kubernetes networking concepts. As you build more complex applications, you'll find ClusterIP services essential for connecting your application components together.
Additional Resources
Practice Exercises
- Create a two-tier application with a frontend and backend, using a ClusterIP service to connect them.
- Modify the previous exercise to include a database tier.
- Create a headless service and observe how it behaves differently from a regular ClusterIP service.
- Test different service discovery methods (IP, DNS name, FQDN) and observe the results.
- Try creating a ClusterIP service with multiple ports and access each port from another pod.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)