Docker and Kubernetes course menu
Kubernetes Services Explained: ClusterIP, NodePort and Ingress
Give pods a stable address with Kubernetes Services, understand ClusterIP, NodePort and LoadBalancer, use DNS names, port-forward for testing, and route traffic with Ingress.
On this page
Pods come and go, and each new pod gets a new IP address, so you cannot rely on those. A Service gives a group of pods one stable name and address, and spreads traffic among them.
For this lesson, a Deployment called web is running 3 Nginx pods, with the label app=web.
Create a Service#
cat > service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
EOF
kubectl apply -f service.yaml
kubectl get service web --no-headers | awk '{print $1, $2}'
kubectl get endpointslices -l kubernetes.io/service-name=web -o jsonpath='{.items[0].endpoints[*].conditions.ready}{"\n"}'service/web created
web ClusterIP
true true trueselectorchooses pods by label.portis the Service's own port, andtargetPortis the pod's port.
The three true values are the three ready pods behind the Service.
Reach it from inside the cluster#
Kubernetes has DNS, so that pods can find a Service by name. Run a temporary pod to test it:
kubectl run client --image=alpine:3.24 --restart=Never --rm -i --quiet -- wget -qO- http://web | grep -o '<title>.*</title>'wget: can't connect to remote host (10.96.4.28): Operation timed out
pod default/client terminated (Error)The full name is web.default.svc.cluster.local, where default is the namespace. Inside the same namespace, web is enough. So an app's database URL can simply say db:5432, as with Compose.
Load balancing#
Each request can go to a different pod. Give each pod its own page, and call the Service several times:
for p in $(kubectl get pods -l app=web -o name); do
kubectl exec "$p" -- sh -c 'hostname > /usr/share/nginx/html/index.html'
done
kubectl run client --image=alpine:3.24 --restart=Never --rm -i --quiet -- sh -c 'for i in 1 2 3 4 5 6 7 8 9 10 11 12; do wget -qO- http://web; done' | sort -u | wc -l2Twelve requests reached more than one pod.
Service types#
Port forwarding#
kubectl port-forward connects a local port to a Service, which is perfect for testing and debugging:
kubectl port-forward service/web 8080:80 > /dev/null 2>&1 &
sleep 2
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080
kill %1200NodePort#
kubectl expose deployment web --name=web-np --type=NodePort --port=80
kubectl get service web-np --no-headers | awk '{print $2}'
kubectl get service web-np -o jsonpath='{.spec.ports[0].nodePort}' | grep -cE '^3[0-9]{4}$'
kubectl delete service web-npservice/web-np exposed
NodePort
1
service "web-np" deleted from default namespaceKubernetes picks a port in the range 30000 to 32767.
Ingress#
A public site usually has one address, with many paths or hostnames. An Ingress routes HTTP traffic to Services, and handles HTTPS:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: site
spec:
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 8000
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80An Ingress needs an ingress controller, such as ingress-nginx, installed in the cluster. The newer Gateway API is a successor to Ingress.
Clean up#
kubectl delete -f service.yaml
kubectl delete deployment webservice "web" deleted from default namespace
deployment.apps "web" deleted from default namespaceCommon mistakes#
Key takeaway#
Finished this lesson?
Related lessons
Recommended Docker and Kubernetes resources
- Docker Deep Dive by Nigel Poulton
A practical guide to how Docker works, for readers who have finished the basics.
Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.