Skip to content

Gateway API with KSail + Cilium

KSail clusters using the Cilium CNI option come Gateway API–ready out of the box. When you select cni: Cilium, KSail automatically:

  1. Installs Gateway API CRDs (experimental channel, v1.4.1) before the Cilium Helm chart.
  2. Enables Cilium’s Gateway API controller (gatewayAPI.enabled: true) in the Helm values.
  3. Creates a GatewayClass named cilium via the Cilium controller.

You don’t need to install any additional controllers or CRD bundles manually.

  • KSail CLI installed

  • Docker running

  • A cluster initialized with Cilium:

    Terminal window
    ksail cluster init --cni Cilium
    ksail cluster create

    Or update an existing ksail.yaml to set spec.cluster.cni: Cilium and run ksail cluster update.

  • kubectl accessible (KSail writes the kubeconfig context — see Companion Tools for context names per distribution).

After cluster creation, confirm the GatewayClass exists and is ready:

Terminal window
kubectl get gatewayclass

Expected output:

NAME CONTROLLER ACCEPTED AGE
cilium io.cilium/gateway-controller True 2m

If ACCEPTED is False, wait a few seconds for Cilium to finish initialising. You can also check:

Terminal window
kubectl get crd gateways.gateway.networking.k8s.io

The Gateway API request path is: GatewayClassGatewayHTTPRouteService.

  1. Deploy a sample app

    Terminal window
    kubectl create deployment httpbin \
    --image=kennethreitz/httpbin:latest \
    --port=80
    kubectl expose deployment httpbin --port=80
  2. Create a Gateway

    gateway.yaml
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
    name: my-gateway
    namespace: default
    spec:
    gatewayClassName: cilium
    listeners:
    - name: http
    port: 80
    protocol: HTTP
    Terminal window
    kubectl apply -f gateway.yaml
    kubectl get gateway my-gateway

    Wait until PROGRAMMED is True:

    NAME CLASS ADDRESS PROGRAMMED AGE
    my-gateway cilium <pending> True 30s
  3. Create an HTTPRoute

    httproute.yaml
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
    name: httpbin-route
    namespace: default
    spec:
    parentRefs:
    - name: my-gateway
    hostnames:
    - "httpbin.local"
    rules:
    - matches:
    - path:
    type: PathPrefix
    value: /
    backendRefs:
    - name: httpbin
    port: 80
    Terminal window
    kubectl apply -f httproute.yaml
    kubectl get httproute httpbin-route
  4. Test the route

    For local Docker clusters, port-forward the Cilium Gateway Service to test:

    Terminal window
    # Port-forward the Cilium Envoy listener Service created for the Gateway
    kubectl -n kube-system port-forward svc/cilium-gateway-my-gateway 8080:80 &
    # Send a request that matches the HTTPRoute hostname
    curl -H "Host: httpbin.local" http://localhost:8080/get

Split traffic between services based on URL path prefix:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: default
spec:
parentRefs:
- name: my-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: backend-api
port: 8080
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: frontend
port: 3000

Route requests based on HTTP headers — useful for canary deployments or A/B testing:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-route
namespace: default
spec:
parentRefs:
- name: my-gateway
rules:
- matches:
- headers:
- name: X-Version
value: canary
backendRefs:
- name: my-app-canary
port: 80
- backendRefs:
- name: my-app
port: 80

Terminate TLS at the Gateway using a Kubernetes Secret:

tls-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-tls-gateway
namespace: default
spec:
gatewayClassName: cilium
listeners:
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: my-tls-secret # a kubernetes.io/tls Secret
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-tls-route
namespace: default
spec:
parentRefs:
- name: my-tls-gateway
hostnames:
- "myapp.example.com"
rules:
- backendRefs:
- name: my-app
port: 80

Gateway API replaces the Kubernetes Ingress resource. Here is a side-by-side comparison of a common pattern:

ConcernIngressHTTPRoute
Entry pointIngressGateway + HTTPRoute
Path routingspec.rules[].http.pathsspec.rules[].matches[].path
Host routingspec.rules[].hostspec.hostnames[]
TLSspec.tls[]Gateway listener tls section
Annotation-based featuresVaries by controllerNative spec fields

Before (Ingress):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: myapp.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80

After (HTTPRoute):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
spec:
parentRefs:
- name: my-gateway
hostnames:
- "myapp.local"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: my-app
port: 80

Key changes:

  • Replace IngressGateway + HTTPRoute
  • Move hostname from spec.rules[].hostspec.hostnames[]
  • Move path from spec.rules[].http.pathsspec.rules[].matches[].path
  • Drop controller-specific annotations — use native HTTPRoute filters instead

When using KSail with Flux or ArgoCD, place Gateway and HTTPRoute manifests in your source directory alongside other workload manifests:

k8s/
├── kustomization.yaml
├── gateway.yaml # Gateway resource
└── my-app/
├── deployment.yaml
├── service.yaml
└── httproute.yaml # HTTPRoute resource

The Gateway API CRDs and the cilium GatewayClass are managed by KSail (installed during cluster create). Your manifests only need to declare Gateway and HTTPRoute resources — no CRD or GatewayClass manifest is needed in your source directory.

Terminal window
ksail workload apply -f k8s/
# or with GitOps
ksail workload push
ksail workload reconcile

GatewayClass ACCEPTED is False:

Terminal window
kubectl describe gatewayclass cilium

Cilium’s controller may still be starting. Wait for Cilium pods to be ready:

Terminal window
kubectl get pods -n kube-system -l k8s-app=cilium

Gateway PROGRAMMED is False:

Terminal window
kubectl describe gateway my-gateway

Check for port conflicts or missing CRDs. On Docker clusters without an external LoadBalancer, confirm Cilium is running with gatewayAPI.hostNetwork.enabled: true (KSail sets this automatically for Docker providers when no LoadBalancer is configured).

HTTPRoute not routing traffic:

Terminal window
kubectl get httproute my-route -o yaml

Check status.parents[].conditions for Accepted and ResolvedRefs — both must be True. If ResolvedRefs is False, the backend Service may not exist or the port may be wrong.

CRDs not found:

Gateway API CRDs are installed by KSail during cluster create for Cilium clusters. If you’re seeing No resources found for gateway.networking.k8s.io:

Terminal window
kubectl get crd | grep gateway.networking.k8s.io

Re-run ksail cluster update to reinstall CRDs if they are missing.