Skip to content
Laviyx Learn

Try

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.

Advanced6 min readUpdated
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#

Terminal
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"}'
Output
service/web created
web ClusterIP
true true true
  • selector chooses pods by label.
  • port is the Service's own port, and targetPort is 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:

Terminal
kubectl run client --image=alpine:3.24 --restart=Never --rm -i --quiet -- wget -qO- http://web | grep -o '<title>.*</title>'
Output
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:

Terminal
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 -l
Output
2

Twelve requests reached more than one pod.

Service types#

TypeReachable fromUse
ClusterIPInside the cluster only; the defaultDatabases, internal APIs
NodePortA high port on each nodeTesting, simple setups
LoadBalancerA cloud load balancerPublic services in the cloud

Port forwarding#

kubectl port-forward connects a local port to a Service, which is perfect for testing and debugging:

Terminal
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 %1
Output
200

NodePort#

Terminal
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-np
Output
service/web-np exposed
NodePort
1
service "web-np" deleted from default namespace

Kubernetes 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:

YAML
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: 80

An Ingress needs an ingress controller, such as ingress-nginx, installed in the cluster. The newer Gateway API is a successor to Ingress.

Clean up#

Terminal
kubectl delete -f service.yaml
kubectl delete deployment web
Output
service "web" deleted from default namespace
deployment.apps "web" deleted from default namespace

Common mistakes#

Key takeaway#

Next: ConfigMaps, Secrets and Probes.

Finished this lesson?

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.