| |

LFCE 140 ๐Ÿง Kubernetes โ€” Services

โฑ๏ธ Estimated read: 11 min | |

A Kubernetes Service is an abstraction that defines a logical set of Pods and a policy for accessing them. Pods are ephemeral: they are created, deleted, and replaced with new IP addresses. A Service provides a stable virtual IP address and DNS name that front-end clients can use, while the Service’s selector determines which Pods receive the traffic . The Service is the layer that decouples the consumer from the Pod’s lifecycle, and the type field determines how the Service is exposed: internally to the cluster, on a node port, through a cloud load balancer, or as an alias for an external DNS name.

This chapter covers the four Service typesโ€”ClusterIP, NodePort, LoadBalancer, and ExternalNameโ€”the selector mechanism that binds a Service to Pods, the port mapping between the Service and the container, and the headless Service that returns Pod IPs directly instead of a virtual IP.

Key point: A Service selects Pods by label. If the selector does not match the Pods’ labels, the Service has no endpoints and all traffic fails. The kubectl get endpointslices command is the first diagnostic step when a Service is not working.


Why Services exist

The Pod IP problem. Every Pod gets its own IP address, but that address changes when the Pod is recreated. A Deployment that replaces a failed Pod creates a new Pod with a new IP. A client that hardcodes a Pod IP breaks on the next restart. A Service provides a stable ClusterIP and DNS name that survive Pod churn .

The discovery problem. A front-end needs to reach a back-end without knowing which Pods exist or where they are running. The Service’s selector matches the back-end Pods by label, and the Service’s DNS name (my-service.default.svc.cluster.local) resolves to the Service’s virtual IP. The client uses the name; Kubernetes handles the routing .

The exposure problem. Not every service should be reachable from outside the cluster. A database should only be reachable from the application tier. A web front-end should be reachable from the internet. The Service type field determines the exposure level, from internal-only (ClusterIP) to cloud load balancer (LoadBalancer) .


a. ClusterIP: the default internal Service

ClusterIP is the default type. It allocates a virtual IP address that is reachable only from within the cluster. The Service’s DNS name resolves to this IP, and kube-proxy on each node routes traffic from the virtual IP to one of the selected Pods .

apiVersion: v1
kind: Service
metadata:
  name: api-backend
spec:
  type: ClusterIP
  selector:
    app: api-backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

The port is the port the Service exposes. The targetPort is the port the container listens on. A client calls the Service at api-backend:80, and the request is forwarded to a selected Pod at port 8080 . The targetPort can also be a named port that matches the container’s ports field.

A ClusterIP Service is appropriate for internal communication: the front-end calls the back-end, the back-end calls the database, and none of these are reachable from outside the cluster.


b. NodePort: exposing a port on every node

NodePort is a superset of ClusterIP. It allocates a ClusterIP for internal traffic and additionally opens a port on every node in the cluster. External clients call NodeIP:NodePort, and the request is forwarded to the Service’s selected Pods .

apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080

The nodePort value is in the range 30000โ€“32767. If it is omitted, Kubernetes allocates one automatically. The Service is reachable in two ways: internally at ClusterIP:port, and externally at NodeIP:nodePort .

NodePort is useful when the cluster does not have a cloud load balancer, or when a specific port must be reachable on the nodes for an external load balancer to use. The documentation notes that some cluster configurations use a NodePort Service behind an external application load balancer .


c. LoadBalancer: a cloud load balancer

LoadBalancer is a superset of NodePort. It allocates a ClusterIP, a NodePort, and additionally provisions an external load balancer through the cloud provider’s integration. The load balancer gets a stable external IP address, and traffic from the internet reaches the Service through it .

apiVersion: v1
kind: Service
metadata:
  name: web-lb
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

The external IP appears in the Service’s status.loadBalancer.ingress field after the cloud controller provisions the load balancer. In GKE, a regional external passthrough Network Load Balancer is created, and the IP is visible in kubectl get service . The loadBalancerClass field can select a non-default load balancer implementation .

LoadBalancer is the standard way to expose a service to the internet on a cloud-managed cluster. On a self-hosted cluster without a cloud provider, the external IP remains <pending> because there is no controller to provision the load balancer.


d. ExternalName and headless Services

ExternalName is fundamentally different from the other three. It does not select Pods and does not allocate an IP address. It maps the Service’s DNS name to an external DNS name via a CNAME record. A client inside the cluster calls the Service’s name, and the DNS resolver returns the external name .

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: db.example.com

A client calls external-db.default.svc.cluster.local, and DNS resolves it to db.example.com. No proxying is involved; the client connects directly to the external address. This is useful for migrating a service out of the cluster without changing the clients’ configuration .

A headless Service is created by setting clusterIP: None on a ClusterIP Service. It does not allocate a virtual IP. Instead, DNS returns the IP addresses of the individual Pods that match the selector. This allows a client to connect to a specific Pod rather than a load-balanced virtual IP, which is useful for StatefulSets and for clients that need to manage their own connection pooling .


Complete Example Session

# ============================================
# PART 1: A DEPLOYMENT TO EXPOSE
# ============================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
# ============================================
# PART 2: CLUSTERIP SERVICE
# ============================================
apiVersion: v1
kind: Service
metadata:
  name: web-clusterip
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
# ============================================
# PART 3: VERIFY THE SERVICE
# ============================================
kubectl apply -f deployment.yaml -f service.yaml
kubectl get service web-clusterip

# NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
# web-clusterip   ClusterIP   10.96.123.45    <none>        80/TCP
# ============================================
# PART 4: CHECK THE ENDPOINTS
# ============================================
kubectl get endpointslices -l kubernetes.io/service-name=web-clusterip

# NAME              ADDRESSTYPE   PORTS   ENDPOINTS
# web-clusterip-xx   IPv4          80      10.244.1.5,10.244.2.3,10.244.3.7
# ============================================
# PART 5: NODEPORT SERVICE
# ============================================
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
# ============================================
# PART 6: ACCESS VIA NODEPORT
# ============================================
# Get a node IP
kubectl get nodes -o wide

# curl http://<node-ip>:30080
# ============================================
# PART 7: LOADBALANCER SERVICE
# ============================================
apiVersion: v1
kind: Service
metadata:
  name: web-lb
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
# ============================================
# PART 8: CHECK THE EXTERNAL IP
# ============================================
kubectl get service web-lb

# NAME     TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)
# web-lb   LoadBalancer   10.96.123.46    203.0.113.10     80:31234/TCP
# ============================================
# PART 9: EXTERNALNAME SERVICE
# ============================================
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: db.example.com
# ============================================
# PART 10: HEADLESS SERVICE
# ============================================
apiVersion: v1
kind: Service
metadata:
  name: web-headless
spec:
  clusterIP: None
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

The ten parts covered the Deployment, the ClusterIP Service, verification, endpoints, the NodePort Service, NodePort access, the LoadBalancer Service, the external IP, the ExternalName Service, and the headless Service.


Quick Reference

Service types

TypeScopeExternal accessUse case
ClusterIPInternalNoInternal service communication
NodePortCluster nodesYes (NodeIP:NodePort)No cloud LB, specific port
LoadBalancerCloud LBYes (external IP)Internet-facing on cloud
ExternalNameDNS aliasNo (CNAME)External service alias
Headless (clusterIP: None)DNS returns Pod IPsNoStatefulSets, direct Pod access

Port mapping

FieldMeaning
portPort the Service exposes
targetPortPort the container listens on
nodePortPort on each node (30000โ€“32767)

Selector and endpoints

ItemDetail
Selectorspec.selector matches Pod labels
EndpointsCreated automatically when selector matches
No selectorManual Endpoints or ExternalName
Check endpointskubectl get endpointslices -l kubernetes.io/service-name=<name>

DNS names

FormatExample
ClusterIP<service>.<namespace>.svc.cluster.local
HeadlessReturns Pod IPs directly
ExternalNameCNAME to externalName

Best Practices

โœ… Do This:

# Use ClusterIP for internal services
type: ClusterIP                                            # โœ…
# Always set targetPort explicitly
ports:
  - port: 80
    targetPort: 8080                                       # โœ…
# Verify endpoints after creating a Service
kubectl get endpointslices -l kubernetes.io/service-name=<name> # โœ…
# Use ExternalName for external DNS aliases
type: ExternalName
externalName: db.example.com                               # โœ…

โŒ Don’t Do This:

# Use LoadBalancer for an internal-only service
type: LoadBalancer  # provisions a cloud LB unnecessarily # โŒ
# Mismatch selector and Pod labels
selector: { app: web }  # Pods have app: nginx             # โŒ
# Hardcode nodePort without checking for conflicts
nodePort: 30080  # may already be in use                   # โŒ
# Use a headless Service for a load-balanced front-end
clusterIP: None  # no load balancing                       # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Service has no endpointsSelector does not match Pod labelsFix the selector or the Pod labels
External IP is <pending>No cloud provider or load balancer controllerUse NodePort or install a load balancer
NodePort not reachableFirewall blocks the port on the nodeOpen the node port in the firewall
targetPort connection refusedContainer not listening on that portCheck the container’s ports and the application
ExternalName does not resolveCoreDNS not running or wrong externalNameCheck CoreDNS and the CNAME value

Real-World Examples

1. ClusterIP Service

spec:
  type: ClusterIP
  selector: { app: api }
  ports: [{ port: 80, targetPort: 8080 }]

2. NodePort Service

spec:
  type: NodePort
  selector: { app: web }
  ports: [{ port: 80, targetPort: 80, nodePort: 30080 }]

3. LoadBalancer Service

spec:
  type: LoadBalancer
  selector: { app: web }
  ports: [{ port: 80, targetPort: 80 }]

4. ExternalName Service

spec:
  type: ExternalName
  externalName: db.example.com

5. Headless Service

spec:
  clusterIP: None
  selector: { app: db }

6. Check endpoints

kubectl get endpointslices -l kubernetes.io/service-name=web

7. Expose a Deployment imperatively

kubectl expose deployment web --type=ClusterIP --port=80 --target-port=80

8. Get Service details

kubectl describe service web-clusterip

9. DNS name

web-clusterip.default.svc.cluster.local

10. NodePort range

30000โ€“32767

Visual

The Service abstraction

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  SERVICE: web-clusterip (10.96.123.45)                       โ”‚
โ”‚                                                              โ”‚
โ”‚  selector: app=web                                           โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ”œโ”€โ”€ Pod 1 (10.244.1.5)                                โ”‚
โ”‚       โ”œโ”€โ”€ Pod 2 (10.244.2.3)                                โ”‚
โ”‚       โ””โ”€โ”€ Pod 3 (10.244.3.7)                                โ”‚
โ”‚                                                              โ”‚
โ”‚  Client calls web-clusterip:80                               โ”‚
โ”‚  kube-proxy routes to one of the Pods at port 80            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Service type hierarchy

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ClusterIP                                                   โ”‚
โ”‚    โ””โ”€โ”€ NodePort (adds node port)                             โ”‚
โ”‚          โ””โ”€โ”€ LoadBalancer (adds cloud LB)                    โ”‚
โ”‚                                                              โ”‚
โ”‚  ExternalName (separate, DNS alias)                          โ”‚
โ”‚  Headless (ClusterIP with clusterIP: None)                   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Port mapping

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Client โ”€โ”€โ–บ Service:port (80) โ”€โ”€โ–บ Pod:targetPort (8080)     โ”‚
โ”‚                                                              โ”‚
โ”‚  port: the Service's port                                    โ”‚
โ”‚  targetPort: the container's port                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

NodePort access

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  External client                                             โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  NodeIP:30080 โ”€โ”€โ–บ Service โ”€โ”€โ–บ Pod:targetPort                โ”‚
โ”‚                                                              โ”‚
โ”‚  The same port is open on every node.                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Default typeClusterIP
Internal DNS<service>.<namespace>.svc.cluster.local
SelectorMatches Pod labels
portService port
targetPortContainer port
NodePort range30000โ€“32767
LoadBalancerCloud-provisioned external IP
ExternalNameCNAME to external DNS
HeadlessclusterIP: None, returns Pod IPs
Endpoints checkkubectl get endpointslices -l kubernetes.io/service-name=<name>

Key takeaways:

  • A Service provides a stable virtual IP and DNS name for a set of Pods. The Pods are selected by label, and the Service’s ClusterIP is what clients use. The Pod IPs change; the Service IP does not .
  • ClusterIP is the default and the foundation. It is reachable only from inside the cluster. NodePort and LoadBalancer are supersets that add external access .
  • The selector must match the Pod labels. If spec.selector does not match the Pods’ metadata.labels, the Service has no endpoints and traffic fails. kubectl get endpointslices shows whether the Service found its Pods .
  • NodePort opens a port on every node. External clients use NodeIP:nodePort. The port is in the 30000โ€“32767 range. NodePort is useful when no cloud load balancer is available .
  • LoadBalancer provisions a cloud load balancer. The external IP appears in status.loadBalancer.ingress after the controller creates the load balancer. On a self-hosted cluster without a cloud provider, the IP remains <pending> .
  • ExternalName is a DNS alias, not a proxy. It maps the Service’s name to an external DNS name via a CNAME. No Pods are selected, and no traffic is proxied .
  • A headless Service returns Pod IPs instead of a virtual IP. It is used for StatefulSets and for clients that need to connect to a specific Pod rather than a load-balanced Service .

Remember: A Service is the stable front door to a set of Pods. The selector determines which Pods are behind the door. The type determines who can reach the door: ClusterIP for internal clients, NodePort for node-level access, LoadBalancer for the internet, and ExternalName for a DNS alias. The port and targetPort determine how traffic is forwarded. When a Service is not working, check the endpoints first: if the selector does not match the Pod labels, there is nothing behind the door.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!