← Articles

One front door: Services and Ingress

Published
Read
6 minutes
Part of
Kubernetes for the rest of us, ch. 02
Tags
Kubernetes · Services · Ingress · Traefik

Give your application a stable address and follow a browser request through Ingress and a Service to its Pod.

Your application needs an address that survives the Pod running it.

In chapter one, you put nginx in a Pod and visited it through kubectl port-forward. That works for a quick look. For an application people need to reach, I want a front door that stays put when the tenant moves out.

A Service provides that door. Ingress routes browser traffic to it. You can add both to the Docker Desktop cluster you already created.

§ 01

Pods move. The address should stay.

Each Pod has an IP address. A replacement Pod can have a different one, so sending callers directly to that IP means asking them to keep updating their address book.

A Service gives an application a stable name and, by default, an internal IP address. It finds its Pods through labels: small pieces of metadata attached to Kubernetes objects.

Think of the Service as the front door for a household. The people behind it can change without everyone who visits needing new directions. In Kubernetes, the selector on the Service says which labels its Pods must have. Kubernetes keeps the matching backend addresses up to date.

The Service you create here is reachable inside the cluster. Your browser needs a way in from outside it.

§ 02

Give nginx a Service

Use the cluster from chapter one: Docker Desktop with the kind provisioner, on Windows or macOS. Stop any old port-forward command with Ctrl+C and check your context:

terminal
kubectl config use-context docker-desktop
kubectl get nodes

The Node should be Ready. Chapter one ended by deleting nginx, so recreate it now. Update nginx-pod.yaml to include a label:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: guide-nginx
  namespace: default
  labels:
    app: guide-nginx
spec:
  containers:
    - name: nginx
      image: nginx:stable
      ports:
        - containerPort: 80

Apply it and wait for nginx:

terminal
kubectl apply -f nginx-pod.yaml
kubectl wait -n default --for=condition=Ready pod/guide-nginx --timeout=120s

Save the following as nginx-service.yaml:

yaml
apiVersion: v1
kind: Service
metadata:
  name: guide-nginx
  namespace: default
spec:
  selector:
    app: guide-nginx
  ports:
    - port: 80
      targetPort: 80

The selector matches the label you added to the Pod. port is where the Service accepts connections; targetPort is where nginx listens inside the Pod. Both happen to be 80.

terminal
kubectl apply -f nginx-service.yaml
kubectl get service guide-nginx -n default

You should see a Service named guide-nginx. It gives nginx an internal address; the next step makes it reachable from your browser.

§ 03

Ingress gives visitors directions

An Ingress describes how HTTP and HTTPS requests should reach Services. A rule might say: requests for this hostname, at this path, belong at the nginx Service.

In the house analogy, the hostname is the street address. The Ingress rules tell arriving visitors which front door to use.

Those rules need an Ingress controller to put them into practice. I am using Traefik, a reverse proxy that reads the rules and forwards requests.

Docker Desktop already handles the cluster’s external networking. For the setup used here, with desktop-cloud-provider-kind:v0.7.0 and no IngressClass installed, you still need to add the Ingress controller.

Install Traefik

Install Helm, the package manager used to install Traefik. If you already use Homebrew on macOS:

terminal
brew install helm

On Windows, with WinGet:

powershell
winget install Helm.Helm

If neither package manager is available, the Helm installation page includes downloadable binaries. Reopen your terminal after installation and check helm version.

Save this as traefik-values.yaml:

yaml
providers:
  kubernetesCRD:
    enabled: false
  kubernetesIngress:
    enabled: true
    nativeLBByDefault: true
ports:
  websecure:
    expose:
      default: false

This keeps the exercise on standard Kubernetes Ingress and plain HTTP. The nativeLBByDefault setting sends traffic through the nginx Service on its way to the Pod.

Install chart version 42.0.0, which packages Traefik 3.7.14 and requires Kubernetes 1.30 or newer:

terminal
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm upgrade --install traefik traefik/traefik --version 42.0.0 --namespace default --kube-context docker-desktop -f traefik-values.yaml --wait --timeout 5m
kubectl get ingressclass
kubectl get service traefik -n default

Expect an IngressClass named traefik. Traefik also gets its own LoadBalancer Service: Docker Desktop’s networking makes that entry point accessible from your laptop. The nginx Service remains internal.

Add the route

Save this as nginx-ingress.yaml:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: guide-nginx
  namespace: default
spec:
  ingressClassName: traefik
  rules:
    - host: localhost
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: guide-nginx
                port:
                  number: 80

ingressClassName selects Traefik. The rule sends requests for localhost, under /, to port 80 of the guide-nginx Service.

terminal
kubectl apply -f nginx-ingress.yaml
kubectl get ingress guide-nginx -n default

Open http://localhost. You should see the nginx welcome page without a port-forward process running.

The browser reaches Docker Desktop’s published HTTP port. Traffic enters through Traefik’s LoadBalancer Service, reaches Traefik, and follows the Ingress rule to the nginx Service. That Service forwards it to the Pod’s port 80.

On an internet-facing cluster, public DNS would point your hostname at the external entry point. Here, localhost brings the browser back to your own laptop. This exercise sets up local HTTP access; public DNS and TLS need their own configuration.

If the door stays shut

Check the controller and route:

terminal
kubectl get pods -n default -l app.kubernetes.io/name=traefik
kubectl describe ingress guide-nginx -n default
kubectl get service traefik -n default

A Traefik 404 usually means the hostname or path did not match. Use http://localhost exactly: visiting 127.0.0.1 sends a different host header. A 503 can mean nginx is unavailable. Check that its Pod is ready and that the Service selector matches the Pod label:

terminal
kubectl get pod guide-nginx -n default --show-labels
kubectl describe service guide-nginx -n default

If the browser cannot connect at all, check that port 80 is free and the Traefik LoadBalancer has finished provisioning. To isolate host networking from routing, temporarily run:

terminal
kubectl port-forward -n default service/traefik 8080:80

Visit http://localhost:8080. This still exercises Traefik and the Ingress rule, but bypasses Docker Desktop’s external entry point. If it works, investigate the LoadBalancer and host port. Stop forwarding with Ctrl+C before continuing.

§ 04

Replace the tenant

With the normal http://localhost route working, delete nginx:

terminal
kubectl delete pod guide-nginx -n default

Refresh the page. Requests will fail while the only application Pod is gone. A Service keeps the address stable; it cannot serve a page without a backend.

Recreate the Pod from the same file:

terminal
kubectl apply -f nginx-pod.yaml
kubectl wait -n default --for=condition=Ready pod/guide-nginx --timeout=120s
kubectl get pods -n default -o wide

The Pod may have a different IP. Refresh the same browser address and nginx should return. You did not change the Service or Ingress.

Keep these resources and YAML files for the next chapters. Chapter three gives them a namespace of their own; scaling follows in part four.