Services Basics
This section covers the everyday Service patterns you will use to give ephemeral pods a stable virtual IP and DNS name.
Each example is a small, complete manifest or command you can adapt directly.
Prerequisites
- A running cluster on Kubernetes 1.36.2 with kube-proxy or a CNI dataplane and CoreDNS installed.
kubectlmatching your control plane minor version.- A Deployment whose pods carry a label you can select on, for example
app: web.
Quick check that the pieces exist:
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get deploy -A | grep -i coredns || trueBasic Examples
1. A ClusterIP Service in front of a Deployment
The default Service type, reachable only inside the cluster.
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080selectormatches pods labeledapp: web; those become the backends.portis the port the Service listens on;targetPortis the container port.- No
typemeansClusterIP, so this VIP is cluster-internal only. - Always name ports (
name: http) so probes and other objects can reference them.
2. Resolve a Service by DNS
Every Service gets a DNS record from CoreDNS.
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- \
nslookup web.default.svc.cluster.local- The full name is
<service>.<namespace>.svc.cluster.local. - From the same namespace,
webalone resolves thanks to the search domain. - The record returns the ClusterIP, not pod IPs (for a normal Service).
- If resolution fails, check CoreDNS and the pod's
/etc/resolv.conf.
3. Target a named port instead of a number
Referencing the port by name decouples the Service from container port numbers.
spec:
selector:
app: web
ports:
- name: http
port: 80
targetPort: httptargetPort: httppoints at the container port namedhttp.- The container spec must declare
ports: [{name: http, containerPort: 8080}]. - Renumbering the container port no longer requires editing the Service.
- This is a best practice for long-lived Services.
4. Expose more than one port
A Service can map several ports at once.
spec:
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080
- name: metrics
port: 9090
targetPort: 9090- Each entry needs a unique
namewhen there is more than one port. - Common split:
httpfor traffic,metricsfor Prometheus scraping. - The names show up in EndpointSlices, aiding debugging.
- Keep protocol defaults (
TCP) unless you truly needUDPorSCTP.
5. Inspect the endpoints behind a Service
Confirm which pods a Service is actually routing to.
kubectl get endpointslices -l kubernetes.io/service-name=web
kubectl describe service web- EndpointSlices list the current Ready pod IPs and ports.
- An empty endpoint set almost always means the selector matches nothing or pods are not Ready.
kubectl describe servicesummarizes the selector, ports, and endpoints.- This is the first command to run when traffic disappears.
6. Quickly reach a Service with port-forward
Test a Service from your laptop without exposing it publicly.
kubectl port-forward service/web 8080:80- Local port
8080now tunnels to the Service port80. - Useful for smoke tests before wiring up Ingress or a LoadBalancer.
- The tunnel routes through the API server, so it is for debugging, not production traffic.
- Ctrl-C closes the tunnel.
7. Set readiness so traffic only hits healthy pods
Endpoints follow pod readiness, so define a probe.
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5- Unready pods are pulled from EndpointSlices and stop receiving traffic.
- The pod is not killed - it just drains until it is Ready again.
- This is what makes rolling updates seamless for clients.
- Point the probe at a cheap, dependency-free endpoint.
Intermediate Examples
8. A headless Service for direct pod DNS
Skip the VIP and return pod IPs directly, which StatefulSets need.
apiVersion: v1
kind: Service
metadata:
name: db
spec:
clusterIP: None
selector:
app: db
ports:
- name: pg
port: 5432clusterIP: Nonemakes the Service headless.- DNS returns one A record per Ready pod instead of a single VIP.
- Clients or the app do their own load balancing across those IPs.
- StatefulSet pods also get stable per-pod DNS like
db-0.db.
9. Preserve client source IP on external traffic
Keep the real caller IP for a LoadBalancer Service.
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080Localroutes only to pods on the receiving node, avoiding a second hop.- The client source IP is preserved, which logging and rate limiting need.
- Nodes with no local pod fail the cloud LB health check and are skipped.
- The trade-off is less even spreading than the default
Clusterpolicy.
10. Point a Service at an external address
Alias an out-of-cluster host behind a stable in-cluster name.
apiVersion: v1
kind: Service
metadata:
name: payments-api
spec:
type: ExternalName
externalName: api.payments.example.comExternalNamereturns a CNAME to the external host - no proxying or VIP.- Handy for migrating a dependency into the cluster without changing callers.
- It resolves at DNS time only, so it does not do health checking or TLS.
- Swap the type later once the backend moves in-cluster.
11. Pin a client to one pod with session affinity
Keep a caller on the same backend for a window when you need stickiness.
spec:
selector:
app: web
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
ports:
- name: http
port: 80
targetPort: 8080sessionAffinity: ClientIPkeys routing on the source IP.- The timeout controls how long the affinity lasts before rebalancing.
- Use it sparingly, since it works against even load spreading.
- It is L4 affinity only, not cookie-based stickiness.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).