Kubernetes Services: ClusterIP, NodePort, LoadBalancer, Ingress
Master Kubernetes service types and Ingress controllers to expose your applications inside and outside the cluster with proper load balancing and routing.
Kubernetes pods get new IP addresses every time they reschedule, which breaks any hardcoded connection strings. Services fix this by giving you a stable virtual IP that load-balances across your pods. ClusterIP covers internal microservice traffic, while NodePort, LoadBalancer, and Ingress handle external access — each with different implications for cost, routing flexibility, and operational overhead. For most production HTTP/HTTPS workloads, Ingress plus a single shared LoadBalancer beats running a separate LoadBalancer per service. TCP/UDP protocols or non-HTTP services are where you still need LoadBalancer directly.
Kubernetes Services: ClusterIP, NodePort, LoadBalancer, and Ingress
Pods in Kubernetes are ephemeral. They get IP addresses assigned when they start and those addresses change when pods reschedule. If you want your application to be reachable, you need something stable. Kubernetes Services provide that stability by creating a persistent endpoint for a set of pods.
This post covers the four service types, when to use each one, and how Ingress controllers extend routing beyond simple port forwarding.
If you need to understand the basics of Kubernetes first, check the Kubernetes fundamentals post. For advanced networking patterns, see the Advanced Kubernetes post.
Introduction
Pod IPs change when pods reschedule. If your app depends on a fixed address, it breaks constantly. Kubernetes Services give you a stable virtual IP that load-balances across matching pods, which is what makes microservices actually work in practice.
External traffic is a separate problem. Kubernetes gives you four service types and Ingress resources for different exposure patterns. Picking the wrong one costs money (over-provisioned load balancers) or creates weird URLs that users cannot reach. Getting it right means understanding how traffic actually flows from the outside world to your container.
This post covers the four service types, when to use each, and how Ingress controllers add HTTP routing on top. You will see how a request travels from a user to your pod, the trade-offs between each approach, and how to fix the most common networking problems you will hit in production.
Service Types Comparison
Kubernetes offers four service types:
Decision Matrix: Choosing the Right Service Type
| Access Scenario | Service Type | Example |
|---|---|---|
| Internal microservice-to-microservice | ClusterIP | API to database |
| Expose single node for debugging | NodePort | Dev environment access |
| Production HTTP/HTTPS traffic | Ingress | Web app frontend |
| TCP/UDP without Ingress complexity | LoadBalancer | Custom protocol, legacy apps |
| Cross-cluster service discovery | ExternalName | Integrate external service |
Skip NodePort for production HTTP services. The port range (30000-32767) is awkward, and node IPs change in dynamic clusters.
Skip LoadBalancer per microservice. One load balancer per team or product boundary is usually enough. Provisioning a cloud LB for every pod gets expensive fast.
ClusterIP is internal only. If you need external access, ClusterIP is not your answer.
Traffic Flow Architecture
Here is how a request moves from an external user down to a pod:
flowchart TD
User([External User]) --> Internet[Internet]
Internet --> DNS[DNS Resolution<br/>api.example.com]
DNS --> ALB[Cloud Load Balancer<br/>NLB/ALB]
ALB --> Ingress[Ingress Controller<br/>NGINX/Traefik]
Ingress --> SvcClusterIP[ClusterIP Service<br/>kube-proxy routes to Pods]
Ingress --> SvcNodePort[NodePort Service<br/>:30000-32767 on each Node]
Ingress --> SvcLB[LoadBalancer Service<br/>Cloud-managed LB]
SvcClusterIP --> Pod1[Pod<br/>app=v1]
SvcClusterIP --> Pod2[Pod<br/>app=v1]
SvcClusterIP --> Pod3[Pod<br/>app=v1]
SvcNodePort --> Node1[Node<br/>kube-proxy]
Node1 --> Pod1
SvcLB --> Pod1
SvcLB --> Pod2
SvcLB --> Pod3
For most production HTTP/HTTPS workloads, the typical path is: User → DNS → Load Balancer → Ingress → ClusterIP Service → Pods.
For TCP services without Ingress, the path skips the Ingress step and goes directly: User → DNS → Load Balancer Service → Pods.
NodePort is mainly for development. The path there is: User → Node IP and port → Pod.
Service Types Overview
| Type | Access Scope | Use Case |
|---|---|---|
| ClusterIP | Internal only | Microservices within the cluster |
| NodePort | Exposes on each node IP | Development, simple external access |
| LoadBalancer | External via cloud LB | Production external access |
| ExternalName | Maps to external DNS | Integrating external services |
ClusterIP is the default. You get an internal cluster IP that pods can use to communicate with each other. The other types expose services outside the cluster.
ClusterIP for Internal Access
ClusterIP is the most common service type. It creates an internal IP that load-balances traffic across all matching pods. Other pods in the cluster use the service name to reach your application.
apiVersion: v1
kind: Service
metadata:
name: api-backend
namespace: production
spec:
type: ClusterIP
selector:
app: api-backend
version: v2
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
- name: grpc
protocol: TCP
port: 50051
targetPort: 50051
The targetPort can be a number or a name that matches the container port. Using names makes it easier to update ports without changing the service.
Within the cluster, pods access the service using its fully qualified name:
http://api-backend.production.svc.cluster.local
Or just the service name if they are in the same namespace:
http://api-backend
DNS is automatic. Kubernetes maintains a DNS entry for every service.
Headless Services for StatefulSets
Set clusterIP: None to create a headless service. Instead of load balancing, DNS returns the pod IPs directly. This is useful for StatefulSets where clients need to discover individual pod addresses.
apiVersion: v1
kind: Service
metadata:
name: postgres-cluster
namespace: database
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
With a headless service, DNS queries return A records for each pod: postgres-cluster-0.postgres-cluster.database.svc.cluster.local, and so on.
NodePort for Development
NodePort opens a port on every node in the cluster. Traffic arriving at http://<node-ip>:<node-port> gets routed to the service. The port range defaults to 30000-32767.
apiVersion: v1
kind: Service
metadata:
name: web-frontend-nodeport
spec:
type: NodePort
selector:
app: web-frontend
ports:
- port: 80
targetPort: 80
nodePort: 30080
Setting nodePort is optional. Kubernetes assigns one from the default range if you omit it.
NodePort works for development and quick demos. For production, use LoadBalancer or Ingress. NodePort bypasses some load balancing logic and exposes infrastructure details you may not want.
LoadBalancer with Cloud Controllers
On cloud providers that support external load balancers (AWS, GCP, Azure), the LoadBalancer type provisions a managed load balancer and routes traffic to your service.
apiVersion: v1
kind: Service
metadata:
name: web-frontend-lb
namespace: production
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
type: LoadBalancer
selector:
app: web-frontend
ports:
- port: 80
targetPort: 80
- port: 443
targetPort: 443
The annotation service.beta.kubernetes.io/aws-load-balancer-type: "nlb" creates a Network Load Balancer on AWS instead of a Classic Load Balancer.
Cloud controllers create load balancers with health checks pointed at your pods. They also handle SSL termination if you configure certificates.
For SSL termination on the load balancer, you need to annotate the service with the certificate ARN:
annotations:
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:us-east-1:123456789:certificate/abc123"
Ingress Controllers and Rules
Ingress is not a service type. It is a Kubernetes resource that provides HTTP/HTTPS routing rules. An Ingress controller implements the routing. Without a controller, Ingress resources do nothing.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /users
pathType: Prefix
backend:
service:
name: users-api
port:
number: 80
- path: /products
pathType: Prefix
backend:
service:
name: products-api
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-console
port:
number: 80
tls:
- hosts:
- api.example.com
- admin.example.com
secretName: example-com-tls
The ingressClassName field specifies which Ingress controller handles this Ingress. Common controllers include NGINX Ingress Controller, Traefik, and cloud-provider ingress controllers.
Path rewriting
Backend services rarely expose the same path structure as your Ingress routes. A frontend at example.com/users might proxy to an API that expects /users at its root. Without path rewriting, requests to /api/v1/users arrive at the backend as /api/v1/users, and the backend returns 404 because it only knows about /users.
The NGINX ingress controller handles this with the rewrite-target annotation:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
The $2 holds the second captured group from a regex prefix match. Pair it with a regex path type:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /api/v1(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: users-api
port:
number: 80
Requests to /api/v1/users hit the backend as /users. Requests to /api/v1/products/42 hit the backend as /products/42. The regex (.*) passes everything after /api/v1/ as-is, preserving query strings and nested paths.
This pattern shows up constantly when migrating from a monolith. You can route /api/v2/* to the new service and /legacy/* to the old one, rewriting paths to match whatever contract each backend expects.
Rate limiting via Ingress
Ingress controllers apply rate limiting at the edge, before traffic hits your backend services. NGINX Ingress Controller supports two limit types: requests per second (RPS) and concurrent connections. RPS gates how many new connections arrive per second. The connection limit caps simultaneous keep-alive connections from a single client. Set both when you want to stop burst floods and sustained high concurrency from the same source.
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-connections: "5"
When a client exceeds either limit, NGINX returns HTTP 429 with a Retry-After header. 429 tells the client to back off and retry later, which is exactly what you want for rate limiting. A 503 signals a dead backend, which misleads monitoring. To distinguish rate limits from real errors, use a custom error page via nginx.ingress.kubernetes.io/limit-connections.
The limit-burst annotation sets the token bucket size, or how many requests a client can fire off before the RPS limit kicks in. Without it, every request above the RPS rate gets rejected immediately. With limit-burst: 20, a client can burst 20 requests and only the 21st starts throttling. Set burst higher for batch jobs and sync workers. Set it lower for user-facing APIs where you want strict fairness.
These annotations apply per-client IP. In a cluster with many users behind a corporate NAT, one IP hitting the limit affects everyone behind it. For more granular control, annotate per-service instead of globally, or use Envoy Rate Limiter or Istio for header-based or JWT-claim-based limits.
Security Considerations
Services enable connectivity, but you should restrict which pods can communicate with which. Kubernetes Network Policies (covered in a separate post) let you enforce microsegmentation between pods.
For external access, prefer Ingress with TLS termination over NodePort. Ingress provides path-based routing and centralized SSL management.
Avoid exposing services directly with LoadBalancer unless you need layer 3/4 load balancing. Most web traffic works fine with Ingress and a single load balancer.
Trade-off Analysis
ClusterIP vs Headless Services
The difference between ClusterIP and headless comes down to who manages pod discovery. With ClusterIP, Kubernetes gives you a stable virtual IP and load-balances traffic across all matching pods. Your client hits one address and Kubernetes handles the rest. With headless (clusterIP: None), there is no virtual IP. DNS returns individual pod A records instead, and your client is responsible for tracking which pods exist and picking one.
| Aspect | ClusterIP | Headless (ClusterIP: None) |
|---|---|---|
| Load balancing | Built-in, round-robin | None — client manages pod discovery |
| DNS resolution | Single service IP | Multiple A records (one per pod) |
| Use case | Stateless microservices | StatefulSets, leader election |
| Client complexity | Low | High |
For stateless services, ClusterIP is almost always the right choice. Your client does not need to know which pod is which. For StatefulSets, headless is necessary because pods have stable identities — a PostgreSQL replica needs to know it is connecting to postgres-cluster-1, not just any available pod. The client also needs to track which pod is primary for writes versus read-only replicas.
With ClusterIP, retry logic is simple: reconnect to the service IP and Kubernetes routes to a healthy pod. With headless, your client needs to watch DNS, handle pod IP changes, and manage elections if it is a primary-secondary setup. Only go headless when your application actually needs stable per-pod addressing.
Service Type Selection Trade-offs
Each service type maps to a specific access pattern. Pick the wrong one and you either limit your traffic or pay for something you do not need.
| Service Type | Pros | Cons |
|---|---|---|
| ClusterIP | Simple, internal only | No external access |
| NodePort | Works without cloud provider | High ports, security concerns, no LB |
| LoadBalancer | Full cloud integration | Cost per service, overkill for HTTP |
| Ingress | Host/path routing, SSL termination | Extra controller deployment |
ClusterIP is the workhorse. Use it for every internal service: API to database, frontend to backend, worker to queue. Nothing external can reach it, which is exactly what you want for internal traffic.
NodePort exists for debugging and single-node clusters. It is not a production HTTP solution because the port range (30000-32767) is not firewall-friendly and node IPs are not stable in managed clusters. Use it for temporary access to internal services during development, nothing more.
LoadBalancer is for TCP/UDP protocols that Ingress cannot handle. Cassandra, Redis in direct mode, custom protocols that expect port-level routing. If it is HTTP/HTTPS, use Ingress with a shared LoadBalancer. One LB handling all your HTTP services costs less than one LB per service.
Ingress is the HTTP/HTTPS layer. It routes by host and path, terminates SSL, and shares a single LoadBalancer across dozens of services. The trade-off is deploying and operating an Ingress controller. If you are already on a cloud provider with a managed Ingress controller (GKE ingress, EKS ALB ingress), this trade-off is worth taking.
Ingress vs LoadBalancer per Service
Use one LoadBalancer for Ingress, not one per service. The cost and quota difference is real at scale.
| Criteria | Ingress Controller | One LoadBalancer per Service |
|---|---|---|
| Cost | One LB shared across all services | One LB per service |
| SSL management | Centralized | Per-service or per-LB |
| HTTP/HTTPS routing | Path-based, host-based | Port-based only |
| Operational overhead | Controller deployment | Cloud quota management |
AWS starts you with 20 classic Load Balancers per region. GCP allows 50 forwarding rules per project. In a 20-service cluster, you burn through those limits before shipping anything if you provision one LB per service. With an Ingress controller, one NLB or ALB handles all HTTP traffic for all services across all namespaces.
SSL management is simpler with Ingress too. You store one certificate per hostname and the controller terminates TLS for every backend service behind it. With per-service LBs, you either terminate SSL at the LB (each one needs its own certificate) or at the backend (each service handles its own certs, more operational work).
Per-service LBs make sense for TCP protocols Ingress cannot handle. If you are running Cassandra, custom RPC, or any non-HTTP workload, those need layer 4 routing and per-service LBs are the right tool. Name-based HTTP routing is almost always Ingress.
For most production HTTP/HTTPS workloads, Ingress with a single shared LoadBalancer is the most cost-effective approach.
Production Failure Scenarios
ClusterIP Service Not Reachable After Pod Restart
When a pod restarts, its IP changes. If your application hardcodes pod IPs instead of using the ClusterIP service name, communication breaks.
Symptoms: Pod-to-pod communication fails after restarts, Connection refused errors.
Diagnosis:
kubectl get pod -o wide # Check pod IPs
kubectl get endpoints <service-name> # Should list pod IPs
kubectl describe pod <pod-name> # Check status
Mitigation: Always use the ClusterIP service DNS name for pod-to-pod communication, never pod IPs.
NodePort Service Port Conflicts
When two services request the same nodePort value, Kubernetes rejects the second one with nodePort port is already allocated. This sneaks into clusters through hardcoded ports in Helm charts or kustomization overlays that nobody validated against existing allocations. In GitOps workflows, the conflict only shows up at apply time, not at render time, so code review misses it.
To diagnose an active conflict, filter services by nodePort in the 30000-32767 range:
kubectl get svc -A -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.type}{"\t"}{.spec.ports[*].nodePort}{"\n"}{end}' | grep -v '<none>'
The output lists every service with an assigned nodePort. When provisioning a new service, pick a free port or omit nodePort entirely and let Kubernetes assign one. Conflicts only happen when two manifests hardcode the same value.
Prevention across teams requires a shared allocation record. Some teams keep a ConfigMap with reserved ports and owners. Others skip NodePort entirely and use LoadBalancer or Ingress, which removes manual port management from the picture. If you run NodePort in a multi-tenant cluster, treat nodePort allocations like ephemeral port ranges: assign them centrally and never hardcode them in definitions that migrate between environments.
LoadBalancer Service Stays Pending
On cloud providers, LoadBalancer provisioning can fail due to quota limits, missing IAM permissions, or unsupported service configurations.
Symptoms: ExternalLB Pending status persists for minutes.
Diagnosis:
kubectl describe service <name> -n <namespace>
# Check events for error messages
kubectl get events --sort-by='.lastTimestamp' -n <namespace>
Mitigation: Verify cloud IAM permissions for the service account. Check cloud quota for load balancers. Use annotations to specify the correct load balancer type (NLB vs CLB on AWS).
Common Pitfalls / Anti-Patterns
Exposing Services Directly Without Ingress
Provisioning a LoadBalancer service per microservice hits cloud resource quotas fast. AWS default quota is 20 classic LBs per region; GCP allows 50 forwarding rules per project. In a 50-service cluster, you burn through those limits before shipping anything. Requesting quota increases requires AWS account reviews or GCP billing triage — not something you want mid-incident.
Cost is the other problem. Each cloud LB bills hourly regardless of actual traffic. An idle staging environment with 20 microservices paying $0.025/hour per NLB costs $360/month in pure waste. Ingress controllers share a single LB across all HTTP/HTTPS services, spreading that cost across dozens of namespaces and teams instead of burning budget on redundant infrastructure.
That said, LoadBalancer is the right call for TCP protocols Ingress cannot handle — Cassandra, Redis in direct mode, or legacy RPC systems that expect port-level routing. Some teams also run one LoadBalancer per product boundary rather than per service, letting Ingress handle internal HTTP routing for each product. Trading some flexibility for a simpler network topology is a reasonable trade-off.
The rule: default to Ingress for all HTTP/HTTPS workloads. Only reach for LoadBalancer when you need layer 4 routing for a non-HTTP protocol or when connecting to a service that predates Ingress support.
Using NodePort in Production
NodePort allocates a port from a fixed range (30000-32767) on every node in the cluster. When a pod receives traffic, kube-proxy routes it from that high port to the target pod. This sounds convenient for dev environments, but the model breaks down in production for several reasons.
The port range itself is the first problem. Standard HTTP is 80 or 443. Users hitting https://example.com:31234 see a port that looks like a VPN or proxy. Corporate firewalls routinely block non-standard ports, dropping traffic silently before it reaches your cluster. In regulated industries with strict outbound policies, high ports may be blocked entirely.
Node IP stability is the second problem. In managed Kubernetes (EKS, GKE, AKS), nodes live in an auto-scaling group. When scale-in events occur, terminated nodes return their IPs to the VPC pool and new nodes pick up different ones. Your NodePort service still exposes port 30080 on the new IPs, but any DNS records or firewall rules pointing to old node IPs go stale. You either maintain a dynamic discovery layer or accept intermittent outages during node churn.
Security is the third concern. Opening a port on every node means your service is reachable from anywhere in the cluster network. If nodes share a subnet with other workloads, a compromised pod can hit the NodePort directly, bypassing your Ingress controller’s authentication layer entirely. ClusterIP and Ingress shrink this attack surface by only exposing services through explicitly routed paths.
For local development, NodePort is fine. For anything beyond a quick demo, use Ingress with a proper HTTP/HTTPS stack or a LoadBalancer for TCP services. The routing clarity, SSL termination, and firewall-friendliness of standard ports justifies the extra setup time.
Skipping Health Checks on Headless Services
StatefulSets give pods stable identities: postgres-0 is always the primary, postgres-1 and postgres-2 are replicas. Clients querying the headless service DNS get all pod IPs and must pick one. Without health checks, clients can try to write to a pod that is still replaying WAL segments, causing failed queries, split-brain, or worse depending on the database.
The fix is a readinessProbe on the StatefulSet pods. Without it, the pod enters DNS as soon as its container process starts, which can be well before it can actually handle queries. A PostgreSQL replica signals readiness by responding to a replication check or a simple TCP connection that advances the query buffer.
spec:
containers:
- name: postgres
readinessProbe:
exec:
command:
- sh
- -c
- "psql -h localhost -U postgres -c 'SELECT 1' -q"
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
failureThreshold: 3 tells Kubernetes to mark the pod not ready after three consecutive failures, giving the database time to catch up on WAL replay. Without it, transient startup load causes the pod to flap in and out of DNS, which sends clients into reconnection loops through PgBouncer.
Liveness probes are a separate concern and rarely what you want for StatefulSets. A liveness probe that kills the container because the database is momentarily slow creates more problems than it solves. Stick with readiness probes for traffic management and let PostgreSQL handle internal restart logic through max_connections and statement_timeout.
Interview Questions
Expected answer points:
- ClusterIP provides internal-only access within the cluster — only other pods can reach it
- NodePort exposes the service on a high port (30000-32767) on every node's IP — useful for dev but not production HTTP
- LoadBalancer provisions an external cloud load balancer (AWS ELB, GCP LB, Azure LB) for production external access
- Ingress is not a service type but a routing layer for HTTP/HTTPS with host and path-based rules
Expected answer points:
- kube-proxy watches for Service and Endpoint changes on each node
- It programs iptables or IPVS rules to redirect traffic to the service IP:port to backend pod IPs
- Load balancing is performed at the kernel level using randomized selection among backend pods
- When endpoints change (pod restarts), iptables/IPVS rules are updated
Expected answer points:
- A headless service is created by setting `clusterIP: None`
- No ClusterIP is assigned — DNS returns individual pod A records instead
- Clients can discover pod IPs directly for StatefulSet workloads where each pod needs a stable identity
- Useful for leader election, master-slave database setups, and scenarios requiring direct pod-to-pod communication
Expected answer points:
- targetPort specifies the port on the pod that receives traffic
- It can be a number or a named port matching the container's port definition
- Using named ports makes updates easier — changing the container port does not require changing the service
- If omitted, targetPort defaults to the `port` value
Expected answer points:
- NodePort uses a non-standard port range (30000-32767) that is not typical for end users
- Node IPs are dynamic in managed clusters — using them requires additional discovery or stable host entries
- No built-in SSL termination, path-based routing, or host-based routing
- Traffic bypasses load balancing logic that Ingress or LoadBalancer types provide
Expected answer points:
- Ingress is a Kubernetes resource; an Ingress controller is the implementation that acts on it
- Ingress provides HTTP/HTTPS routing with host-based and path-based rules
- LoadBalancer is layer 4 (TCP/UDP) — one per service, no path routing
- A single Ingress controller can route many services through one shared LoadBalancer, reducing cost
Expected answer points:
- `service.beta.kubernetes.io/aws-load-balancer-type: "nlp"` — specifies NLB vs Classic LB
- `service.beta.kubernetes.io/aws-load-balancer-ssl-cert:
` — assigns ACM certificate for SSL termination - `service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp"` — for HTTP vs TCP backends
- `service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"` — distributes traffic across AZs
Expected answer points:
- kube-proxy updates iptables rules to remove the terminated pod from the endpoint list
- In-flight requests to the terminated pod fail with connection reset
- New requests route to remaining healthy pods
- Clients should implement retry logic with backoff to handle transient failures during pod churn
Expected answer points:
- Default is `None` — iptables routes each connection to a random backend pod
- `ClientIP` affinity hashes the source IP and routes to the same pod for a configurable timeout
- Session affinity is useful for stateful protocols where the client must reach the same backend
- It is not a substitute for sticky sessions at the application level — it only guarantees the same backend pod
Expected answer points:
- Services do not provide fine-grained traffic control (canary releases, traffic splitting)
- No built-in circuit breaking, retry policies, or rate limiting at the service mesh level
- NetworkPolicies provide L3/L4 isolation but not L7 traffic management
- Service mesh solutions (Istio, Linkerd) extend Services with L7 control, mTLS, and observability
Expected answer points:
- Verify endpoints exist: `kubectl get endpoints
` should list pod IPs - Check pod selector labels match: `kubectl describe service
` shows selector - Verify pods are running and ready: `kubectl get pods -l app=
` - Check kube-proxy logs on nodes for iptables programming issues
- Test connectivity from another pod using the service DNS name
Expected answer points:
- ClusterIP is purely internal — no external access by design
- LoadBalancer triggers cloud provider to provision an external load balancer that routes to the service
- The cloud LB has an external IP/DNS that external clients use
- LoadBalancer is more expensive and best reserved for TCP/UDP protocols or when Ingress is not suitable
Expected answer points:
- Yes — an Ingress spec can contain multiple `rules` entries, each with a different `host`
- Each host can have its own set of paths routing to different backend services
- A single TLS block can cover multiple hosts via multiple entries in the `hosts` list
- Wildcard hosts (e.g., `*.example.com`) can be used to match all subdomains
Expected answer points:
- Exact match: URL path must match the path string exactly (e.g., `/users` matches only `/users`)
- Prefix match: URL path must have the prefix at the start (e.g., `/users` matches `/users`, `/users/123`, `/users/foo/bar`)
- Prefix is more commonly used and is the default if pathType is omitted
- Exact is useful when you need strict path boundaries and no unintended prefix matching
Expected answer points:
- Endpoints is an auto-created resource that lists the IP addresses of pods matching the service selector
- When a pod is added or removed, the Endpoints object is updated automatically
- kube-proxy watches Endpoints and programs routing rules accordingly
- You can create Headless Services where the Endpoints directly drive DNS A records
Expected answer points:
- `Local` preserves the client source IP address — without it, the load balancer SNATs traffic, hiding the original client IP
- With `Local`, traffic is only routed to nodes that have at least one backing pod — other nodes terminate the connection at the LB
- Trade-off: `Local` can create uneven load distribution when pods are not evenly spread across nodes
- Use `Local` when the application needs the original client IP for logging, compliance, or geo-based routing
Expected answer points:
- Health checks are configured on the pod via `readinessProbe` and `livenessProbe` fields, not on the Service itself
- readinessProbe determines if a pod can receive traffic — failing readiness removes the pod from the Service endpoint list
- livenessProbe determines if a pod should be restarted — failing liveness triggers container restart
- Common probe types: `exec` (run a command), `httpGet` (GET request), `tcpSocket` (open a TCP connection)
Expected answer points:
- EndpointSlices are a more scalable replacement for Endpoints, introduced to handle large clusters with many pods
- Endpoints stores all pod IPs in a single object — EndpointSlices splits them across multiple objects of up to 100 IPs each
- EndpointSlices include topology information (region, zone, hostname) for topology-aware routing
- kube-proxy and other controllers consume EndpointSlices the same way they consume Endpoints
Expected answer points:
- The annotation specifies that the NLB should route directly to pod IPs (IP target) rather than node IPs (instance target)
- IP target mode eliminates node-level hop and preserves client source IP (like `externalTrafficPolicy: Local`)
- With IP target, the NLB sends traffic directly to pods across all availability zones simultaneously
- This mode requires the AWS VPC CNI plugin and works with pods that have ENIs attached in the VPC subnet
Expected answer points:
- CoreDNS creates A records for ClusterIP services: `
. .svc.cluster.local` resolves to the ClusterIP - For headless services (ClusterIP: None), CoreDNS creates A records for each pod: `
. .svc.cluster.local` resolves to all pod IPs - SRV records are created for named ports: `
. . .svc.cluster.local` - ExternalName services create CNAME records pointing to the external FQDN specified
Further Reading
- Kubernetes Official Documentation - Services
- Ingress Controllers
- Network Policies
- Cloud Load Balancer Integration
Conclusion
Kubernetes Services provide stable endpoints for your applications. ClusterIP works for internal microservice communication. NodePort is useful for development and testing. LoadBalancer integrates with cloud providers for production external access. Ingress controllers add HTTP/HTTPS routing with host-based and path-based rules, SSL termination, and rate limiting.
Start with ClusterIP for internal traffic. Add Ingress when you need external HTTP/HTTPS access. Use LoadBalancer only when you need TCP-level load balancing or integration with non-HTTP services.
Understanding these networking primitives helps you design services that are reachable, scalable, and secure. For deeper Kubernetes networking concepts like Network Policies, see the Advanced Kubernetes post.
Category
Related Posts
Kubernetes Network Policies: Securing Pod-to-Pod Communication
Implement microsegmentation in Kubernetes using Network Policies to control traffic flow between pods and enforce zero-trust networking.
Cloud Security: IAM, Network Isolation, and Encryption
Implement defense-in-depth security for cloud infrastructure—identity and access management, network isolation, encryption, and security monitoring.
Container Security: Image Scanning and Vulnerability Management
Implement comprehensive container security: from scanning images for vulnerabilities to runtime security monitoring and secrets protection.