Overview and What You Will Learn
In this guide you will learn all four Kubernetes Service types, when to use each one, and how to create them with real YAML examples. You will understand how traffic flows from the internet into your cluster, how internal pod-to-pod communication works, and how to debug Service connectivity problems using practical commands.
Why This Matters in Production
Pods get new IP addresses every time they restart. A Service gives your application a stable, permanent address that never changes. Without understanding Service types, you will either expose internal services to the internet accidentally, or fail to expose the right services properly β both of which cause security issues or downtime at scale.
Core Principles
+------------------------------------------+| Internet / External Traffic |+------------------------------------------+ | v+------------------------------------------+| LoadBalancer Service | <- Cloud load balancer created| External IP exposed to internet | Use for: public-facing apps+------------------------------------------+ | v+------------------------------------------+| NodePort Service | <- Port opened on every node| Accessible via node IP + port | Use for: testing, on-prem clusters+------------------------------------------+ | v+------------------------------------------+| ClusterIP Service | <- Internal virtual IP only| Only reachable inside the cluster | Use for: everything internal+------------------------------------------+ | v+------------------------------------------+| ExternalName Service | <- DNS alias to external service| Maps to an external DNS name | Use for: migrating external DBs+------------------------------------------+Detailed Step-by-Step Practical Lab
Step 1: ClusterIP β Internal Communication Between Services
ClusterIP is the default Service type. It creates a stable virtual IP that only works inside the cluster. This is what every internal microservice should use to communicate with other services.
# clusterip-service.yaml β internal payments API serviceapiVersion: v1kind: Servicemetadata: name: payments-api namespace: productionspec: type: ClusterIP # Default β can omit this line entirely selector: app: payments-api # Routes to any pod with this label ports: - name: http port: 80 # Port that other services call targetPort: 8080 # Port the container actually listens on# Apply and inspectkubectl apply -f clusterip-service.yamlkubectl get service payments-api -n production # Output:# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# payments-api ClusterIP 10.96.45.200 <none> 80/TCP 5m # Test internal connectivity from another podkubectl run test-pod --image=curlimages/curl -it --rm -n production -- \ curl http://payments-api.production.svc.cluster.local/health# Expected: {"status": "healthy"} # See which pods the Service is routing tokubectl get endpoints payments-api -n production# Output shows the actual pod IPs behind the serviceStep 2: NodePort β Testing and On-Premise Exposure
NodePort opens a specific port on every node in the cluster. Anyone who can reach a node IP can access the service. Use this for testing or on-premise clusters β avoid in cloud production environments where LoadBalancer is available.
# nodeport-service.yamlapiVersion: v1kind: Servicemetadata: name: order-api-nodeport namespace: stagingspec: type: NodePort selector: app: order-api ports: - port: 80 # ClusterIP port (internal) targetPort: 8080 # Container port nodePort: 30080 # Port opened on every node (30000-32767 range) # If omitted, Kubernetes picks a random port# Get a node IP to test withkubectl get nodes -o wide# NAME INTERNAL-IP# mumbai-worker-1 10.0.1.20# mumbai-worker-2 10.0.1.21 # Access via any node IP + nodePortcurl http://10.0.1.20:30080/healthcurl http://10.0.1.21:30080/health# Both work β kube-proxy routes to any healthy podCOMMON MISTAKE / WARNING**Security:** Never use NodePort for sensitive production services. NodePort opens a port on every node in the cluster including control plane nodes. In a cloud environment, always use LoadBalancer or Ingress for external traffic so you have one controlled entry point.
Step 3: LoadBalancer β Production External Exposure
LoadBalancer creates an actual cloud load balancer (AWS ALB/NLB, GCP Load Balancer) and assigns a public IP automatically. This is the correct way to expose a service externally in cloud environments.
# loadbalancer-service.yaml β external API gatewayapiVersion: v1kind: Servicemetadata: name: api-gateway namespace: production annotations: # AWS-specific annotations for NLB instead of classic load balancer service.beta.kubernetes.io/aws-load-balancer-type: "nlb" service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"spec: type: LoadBalancer selector: app: api-gateway ports: - name: http port: 80 targetPort: 8080 - name: https port: 443 targetPort: 8443# Apply and wait for the external IP to be assignedkubectl apply -f loadbalancer-service.yamlkubectl get service api-gateway -n production --watch # Output after cloud LB is provisioned (takes 1-2 minutes):# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)# api-gateway LoadBalancer 10.96.22.100 52.66.123.45 80:31204/TCP # Test the external endpointcurl http://52.66.123.45/healthPLACEMENT PRO TIP**Tip:** In production clusters with many services, each LoadBalancer Service creates a separate cloud load balancer β which gets expensive fast. At Swiggy or Razorpay scale with 50+ services, use one Ingress Controller with a single LoadBalancer instead of creating a LoadBalancer per service. This saves significant cloud costs.
Step 4: ExternalName β Connecting to External Services
ExternalName maps a Kubernetes Service name to an external DNS name. Pods inside the cluster use the Service name and DNS resolution handles the redirect transparently.
# externalname-service.yaml β map internal name to external RDS databaseapiVersion: v1kind: Servicemetadata: name: postgres-db namespace: productionspec: type: ExternalName externalName: mumbai-prod-db.c7xyz.ap-south-1.rds.amazonaws.com # Pods call postgres-db.production.svc.cluster.local # which resolves to the RDS endpoint automaticallyProduction Best Practices & Common Pitfalls
- Always name your Service ports (e.g.,
name: http,name: grpc). Named ports allow Ingress controllers and monitoring tools to correctly identify the protocol and configure routing rules. - Use
sessionAffinity: ClientIPon a ClusterIP Service if your application stores session state in memory. Without it, consecutive requests from the same user may hit different pods β each with a different session state.
COMMON MISTAKE / WARNING**Common Mistake:** Creating a Service with a `selector` that does not match any pod labels. The Service is created successfully but has zero endpoints β all traffic silently drops. Always verify with `kubectl get endpoints` immediately after creating a Service. Empty endpoints means the label selector is wrong.
Quick Reference & Troubleshooting Commands
| Command | Purpose |
|---|---|
kubectl get services -n <ns> |
List all Services and their types |
kubectl get endpoints <svc> -n <ns> |
See which pod IPs back a Service |
kubectl describe service <svc> -n <ns> |
Full Service details and events |
kubectl run test --image=curlimages/curl -it --rm -- curl http://<svc> |
Test Service connectivity |
kubectl get service <svc> -o jsonpath='{.spec.clusterIP}' |
Get ClusterIP of a Service |