Skip to content

How to Expose a Kubernetes Service Externally

DodaTech 2 min read

In this tutorial, you'll learn about How to Expose a Kubernetes Service Externally. We cover key concepts, practical examples, and best practices.

The Problem

Your pod is running inside the cluster but you cannot access it from your browser or API client. A ClusterIP service is only reachable within the cluster, so external traffic has no way to reach your application.

Quick Fix

Step 1: Change the service type to LoadBalancer

Edit the service to use a load balancer (cloud providers only):

kubectl edit service my-service

Change type: ClusterIP to type: LoadBalancer:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 3000
  selector:
    app: my-app

Get the external IP:

kubectl get service my-service
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-service   LoadBalancer   10.96.0.1       <pending>     80:30080/TCP   30s

Step 2: Use NodePort for local or bare-metal clusters

Expose the service on a static port on every node:

kubectl expose deployment my-app --type=NodePort --port=80 --target-port=3000
service/my-app exposed

Find the allocated port:

kubectl get service my-app
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
my-app    NodePort   10.96.0.2       <none>        80:30080/TCP     10s

Access via http://node-ip:30080.

Step 3: Use kubectl port-forward for testing

Forward a local port to the pod without changing the service:

kubectl port-forward pod/my-app-pod 8080:3000
Forwarding from 127.0.0.1:8080 -> 3000

Access via http://localhost:8080.

Step 4: Use Ingress for HTTP routing

Create an Ingress resource to route traffic:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-service
                port:
                  number: 80

Alternative Solutions

Use a proxy sidecar for legacy apps

Add an nginx sidecar to expose apps that only listen on localhost:

spec:
  containers:
    - name: nginx
      image: nginx:alpine

Use ClusterIP with kubectl proxy

Access internal services without exposing them:

kubectl proxy
# Then access via http://localhost:8001/api/v1/namespaces/default/services/my-service/proxy/

Common Mistakes to Avoid

Using LoadBalancer when Ingress is more appropriate. Each LoadBalancer creates a cloud load balancer, which costs money. Use Ingress for HTTP services.

Forgetting to specify targetPort. If the container listens on a port different from the service port, traffic fails. Match targetPort to the container port.

Using NodePort without a fixed port in production. NodePort assigns a random port by default, which may conflict with other services. Specify nodePort explicitly.

Pro Tips

Use Headless Service for stateful applications. Set clusterIP: None for databases that need direct pod-to-pod communication without load balancing.

Use ExternalName service for external resources. Map a Kubernetes service name to an external DNS name: externalName: db.example.com.

Use sessionAffinity for sticky sessions. Set sessionAffinity: ClientIP to route requests from the same client to the same pod.

Prevention

  • Use ClusterIP for internal services, NodePort for local testing, and LoadBalancer for production cloud deployments.
  • Set up an Ingress controller for HTTP routing with multiple services.
  • Use kubectl port-forward for quick debugging without modifying service types.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro