This guide provides a detailed step-by-step process for migrating from traditional nginx Ingress to Kubernetes Gateway API using drunk-lib and drunk-app charts.
The Kubernetes Gateway API is the next generation of Ingress, offering:
Defines the controller implementation (like nginx, Istio, Envoy). Typically managed by cluster administrators.
Represents a network entry point (like a LoadBalancer). Defines listeners for protocols, ports, and hostnames.
Defines how HTTP traffic from Gateway listeners routes to backend Services. Replaces Ingress.
| Traditional Ingress | Gateway API |
|---|---|
| IngressClass | GatewayClass |
| Ingress | Gateway + HTTPRoute |
| Single resource | Separated concerns |
| Limited routing | Advanced routing features |
First, install the Gateway API Custom Resource Definitions in your cluster:
# Install Gateway API CRDs (standard channel)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
# Verify installation
kubectl get crd | grep gateway
Expected output:
gatewayclasses.gateway.networking.k8s.io
gateways.gateway.networking.k8s.io
httproutes.gateway.networking.k8s.io
referencegrants.gateway.networking.k8s.io
The GatewayClass is typically provided by your Gateway controller. Here are examples for popular controllers:
# Install NGINX Gateway Fabric
kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/latest/download/nginx-gateway.yaml
# Verify GatewayClass
kubectl get gatewayclass
The default GatewayClass created is nginx.
If you need to create a custom GatewayClass:
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nginx
spec:
controllerName: gateway.nginx.org/nginx-gateway-controller
# Optional: Add parameters reference
# parametersRef:
# group: gateway.nginx.org
# kind: NginxProxy
# name: nginx-proxy-config
Apply it:
kubectl apply -f gatewayclass.yaml
# Install Istio
istioctl install --set profile=default
# GatewayClass is automatically created as 'istio'
kubectl get gatewayclass
Now configure your drunk-app chart to create a Gateway for the drunk.dev domain.
First, create or obtain a TLS certificate for *.drunk.dev:
# Option 1: Using cert-manager (recommended)
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: drunk-dev-tls
namespace: default
spec:
secretName: drunk-dev-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- "*.drunk.dev"
- "drunk.dev"
EOF
# Option 2: Using existing certificate
kubectl create secret tls drunk-dev-tls \
--cert=path/to/cert.pem \
--key=path/to/key.pem \
-n default
Create a values file for your Gateway configuration:
# values-gateway-drunk-dev.yaml
# Gateway configuration for drunk.dev domain
gateway:
enabled: true
gatewayClassName: "nginx" # Use the GatewayClass from Step 2
annotations:
# Optional: Customize your gateway
cert-manager.io/cluster-issuer: "letsencrypt-prod"
listeners:
# HTTP listener on port 80
- name: http
protocol: HTTP
port: 80
hostname: "*.drunk.dev"
# Allow routes from same namespace
allowedRoutes:
namespaces:
from: Same
# HTTPS listener on port 443
- name: https
protocol: HTTPS
port: 443
hostname: "*.drunk.dev"
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: drunk-dev-tls
allowedRoutes:
namespaces:
from: Same
# Service configuration (required for HTTPRoute backend)
service:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 8080
# Install the Gateway
helm install my-gateway drunk-charts/drunk-app -f values-gateway-drunk-dev.yaml
# Verify Gateway is ready
kubectl get gateway
kubectl describe gateway my-gateway-drunk-app
Expected status:
NAME CLASS ADDRESS PROGRAMMED AGE
my-gateway-drunk-app nginx 10.96.100.100 True 1m
Now configure HTTPRoutes to direct traffic from the Gateway to your services.
# values-httproute-myapp.yaml
# HTTPRoute configuration
httpRoute:
enabled: true
# Reference the Gateway created in Step 3
parentRefs:
- name: my-gateway-drunk-app # Name of your Gateway
namespace: default # Gateway namespace
sectionName: https # Optional: specific listener
# Hostnames for this application
hostnames:
- "myapp.drunk.dev"
# Routing rules
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: myapp-service
port: 80
httpRoute:
enabled: true
parentRefs:
- name: my-gateway-drunk-app
hostnames:
- "api.drunk.dev"
rules:
# API v1 traffic
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- name: api-v1-service
port: 8080
# API v2 traffic
- matches:
- path:
type: PathPrefix
value: /api/v2
backendRefs:
- name: api-v2-service
port: 8080
# Admin traffic with header matching
- matches:
- path:
type: PathPrefix
value: /admin
headers:
- type: Exact
name: X-Admin-Key
value: secret-admin-key
backendRefs:
- name: admin-service
port: 9090
# Deploy your application with HTTPRoute
helm install myapp drunk-charts/drunk-app \
-f values-gateway-drunk-dev.yaml \
-f values-httproute-myapp.yaml
# Verify HTTPRoute
kubectl get httproute
kubectl describe httproute myapp-drunk-app
Run both Ingress and Gateway API in parallel during migration:
# Your existing ingress configuration
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: "myapp.drunk.dev"
path: "/"
port: 80
tls: myapp-tls
# values-migration.yaml - Run both together
# Keep existing Ingress enabled
ingress:
enabled: true
className: "nginx"
hosts:
- host: "myapp.drunk.dev"
path: "/"
port: 80
tls: myapp-tls
# Add new Gateway API configuration
gateway:
enabled: true
gatewayClassName: "nginx"
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.drunk.dev"
tls:
mode: Terminate
certificateRefs:
- name: drunk-dev-tls
httpRoute:
enabled: true
hostnames:
- "myapp.drunk.dev"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: myapp-service
port: 80
Deploy with:
helm upgrade myapp drunk-charts/drunk-app -f values-migration.yaml
# Get Gateway address
GATEWAY_IP=$(kubectl get gateway my-gateway-drunk-app -o jsonpath='{.status.addresses[0].value}')
# Test HTTPRoute (replace with your actual hostname)
curl -H "Host: myapp.drunk.dev" http://$GATEWAY_IP/
# Test HTTPS
curl -H "Host: myapp.drunk.dev" https://$GATEWAY_IP/ -k
# Check Gateway
kubectl get gateway -o wide
kubectl describe gateway my-gateway-drunk-app
# Check HTTPRoute
kubectl get httproute -o wide
kubectl describe httproute myapp-drunk-app
# Check backend service
kubectl get svc myapp-service
# Test different routes
curl -v https://myapp.drunk.dev/
curl -v https://api.drunk.dev/api/v1/status
curl -v https://api.drunk.dev/api/v2/health
# Check Gateway logs
kubectl logs -n gateway-system -l app=gateway-controller --tail=100
# Watch HTTPRoute status
kubectl get httproute -w
# Check events
kubectl get events --sort-by='.lastTimestamp' | grep -i gateway
Once Gateway API is verified and working:
# values-gateway-only.yaml
# Disable old Ingress
ingress:
enabled: false
# Keep Gateway API enabled
gateway:
enabled: true
gatewayClassName: "nginx"
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.drunk.dev"
tls:
mode: Terminate
certificateRefs:
- name: drunk-dev-tls
httpRoute:
enabled: true
hostnames:
- "myapp.drunk.dev"
rules:
- backendRefs:
- name: myapp-service
port: 80
# Update to Gateway-only configuration
helm upgrade myapp drunk-charts/drunk-app -f values-gateway-only.yaml
# Verify Ingress is removed
kubectl get ingress
# Remove old IngressClass if no longer needed
kubectl get ingressclass
# kubectl delete ingressclass nginx
# Verify only Gateway resources exist
kubectl get gateway,httproute
| Feature | Nginx Ingress | Gateway API |
|---|---|---|
| Controller | IngressClass: nginx | GatewayClass: nginx |
| Entry Point | Ingress resource | Gateway resource |
| Routing | Ingress spec | HTTPRoute resource |
| Domain | spec.rules[].host |
Gateway: listeners[].hostnameHTTPRoute: hostnames[] |
| TLS | spec.tls[] |
Gateway: listeners[].tls |
| Path Matching | paths[].path |
HTTPRoute: rules[].matches[].path |
| Backend | backend.service |
HTTPRoute: backendRefs[] |
| Annotations | metadata.annotations |
Separate filter resources |
Nginx Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.drunk.dev
secretName: myapp-tls
rules:
- host: myapp.drunk.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
Gateway API Equivalent:
# Gateway (shared by multiple apps)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: drunk-dev-gateway
spec:
gatewayClassName: nginx
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.drunk.dev"
tls:
mode: Terminate
certificateRefs:
- name: drunk-dev-tls
---
# HTTPRoute (per application)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp
spec:
parentRefs:
- name: drunk-dev-gateway
hostnames:
- myapp.drunk.dev
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: myapp
port: 80
Symptom: Gateway status shows Programmed: False
Solutions:
# Check Gateway events
kubectl describe gateway my-gateway-drunk-app
# Check controller logs
kubectl logs -n gateway-system -l app=gateway-controller
# Verify GatewayClass exists
kubectl get gatewayclass
# Check if TLS secret exists
kubectl get secret drunk-dev-tls
Symptom: HTTPRoute status shows no parent
Solutions:
# Verify Gateway name and namespace match
kubectl get gateway -A
# Check HTTPRoute parent references
kubectl get httproute myapp-drunk-app -o yaml | grep -A 5 parentRefs
# Verify allowed routes in Gateway
kubectl get gateway my-gateway-drunk-app -o yaml | grep -A 10 allowedRoutes
Symptom: HTTPS not working or certificate errors
Solutions:
# Check if certificate secret exists
kubectl get secret drunk-dev-tls
# Verify certificate in Gateway
kubectl get gateway my-gateway-drunk-app -o yaml | grep -A 5 certificateRefs
# Check cert-manager if using it
kubectl get certificate drunk-dev-tls
kubectl describe certificate drunk-dev-tls
Symptom: 404 or connection errors
Solutions:
# Verify backend service exists
kubectl get svc myapp-service
# Check HTTPRoute backend references
kubectl get httproute myapp-drunk-app -o yaml | grep -A 5 backendRefs
# Test service directly
kubectl port-forward svc/myapp-service 8080:80
curl http://localhost:8080/
# Check HTTPRoute status
kubectl describe httproute myapp-drunk-app
Solution: Use unique listener names and ports, or separate by namespace:
gateway:
listeners:
- name: https-prod
port: 443
- name: https-staging
port: 8443
Nginx Ingress uses / by default as Prefix, Gateway API is explicit:
# Gateway API - be explicit
rules:
- matches:
- path:
type: PathPrefix # or Exact
value: /
Ensure service names match:
# HTTPRoute must reference actual service name
backendRefs:
- name: myapp-service # Must match: kubectl get svc
port: 80
The migration from nginx Ingress to Gateway API involves:
The Gateway API provides better separation of concerns, more expressive routing, and improved extensibility while maintaining compatibility with your existing nginx-based infrastructure.