Challenge ·Medium

Migrate a Traefik Ingress to the Gateway API and Cut Traffic Over

The storefront APIs are served through a Traefik Ingress with TLS and two path rules. Reproduce that configuration with a Gateway and an HTTPRoute on the same hostname, cut traffic over to it, and retire the Ingress.

Scenario

Two APIs run in the storefront namespace, served on shop.acme-retail.io by the Ingress storefront-web. It uses the Traefik controller that ships with k3s and terminates TLS with the storefront-tls Secret.

EndpointService
/api/catalogcatalog-api
/api/ordersorders-api

MetalLB owns two addresses:

AddressOwner
192.168.1.240Traefik Service in kube-system, where the domain points now
192.168.1.241reserved, free for a new load balancer

/etc/hosts on cplane-01 lists both addresses for the domain:

192.168.1.240 shop.acme-retail.io
192.168.1.241 shop.acme-retail.io

When a name appears twice the first match wins, so every request goes to Traefik today. Work from that machine.

Confirm the current path works before you change anything:

curl -sk https://shop.acme-retail.io/api/catalog | jq .

Task

The platform team is retiring Ingress in favour of the Gateway API. There is one domain and it must keep working, so this is a cutover rather than a parallel deployment.

  1. Create a Gateway named storefront-gateway in the storefront namespace, served by the nginx GatewayClass, with an HTTPS listener on port 443 for shop.acme-retail.io that terminates TLS using the storefront-tls Secret.
  2. Create an HTTPRoute named storefront-route in the storefront namespace, attached to storefront-gateway, for shop.acme-retail.io, carrying the same routing rules the Ingress serves today.
  3. Confirm the Gateway answers both endpoints correctly before you touch the live path.
    # Gateway
    curl -sk -o /dev/null -w '%{http_code}\n' --max-time 5 \
      --resolve shop.acme-retail.io:443:192.168.1.241 \
      https://shop.acme-retail.io/api/catalog
    
  4. Remove the Ingress load balancer entry 192.168.1.240 for shop.acme-retail.io from /etc/hosts, so that only 192.168.1.241 is left and the domain resolves to the Gateway.
  5. Once the Gateway is serving the domain, delete the storefront-web Ingress, leaving the Gateway API as the only thing routing to these APIs.
Request flow before and after migrating the Ingress to the Gateway API

The same hostname served by two load balancers. /etc/hosts decides which one a client reaches.

Note

Use the following commands to test both the new Gateway and the existing Ingress independently:

# Gateway
curl -sk -o /dev/null -w '%{http_code}\n' --max-time 5 \
  --resolve shop.acme-retail.io:443:192.168.1.241 \
  https://shop.acme-retail.io/api/catalog
# Ingress
curl -sk -o /dev/null -w '%{http_code}\n' --max-time 5 \
  --resolve shop.acme-retail.io:443:192.168.1.240 \
  https://shop.acme-retail.io/api/catalog
Important

Do not edit the Deployments, Services, or storefront-tls Secret. Leave Traefik installed. Only the routing layer changes. Wait up to 2 minutes for the Gateway and its LoadBalancer to be provisioned.


Hint 1 | Which resources replace an Ingress

The job an Ingress did alone is split across two objects. One owns the address, the port, the protocol, and the certificate. The other owns the hostname and the routing rules, and refers back to the first.

kubectl get gatewayclass
kubectl explain gateway.spec.listeners
kubectl explain httproute.spec.rules

Read the object you are reproducing before writing anything:

kubectl get ingress storefront-web -n storefront -o yaml
Hint 2 | The shape of each field

An Ingress tls block becomes a listener with protocol: HTTPS and a tls stanza naming the same Secret. An Ingress backend becomes a backendRefs item naming the Service and its port.

listeners:
- name: <listener-name>
  port: <port>
  protocol: <protocol>
  hostname: <hostname>
  tls:
    mode: Terminate
    certificateRefs:
    - kind: Secret
      name: <secret-name>
  allowedRoutes:
    namespaces:
      from: Same
rules:
- matches:
  - path:
      type: PathPrefix
      value: <path>
  backendRefs:
  - name: <service-name>
    port: <service-port>

See: Gateway API, Ingress migration

Hint 3 | Telling whether it is ready

Nothing here fails loudly. Both objects report status conditions instead, and the data plane takes a few seconds to appear after the Gateway is created:

kubectl get gateway storefront-gateway -n storefront -w
kubectl describe gateway storefront-gateway -n storefront
kubectl describe httproute storefront-route -n storefront

Programmed: True on the Gateway, and Accepted: True plus ResolvedRefs: True on the route, mean the configuration reached the data plane. A false condition carries a message naming the field to fix.

Hint 4 | Working with /etc/hosts

Editing the file needs sudo, and it already holds both lines for this domain:

grep acme-retail /etc/hosts
sudo vi /etc/hosts
getent hosts shop.acme-retail.io

Nothing needs to be added here, only removed. getent reports what the system will actually resolve, which is a better check than reading the file back.


Test Cases