Skip to content

Gateway

One shared Cilium Gateway API Gateway resource in the gateway namespace handles all inbound traffic. Cilium provisions a single DigitalOcean Load Balancer for it.

Architecture

Cloudflare Edge (orange-cloud DNS, TLS terminated with edge cert)
    ↓  (CF re-encrypts with Origin CA cert)
DO Load Balancer  (proxy-protocol, CF IP allowlist)
Cilium Gateway  shared  (gateway ns)
    ├── :80  http-redirect  → 301 https
    ├── :443 api-oep-stg    → HTTPRoute in oep-stg ns
    ├── :443 api-mansety-prd → HTTPRoute in mansety-prd ns
    ├── :443 api-us-prd     → HTTPRoute in us-prd ns
    ├── :6001 ws-oep-stg    → HTTPRoute in oep-stg ns
    ├── :6001 ws-mansety-prd → HTTPRoute in mansety-prd ns
    ├── :6001 ws-us-prd     → HTTPRoute in us-prd ns
    ├── :443 argocd-unipuka → HTTPRoute in gateway ns (BD-3.14)
    └── :443 grafana-unipuka → HTTPRoute in gateway ns (BD-3.14)

Tenant isolation via allowedRoutes

Each tenant API/WS listener has allowedRoutes.namespaces.from: Selector with:

selector:
  matchLabels:
    gateway.unipuka.app/tenant: <namespace>  # e.g. oep-stg

This means an HTTPRoute in mansety-prd ns cannot attach to the api-oep-stg listener. Namespace labels are set by TF (k8s_namespaces.tf).

Hostnames

Listener Hostname TLS Secret Tenant ns
api-oep-stg api-staging.unipuka.app tls-unipuka-app oep-stg
ws-oep-stg ws-staging.unipuka.app tls-unipuka-app oep-stg
api-mansety-prd api.mansety.com tls-mansety-com mansety-prd
ws-mansety-prd ws.mansety.com tls-mansety-com mansety-prd
api-us-prd api.us-academy.net tls-us-academy-net us-prd
ws-us-prd ws.us-academy.net tls-us-academy-net us-prd
argocd-unipuka argocd.unipuka.app tls-unipuka-app gateway (same)
grafana-unipuka grafana.unipuka.app tls-unipuka-app gateway (same)

Adding a new hostname

See add-hostname.md for the step-by-step recipe.

DO Load Balancer annotations

The Gateway's infrastructure.annotations set:

  • do-loadbalancer-enable-proxy-protocol: "true" - preserves real client IP.
  • do-loadbalancer-allow-cidrs - Cloudflare IP ranges only; non-CF traffic dropped at LB.

Debugging

Listener Accepted=False

kubectl -n gateway describe gateway shared
# Listeners[].Conditions[type=Accepted].Message
# Common: invalid hostname, certificateRef Secret not found/wrong type

Listener ResolvedRefs=False

# TLS Secret missing or wrong type
kubectl -n gateway get secret tls-unipuka-app -o jsonpath='{.type}'
# expect: kubernetes.io/tls

# ExternalSecret Ready?
kubectl -n gateway get externalsecret tls-unipuka-app

HTTPRoute NotAllowedByListeners

kubectl -n oep-stg describe httproute <name>
# Means tenant ns label missing or wrong listener sectionName
kubectl get ns oep-stg --show-labels | grep gateway.unipuka.app

DO LB not provisioned

doctl compute load-balancer list | grep -i cilium
kubectl -n gateway get gateway shared -o jsonpath='{.status.addresses}'
# If empty after 2 min, check cilium-operator logs:
kubectl -n kube-system logs -l app.kubernetes.io/name=cilium-operator | grep -i gateway

HTTP->HTTPS redirect not working

LB=$(kubectl -n gateway get gateway shared -o jsonpath='{.status.addresses[0].value}')
curl -I -H 'Host: api-staging.unipuka.app' http://$LB/
# expect: HTTP/1.1 301  Location: https://api-staging.unipuka.app/