Blog

Kubernetes Services: exposing your Deployment to traffic

A Deployment keeps your Pods running, but nothing can reach them yet. Services are how you expose your app — inside the cluster or to the outside world.

Article info

In the last post we got a Deployment running with three replicas. The Pods are up, they’re healthy — but nothing can actually talk to them. No traffic reaches them from other services, and nothing reaches them from outside the cluster either.

That’s where Services come in.

What a Service does

A Service gives your Pods a stable network address. Pods come and go — they get restarted, replaced, rescheduled — and their IP addresses change every time. A Service sits in front of them and keeps a consistent endpoint regardless of what’s happening underneath.

Think of it as a load balancer that always knows which Pods are healthy and routes traffic to them.

The basic structure

service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
  app: nginx
ports:
  - port: 80
    targetPort: 80

The key part is selector — it matches the labels on your Pods. The Service automatically finds all Pods with app: nginx and routes traffic to them. When a Pod goes down and a new one comes up with the same label, the Service picks it up automatically.

port is what the Service exposes. targetPort is the port on the Pod itself. They’re often the same, but don’t have to be.

Service types

This is where it gets useful. Kubernetes has a few Service types depending on who needs to reach your app.

ClusterIP (default)

Only accessible from inside the cluster. Other services and Pods can reach it, but nothing outside can.

clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: ClusterIP
selector:
  app: nginx
ports:
  - port: 80
    targetPort: 80

This is the default if you don’t specify a type. Good for internal communication between services.

NodePort

Exposes the Service on a port on each Node’s IP. Accessible from outside the cluster if you know the Node IP.

nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
  app: nginx
ports:
  - port: 80
    targetPort: 80
    nodePort: 30080

NodePort range is 30000–32767. Useful for development and testing, but not great for production — you’re exposing a port on every node.

LoadBalancer

Provisions an external load balancer from your cloud provider (AWS, GCP, Azure). This is the standard way to expose an app to the internet in production.

loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: LoadBalancer
selector:
  app: nginx
ports:
  - port: 80
    targetPort: 80

The cloud provider assigns an external IP. Takes a minute to provision. Check the status with:

check-service
kubectl get service nginx-service

When EXTERNAL-IP shows an address instead of <pending>, it’s ready.

Applying and checking

apply-service
# Apply the Service
kubectl apply -f service.yaml

# List all services
kubectl get services

# Get details about a specific service
kubectl describe service nginx-service

The describe output shows you the endpoints — the actual Pod IPs the Service is routing to. If endpoints are empty, check that your selector labels match what’s on your Pods.

Putting it together

Here’s a complete setup: a Deployment and a Service for the same app.

deployment-and-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
  matchLabels:
    app: nginx
template:
  metadata:
    labels:
      app: nginx
  spec:
    containers:
      - name: nginx
        image: nginx:1.27
        ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: ClusterIP
selector:
  app: nginx
ports:
  - port: 80
    targetPort: 80

Notice the --- separator — you can put multiple resources in a single file and apply them together with one kubectl apply -f.

Why this matters

Without a Service, your Deployment is isolated. With one, it becomes reachable — either by other services inside the cluster, or by the outside world. Most real apps need both: a ClusterIP Service for internal communication and a LoadBalancer or Ingress for external traffic.

Next up: how to manage configuration and sensitive data in Kubernetes using ConfigMaps and Secrets.