Introduction
Migrating from a traditional Ingress setup to a Gateway API in Kubernetes may sound straightforward at first, but it quickly becomes challenging once multiple services are involved.
Why migrate?
The traditional Ingress API is limited to simple HTTP routing and becomes awkward as your routing needs grow. The Gateway API offers a more flexible, extensible model with cleaner separation of responsibilities and support for advanced features out of the box. If you want future-proof traffic management that scales with your cluster, Gateway API is the better foundation.
For this showcase, we will go through a setup of a single-node Kubernetes (Microk8s) and then migrate from Ingress to Gateway API.
Current Setup (Ingress)
Our Microk8s is installed the following way:
Kubernetes Installation and Setup
This is how our Kubernetes is set up for the use of Ingress and MetalLB:
snap install microk8s --classic --channel=1.34/stable
microk8s enable community
microk8s enable hostpath-storage
microk8s enable metallb:10.0.130.240-10.0.130.250
microk8s enable ingress
microk8s enable cert-manager
snap alias microk8s.kubectl kubectl
Ingress Service
This is the Ingress Service we use:
apiVersion: v1
kind: Service
metadata:
name: ingress-controller-lb
namespace: ingress
annotations:
metallb.universe.tf/allow-shared-ip: k8s-shared # Needed in our setup. Depends from environment to environment.
spec:
type: LoadBalancer
loadBalancerIP: 10.0.130.240 # Our DNS resolves the URL to access Kubernetes Services to this IP Address.
selector:
name: nginx-ingress-microk8s
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
The cluster exposes the nginx Ingress controller through a LoadBalancer Service with a fixed external IP. All incoming HTTP and HTTPS traffic sent to that IP is forwarded to the Ingress controller Pods, which Microk8s labels automatically. MetalLB provides the external IP in bare-metal setups. This creates a single, stable entry point into the cluster, where the Ingress controller receives every request and routes it to the correct application based on the configured Ingress rules.
ArgoCD Ingress
This is the Ingress for ArgoCD:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server
namespace: argocd
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: nginx
rules:
- host: argocd.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argo-cd-argocd-server
port:
number: 443
tls:
- hosts:
- argocd.example.com
secretName: argocd-certificate
This configuration makes the Ingress controller route requests for argocd.example.com directly to the ArgoCD server's internal Service. TLS is passed through unchanged, allowing ArgoCD to handle HTTPS itself. Because the Ingress is assigned to the nginx controller, any request to that hostname is forwarded straight to the ArgoCD backend on port 443, using the provided TLS certificate for secure access.
Authentik Deployment (with Ingress)
Another Service, Authentik, is deployed via Helm, with this values.yaml (only relevant parts):
[...]
server:
ingress:
enabled: true
ingressClassName: nginx
hosts:
- auth.example.com
paths:
- "/"
tls:
- secretName: authentik-certificate
hosts:
- auth.example.com
env:
- name: AUTHENTIK_HOST
value: "https://auth.example.com"
- name: AUTHENTIK_HOST_BROWSER
value: "https://auth.example.com"
[...]
These Helm values enable an Ingress for Authentik, making it accessible through the nginx Ingress controller under the hostname auth.example.com. TLS is configured using the specified certificate, and all traffic to that host is routed to the Authentik service. The environment variables ensure that Authentik generates correct external URLs and redirects by knowing its public-facing address.
Summary

This setup relies on the traditional Ingress API and a single Ingress controller, which has limited flexibility and outdated traffic-management capabilities. Kubernetes is moving toward the Gateway API, a newer, more robust standard that offers richer routing features, clearer separation of responsibilities, and better support for modern traffic patterns. While the Ingress approach still works, it is no longer the long-term direction of the platform, and future-ready deployments are expected to adopt the Gateway API instead.
Migration to Gateway API
Migrating to the Gateway API means replacing Ingress resources with Gateways and HTTPRoutes, separating the network entry point from the routing logic. But the Gateway API itself only defines the rules, but doesn't actually move traffic. This is why a data-plane implementation like Kong is needed. Kong interprets those Gateway and Route objects, handles TLS, processes requests, and applies policies such as authentication or rate limiting. With most controllers supporting both APIs, you can run the Gateway API alongside your existing Ingress setup and transition services gradually and safely.
In this blog post we will do a complete replacement of Ingress.
Kubernetes Installation and Setup
This is the modified installation process for Kubernetes:
snap install microk8s --classic --channel=1.34/stable
microk8s enable community
microk8s enable metrics-server
microk8s enable hostpath-storage
microk8s enable metallb:10.0.130.240-10.0.130.250
microk8s kubectl apply -f /root/metallb-l2-advertisement.yaml
microk8s enable cert-manager
Note that we don't install ingress anymore.
Before moving on, make sure to delete or disable the old nginx ingress addon if it already claimed the MetalLB IP; the Gateway setup below reuses 10.0.130.240 for Kong.
MetalLB assigns external IPs, but it won’t actually announce them on the network unless an L2Advertisement is defined. This object tells MetalLB which IP pools may be broadcast at layer 2 so that other machines on the LAN can reach your LoadBalancer services. Kong included.
# /root/metallb-l2-advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-l2
namespace: metallb-system
spec:
ipAddressPools:
- default-addresspool # Created automatically by MetalLB during installation
Additionally, we install the Gateway API and Kong:
# Gateway API
microk8s kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
# Kong
microk8s helm repo add kong https://charts.konghq.com
microk8s helm repo update
microk8s kubectl create namespace kong
microk8s helm install kong kong/kong --namespace kong --create-namespace -f /root/kong-values.yaml
microk8s kubectl apply -f /root/kong-gatewayclass.yaml
microk8s kubectl apply -f /root/gateway.yaml
The values.yaml for the Kong Helm chart:
# /root/kong-values.yaml
ingressController:
enabled: true
gateway:
enabled: true
gateway:
enabled: true
proxy:
type: LoadBalancer
annotations:
metallb.universe.tf/allow-shared-ip: k8s-shared
loadBalancerIP: 10.0.130.240
In this setup, the proxy component of Kong is exposed through a LoadBalancer so it can receive external traffic for the Gateway API. Unlike Microk8s' built-in Ingress addon, Kong does not come with any automatic networking configuration. That means MetalLB must be explicitly configured to announce the LoadBalancer IP on the local network. The annotations and the static IP ensure that Kong receives the same external address that was previously used by the Ingress controller. Earlier, Microk8s handled this behind the scenes, but with Gateway API and Kong the responsibility shifts to you, so MetalLB needs to be configured manually to make the LoadBalancer reachable from outside the cluster. Kong's ingress controller stays enabled here because it understands both legacy Ingress objects and HTTPRoutes, which makes mixed-mode migrations possible.
GatewayClass
# /root/kong-gatewayclass.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: kong
annotations:
konghq.com/gatewayclass-unmanaged: 'true'
spec:
controllerName: konghq.com/kic-gateway-controller
This defines the GatewayClass that Kong uses to integrate with the Gateway API. A GatewayClass acts as the “driver” that tells Kubernetes which controller is responsible for managing Gateway resources. In this case, the class named kong is linked to Kong's Gateway controller through the controllerName field. The annotation marks it as an unmanaged class, meaning Kong won't automatically create or modify Gateways for you. With this class in place, any Gateway that references it will be handled by Kong and routed through Kong's proxy.
Gateway
Before creating the Gateway, ensure the wildcard TLS secret (wildcard-k8s-certificate) exists in the kong namespace so the HTTPS listeners can terminate TLS without errors.
# /root/gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: kong-ingress-gateway
namespace: kong
spec:
gatewayClassName: kong
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
- name: https-argocd
protocol: HTTPS
port: 443
hostname: argocd.example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-k8s-certificate
allowedRoutes:
namespaces:
from: All
- name: https-authentik
protocol: HTTPS
port: 443
hostname: auth.example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-k8s-certificate
allowedRoutes:
namespaces:
from: All
This Gateway exposes Kong as the cluster's entry point for HTTP and HTTPS traffic. It uses the previously defined Kong GatewayClass and defines listeners for port 80 and 443. Each HTTPS listener is bound to a specific hostname and TLS certificate, allowing Kong to terminate TLS and route traffic to any matching HTTPRoutes in any namespace. Essentially, this Gateway replaces the old Ingress controller's external interface and becomes the central access point for all incoming traffic.
HTTPRoutes
With the Gateway and listeners in place, the final step is to define the HTTPRoutes. These routes tell Kong how to forward traffic from each listener to the correct backend service. In the Gateway API model, the Gateway handles entry and TLS, while the HTTPRoutes contain all the application-level routing rules. Once the HTTPRoutes are added, the full path from external request to internal service is complete.
ArgoCD:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: argocd-httproute
namespace: argocd
spec:
parentRefs:
- name: kong-ingress-gateway
namespace: kong
sectionName: https-argocd
hostnames:
- argocd.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: argo-cd-argocd-server
namespace: argocd
port: 80
This HTTPRoute connects ArgoCD to the Gateway. It attaches to the Gateway's HTTPS listener for the ArgoCD hostname and defines that all requests under / should be forwarded to the ArgoCD server Service inside the argocd namespace. The route points to port 80 because ArgoCD runs with server.insecure: "true" in this setup, exposing HTTP instead of HTTPS on the Service. In the Gateway API model, the Gateway handles the entry point, while the HTTPRoute provides the actual routing rules to the backend.
Authentik:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: authentik-httproute
namespace: authentik
spec:
parentRefs:
- name: kong-ingress-gateway
namespace: kong
sectionName: https-authentik
hostnames:
- auth.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: authentik-server
namespace: authentik
port: 80
This HTTPRoute works the same way as the ArgoCD route, but targets Authentik instead. It attaches to the Gateway's HTTPS listener for the Authentik hostname and forwards all requests under / to the Authentik server Service in its namespace. The only differences are the hostname, the listener section, and the backend Service it routes to.
Verification
After applying the Gateway and HTTPRoutes, verify the setup:
microk8s kubectl get gateway,httproute -A
microk8s kubectl describe gateway kong-ingress-gateway -n kong # Check listener and address status
curl -k https://argocd.example.com
curl -k https://auth.example.com
Summary

The Gateway API modernizes Kubernetes traffic management by separating the responsibilities of networking and application routing. A Gateway defines the cluster’s entry points and TLS settings, while HTTPRoutes specify how traffic is forwarded to services. Kong provides the data plane that interprets these Gateway API objects, terminates TLS, and applies policies such as authentication or rate limiting. This architecture is more modular, extensible, and future-proof than the traditional Ingress model, making it the recommended approach for upcoming Kubernetes deployments.
