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:
- Installs Gateway API CRDs (experimental channel, v1.4.1) before the Cilium Helm chart.
- Enables Cilium’s Gateway API controller (
gatewayAPI.enabled: true) in the Helm values. - Creates a
GatewayClassnamedciliumvia the Cilium controller.
You don’t need to install any additional controllers or CRD bundles manually.
Prerequisites
Section titled “Prerequisites”-
KSail CLI installed
-
Docker running
-
A cluster initialized with Cilium:
Terminal window ksail cluster init --cni Ciliumksail cluster createOr update an existing
ksail.yamlto setspec.cluster.cni: Ciliumand runksail cluster update. -
kubectlaccessible (KSail writes the kubeconfig context — see Companion Tools for context names per distribution).
Verify Gateway API Is Ready
Section titled “Verify Gateway API Is Ready”After cluster creation, confirm the GatewayClass exists and is ready:
kubectl get gatewayclassExpected output:
NAME CONTROLLER ACCEPTED AGEcilium io.cilium/gateway-controller True 2mIf ACCEPTED is False, wait a few seconds for Cilium to finish initialising. You can also check:
kubectl get crd gateways.gateway.networking.k8s.ioExpose a Service with HTTPRoute
Section titled “Expose a Service with HTTPRoute”The Gateway API request path is: GatewayClass → Gateway → HTTPRoute → Service.
-
Deploy a sample app
Terminal window kubectl create deployment httpbin \--image=kennethreitz/httpbin:latest \--port=80kubectl expose deployment httpbin --port=80 -
Create a Gateway
gateway.yaml apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata:name: my-gatewaynamespace: defaultspec:gatewayClassName: ciliumlisteners:- name: httpport: 80protocol: HTTPTerminal window kubectl apply -f gateway.yamlkubectl get gateway my-gatewayWait until
PROGRAMMEDisTrue:NAME CLASS ADDRESS PROGRAMMED AGEmy-gateway cilium <pending> True 30s -
Create an HTTPRoute
httproute.yaml apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:name: httpbin-routenamespace: defaultspec:parentRefs:- name: my-gatewayhostnames:- "httpbin.local"rules:- matches:- path:type: PathPrefixvalue: /backendRefs:- name: httpbinport: 80Terminal window kubectl apply -f httproute.yamlkubectl get httproute httpbin-route -
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 Gatewaykubectl -n kube-system port-forward svc/cilium-gateway-my-gateway 8080:80 &# Send a request that matches the HTTPRoute hostnamecurl -H "Host: httpbin.local" http://localhost:8080/get
Path-Based Routing
Section titled “Path-Based Routing”Split traffic between services based on URL path prefix:
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: api-route namespace: defaultspec: 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: 3000Header-Based Routing
Section titled “Header-Based Routing”Route requests based on HTTP headers — useful for canary deployments or A/B testing:
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: canary-route namespace: defaultspec: parentRefs: - name: my-gateway rules: - matches: - headers: - name: X-Version value: canary backendRefs: - name: my-app-canary port: 80 - backendRefs: - name: my-app port: 80TLS Termination
Section titled “TLS Termination”Terminate TLS at the Gateway using a Kubernetes Secret:
apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata: name: my-tls-gateway namespace: defaultspec: 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/v1kind: HTTPRoutemetadata: name: my-tls-route namespace: defaultspec: parentRefs: - name: my-tls-gateway hostnames: - "myapp.example.com" rules: - backendRefs: - name: my-app port: 80Migration from Ingress
Section titled “Migration from Ingress”Gateway API replaces the Kubernetes Ingress resource. Here is a side-by-side comparison of a common pattern:
| Concern | Ingress | HTTPRoute |
|---|---|---|
| Entry point | Ingress | Gateway + HTTPRoute |
| Path routing | spec.rules[].http.paths | spec.rules[].matches[].path |
| Host routing | spec.rules[].host | spec.hostnames[] |
| TLS | spec.tls[] | Gateway listener tls section |
| Annotation-based features | Varies by controller | Native spec fields |
Before (Ingress):
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: 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: 80After (HTTPRoute):
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: my-appspec: parentRefs: - name: my-gateway hostnames: - "myapp.local" rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: my-app port: 80Key changes:
- Replace
Ingress→Gateway+HTTPRoute - Move hostname from
spec.rules[].host→spec.hostnames[] - Move path from
spec.rules[].http.paths→spec.rules[].matches[].path - Drop controller-specific annotations — use native
HTTPRoutefilters instead
Using Gateway API in GitOps
Section titled “Using Gateway API in GitOps”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 resourceThe 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.
ksail workload apply -f k8s/# or with GitOpsksail workload pushksail workload reconcileTroubleshooting
Section titled “Troubleshooting”GatewayClass ACCEPTED is False:
kubectl describe gatewayclass ciliumCilium’s controller may still be starting. Wait for Cilium pods to be ready:
kubectl get pods -n kube-system -l k8s-app=ciliumGateway PROGRAMMED is False:
kubectl describe gateway my-gatewayCheck 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:
kubectl get httproute my-route -o yamlCheck 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:
kubectl get crd | grep gateway.networking.k8s.ioRe-run ksail cluster update to reinstall CRDs if they are missing.
Related
Section titled “Related”- Cilium Gateway API docs — upstream reference
- Kubernetes Gateway API docs — spec reference
- Multi-Environment Workflows — managing clusters across environments
- PR Preview Clusters — ephemeral clusters in CI with GitOps