Skip to content

Lab 4: Two Services Behind One Ingress

Pairs with Phase 4. You'll install an ingress controller, then route a real hostname to two different services: / to an nginx frontend, /api to the whoami backend from Lab 2.

This lab needs the port mappings from Lab 1's kind config. If you skipped that, recreate the cluster with the config first.

Step 1: Install ingress-nginx

The Ingress resource is just rules; without a controller pod doing the routing, nothing happens. Install the controller (this manifest is tailored for kind):

bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=120s

Expected:

pod/ingress-nginx-controller-xxxxx condition met

Step 2: Make sure the backend is up

Lab 2's whoami should still be running:

bash
kubectl get deploy whoami svc whoami

If it's gone, re-apply lab-2/deployment.yaml and lab-2/service.yaml.

Step 3: A frontend

Save as lab-4/frontend.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    app: frontend
  ports:
    - port: 80
bash
kubectl apply -f lab-4/frontend.yaml

Step 4: The Ingress

Try writing lab-4/ingress.yaml yourself first: host app.localtest.me, path /api to service whoami port 80, path / to service frontend port 80.

Solution: ingress.yaml
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
spec:
  ingressClassName: nginx
  rules:
    - host: app.localtest.me
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: whoami
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80

Two things people miss: ingressClassName: nginx (without it, the controller ignores your Ingress), and path order doesn't matter - Prefix matching picks the longest match, so /api wins over / for API calls.

bash
kubectl apply -f lab-4/ingress.yaml
kubectl get ingress

Expected (ADDRESS may take a minute to fill in):

NAME   CLASS   HOSTS              ADDRESS     PORTS   AGE
app    nginx   app.localtest.me   localhost   80      30s

Step 5: Test from your browser

*.localtest.me is a public DNS name that always resolves to 127.0.0.1 - no /etc/hosts editing. Thanks to the kind port mappings, port 80 on your Mac lands on the ingress controller.

bash
curl http://app.localtest.me/        # nginx welcome page HTML
curl http://app.localtest.me/api     # whoami output: Hostname, IP, headers

Open both in the browser too. One hostname, one entry point, two services - this replaces every -p flag in your compose file.

Note the whoami output shows X-Forwarded-For and X-Forwarded-Host headers now: your request went Mac -> kind port mapping -> ingress controller -> Service -> pod.

Verify it worked

  • [ ] You can explain why the controller install was a separate step from the Ingress resource
  • [ ] / and /api hit different services on the same hostname
  • [ ] You know what ingressClassName does and what happens without it
  • [ ] You found the forwarded headers in the whoami response and can trace the request path

Stretch goal: TLS

Install cert-manager and issue a self-signed cert for app.localtest.me. Real Let's Encrypt needs a public domain, but the cert-manager mechanics (Issuer, Certificate, annotated Ingress) are identical - and that's the part worth learning.

Next: Lab 5: Operations

A VineLab lab. Released under the MIT License.