---
title: "Kubernetes Service Types and DNS-Based Discovery"
description: "How ClusterIP, NodePort, LoadBalancer, ExternalName, and headless services work, when to use each, and how to debug service connectivity."
url: https://agent-zone.ai/knowledge/kubernetes/service-types-and-discovery/
section: knowledge
date: 2026-02-22
categories: ["kubernetes"]
tags: ["services","dns","networking","clusterip","nodeport","loadbalancer"]
skills: ["service-configuration","service-debugging"]
tools: ["kubectl","nslookup","curl"]
levels: ["intermediate"]
word_count: 805
formats:
  json: https://agent-zone.ai/knowledge/kubernetes/service-types-and-discovery/index.json
  html: https://agent-zone.ai/knowledge/kubernetes/service-types-and-discovery/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Kubernetes+Service+Types+and+DNS-Based+Discovery
---


# Kubernetes Service Types and DNS-Based Discovery

Services are the stable networking abstraction in Kubernetes. Pods come and go, but a Service gives you a consistent DNS name and IP address that routes to the right set of pods. Choosing the wrong Service type or misunderstanding DNS discovery is behind a large percentage of connectivity failures.

## Service Types

### ClusterIP (Default)

ClusterIP creates an internal-only virtual IP. Only pods inside the cluster can reach it. This is what you want for internal communication between microservices.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: api-backend
  namespace: production
spec:
  type: ClusterIP
  selector:
    app: api-backend
  ports:
    - port: 8080
      targetPort: 8080
```

Other pods reach this at `api-backend.production.svc.cluster.local:8080`, or just `api-backend:8080` if they are in the same namespace.

### NodePort

NodePort exposes the service on a static port (30000-32767) on every node's IP. Useful for development, minikube setups, or when you need external access without a cloud load balancer.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: web-frontend
spec:
  type: NodePort
  selector:
    app: web-frontend
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080   # optional; Kubernetes assigns one if omitted
```

Access it at `<any-node-ip>:30080`. In minikube, use `minikube service web-frontend --url` to get the routable address.

### LoadBalancer

LoadBalancer provisions an external load balancer through your cloud provider (AWS ELB, GCP LB, etc.). It is a superset of NodePort -- it creates a NodePort and then puts a cloud LB in front of it.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: public-api
spec:
  type: LoadBalancer
  selector:
    app: public-api
  ports:
    - port: 443
      targetPort: 8443
```

On bare metal or minikube, LoadBalancer services stay in `Pending` state forever unless you run MetalLB or use `minikube tunnel`.

### ExternalName

ExternalName creates a CNAME DNS record pointing to an external hostname. No proxying happens. It is just a DNS alias.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: external-db
  namespace: production
spec:
  type: ExternalName
  externalName: mydb.us-east-1.rds.amazonaws.com
```

Pods can now connect to `external-db:5432` and DNS resolves to the RDS hostname. No selector, no endpoints. Be aware that ExternalName does not support ports -- it only does DNS-level redirection. The client must know the correct port.

### Headless Services

A headless service has `clusterIP: None`. Instead of a single virtual IP, DNS returns the individual pod IPs. This is essential for StatefulSets where clients need to reach specific pods (database replicas, Kafka brokers).

```yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
    - port: 5432
```

DNS for `postgres.default.svc.cluster.local` returns A records for every pod matching the selector. For a StatefulSet, each pod also gets a DNS entry: `postgres-0.postgres.default.svc.cluster.local`.

## DNS-Based Service Discovery

Every Service gets a DNS record in the form:

```
<service-name>.<namespace>.svc.cluster.local
```

Pods in the same namespace can use just `<service-name>`. Pods in a different namespace must include the namespace: `<service-name>.<namespace>`. The full FQDN `svc.cluster.local` suffix is rarely needed but eliminates ambiguity.

CoreDNS configures each pod with search domains:

```
search <pod-namespace>.svc.cluster.local svc.cluster.local cluster.local
```

This is why short names like `api-backend` resolve -- the search domain appends the namespace and cluster suffix automatically.

## Debugging Service Connectivity

When a pod cannot reach a service, work through this sequence:

**1. Does the service exist and have endpoints?**

```bash
kubectl get svc -n <namespace>
kubectl get endpoints <service-name> -n <namespace>
```

If endpoints are empty, the selector does not match any running pods. Compare the service selector with actual pod labels:

```bash
kubectl describe svc <service-name> -n <namespace>
kubectl get pods -n <namespace> --show-labels
```

**2. Can you resolve the DNS name from inside a pod?**

```bash
kubectl exec -it <pod-name> -n <namespace> -- nslookup <service-name>
# Or if nslookup is not available:
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- nslookup <service-name>.<namespace>.svc.cluster.local
```

If DNS resolution fails, check that CoreDNS is running: `kubectl get pods -n kube-system -l k8s-app=kube-dns`.

**3. Can you reach the service port?**

```bash
kubectl exec -it <pod-name> -n <namespace> -- wget -qO- http://<service-name>:<port>/healthz
# Or with curl if available:
kubectl exec -it <pod-name> -n <namespace> -- curl -s http://<service-name>:<port>/healthz
```

**4. Is the target pod actually listening?**

```bash
kubectl exec -it <target-pod> -n <namespace> -- ss -tlnp
```

Verify the `targetPort` on the Service matches the port the container is actually listening on.

**5. Check for network policies blocking traffic:**

```bash
kubectl get networkpolicies -n <namespace>
```

If network policies exist and do not explicitly allow the traffic, it will be silently dropped.

## Common Mistakes

- **Wrong selector.** The service selector must match pod labels exactly. Labels on the Deployment do not count -- it is the `template.metadata.labels` that matters.
- **Port vs targetPort confusion.** `port` is what consumers use. `targetPort` is what the container listens on. They do not have to be the same.
- **Cross-namespace without qualification.** `curl api-backend:8080` works in the same namespace. From another namespace you need `curl api-backend.production:8080`.
- **LoadBalancer pending on bare metal.** Without a cloud provider or MetalLB, LoadBalancer services never get an external IP. Use NodePort or set up MetalLB.
- **Headless service with session affinity.** `sessionAffinity` has no effect on headless services since there is no proxy -- the client connects directly to pod IPs.

