TL;DR (Quick Summary)#
- Service Abstraction: Defines a logical set of Pods determined by a
selectorlabel query and an access policy. - Service Types:
ClusterIP(Default): Exposes the service on a cluster-internal IP. Accessible only from within the cluster.NodePort: Exposes the service on each Worker Node’s IP at a static port (30000-32767). Accessible externally via<NodeIP>:<NodePort>.LoadBalancer: Provisions a cloud provider’s external load balancer (AWS NLB/ALB, GCP Network LB, Azure LB) routing traffic toNodePort.
- Endpoints Object: Kubernetes automatically populates an
Endpointsobject listing the active target Pod IPs matching the service selector.
1. Service Types & Architecture#
graph TD
ClientExt["External Client
(Internet)"] -->|Public IP: 203.0.113.10:80| LB["Cloud LoadBalancer
(Type: LoadBalancer)"]
subgraph K8sCluster["Kubernetes Cluster"]
LB -->|NodePort 30080| NP1["Worker Node 1
(192.168.1.10)"]
LB -->|NodePort 30080| NP2["Worker Node 2
(192.168.1.11)"]
NP1 --> CIP["ClusterIP Service
(10.96.100.50:80)
Selector: app=web"]
NP2 --> CIP
CIP --> Pod1["Pod A (10.244.1.5:80)"]
CIP --> Pod2["Pod B (10.244.2.8:80)"]
CIP --> Pod3["Pod C (10.244.2.9:80)"]
end
2. ClusterIP (Internal Service Discovery)#
ClusterIP is the default service type used for internal microservice-to-microservice communication (e.g., frontend API communicating with an internal database).
Create service-clusterip.yaml:
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: default
spec:
type: ClusterIP
selector:
app: backend
ports:
- name: http
port: 80 # Service Virtual Port
targetPort: 8080 # Container Port on Backend PodApply and inspect the service:
kubectl apply -f service-clusterip.yaml
kubectl get svc backend-serviceExpected Terminal Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend-service ClusterIP 10.96.142.100 <none> 80/TCP 15sInternal DNS Resolution#
Inside any Pod in the cluster, CoreDNS automatically resolves the service name to 10.96.142.100:
- Short FQDN:
backend-service - Full FQDN:
backend-service.default.svc.cluster.local
3. NodePort (Bare-Metal & Local Access)#
NodePort allocates a port from the default range 30000-32767 on all worker nodes. Any traffic sent to <Node-IP>:<NodePort> is forwarded to the backend service.
Create service-nodeport.yaml:
apiVersion: v1
kind: Service
metadata:
name: frontend-nodeport
namespace: default
spec:
type: NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 80
nodePort: 30080 # Optional: explicitly choose port between 30000-32767Apply and test access:
kubectl apply -f service-nodeport.yaml
kubectl get svc frontend-nodeportNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend-nodeport NodePort 10.96.200.45 <none> 80:30080/TCP 10sYou can now curl any worker node’s IP address:
curl http://<WORKER_NODE_IP>:300804. LoadBalancer (Cloud Managed Access)#
When running in managed cloud environments (EKS, GKE, AKS), specifying type: LoadBalancer automatically provisions a cloud load balancer with a public IP address.
Create service-loadbalancer.yaml:
apiVersion: v1
kind: Service
metadata:
name: public-web-lb
namespace: default
spec:
type: LoadBalancer
selector:
app: frontend
ports:
- port: 80
targetPort: 80Apply and check external IP provisioning:
kubectl apply -f service-loadbalancer.yaml
kubectl get svc public-web-lb --watchNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
public-web-lb LoadBalancer 10.96.88.12 35.240.112.50 80:31452/TCP 45s5. Endpoints & EndpointSlices Debugging#
How does a Service know which Pod IPs to route traffic to? Through Endpoints.
List active Endpoints matching service selectors:
kubectl get endpoints backend-serviceExpected Terminal Output:
NAME ENDPOINTS AGE
backend-service 10.244.1.12:8080,10.244.2.14:8080,10.244.2.15:8080 3mIf a Service returns HTTP 503 or connection timeout, run kubectl get endpoints <service-name>. If the ENDPOINTS column says <none>, check if your Service’s spec.selector labels match the Pod’s metadata.labels exactly!
6. Summary & Next Steps#
Services solve L4 transport-layer networking (IPs and Ports). However, creating a separate LoadBalancer service for every single microservice is cost-prohibitive in public clouds (each AWS NLB costs ~$20/month).
In Episode 06: Ingress Controllers & HTTP Path Routing, we will learn how an Ingress Controller acts as a single smart HTTP/S proxy managing L7 path-based and host-based routing across all your services!

