Skip to content

cfgate.iocfgate v0.2.0-alpha.11 · Release documentation

Gateway API Primer

Gateway API is the Kubernetes successor to Ingress, providing a role-oriented, portable, and expressive API for service networking. If you are migrating from an Ingress-based Cloudflare operator (such as STRRL/cloudflare-tunnel-ingress-controller or adyanth/cloudflare-operator), this page explains the key concepts you need to understand.

Gateway API separates concerns by role: infrastructure providers define GatewayClasses, cluster operators create Gateways, and application developers attach Routes. This maps cleanly to cfgate’s architecture.

A GatewayClass defines which controller handles a class of Gateways. It is a cluster-scoped resource (not namespaced).

cfgate registers the controller name cfgate.io/cloudflare-tunnel-controller. You create a GatewayClass that references this controller name, and cfgate will handle all Gateways bound to that class.

Think of GatewayClass as the “driver”: it tells Kubernetes which software manages Gateways of this type.

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: cfgate
spec:
controllerName: cfgate.io/cloudflare-tunnel-controller

You only need one GatewayClass for cfgate. The controller accepts it automatically when spec.controllerName matches.

A Gateway is a runtime instance bound to a GatewayClass. In cfgate, a Gateway represents a Cloudflare Tunnel endpoint.

The cfgate.io/tunnel-ref annotation connects the Gateway to a CloudflareTunnel resource. The GatewayClass tells Kubernetes that cfgate manages this Gateway; the Gateway itself is the runtime binding between the tunnel and the routes.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cloudflare-tunnel
namespace: cfgate-system
annotations:
cfgate.io/tunnel-ref: cfgate-system/my-tunnel
spec:
gatewayClassName: cfgate
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All

Key points:

  • gatewayClassName: cfgate binds this Gateway to the cfgate GatewayClass
  • cfgate.io/tunnel-ref links to the CloudflareTunnel that provides the actual tunnel
  • allowedRoutes.namespaces.from: All permits routes from any namespace to attach (default is Same, which restricts to the Gateway’s namespace)
  • The port and protocol fields satisfy the Gateway API spec but do not determine what cloudflared actually serves. Cloudflared routing is driven by the routes themselves

Routes attach to Gateways via parentRefs and define routing rules. In cfgate, each route becomes one or more cloudflared ingress rules.

Current route support is HTTPRoute only.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
namespace: default
spec:
parentRefs:
- name: cloudflare-tunnel
namespace: cfgate-system
hostnames:
- app.example.com
rules:
- backendRefs:
- name: my-service
port: 80

This creates a cloudflared ingress rule: app.example.com routes to http://my-service.default.svc.cluster.local:80.

Per-route behavior is configured via annotations on the Route resource. See Annotations Reference for the full list.

The full chain from infrastructure to application routing:

flowchart LR
HR["HTTPRoute
(namespace: default)
parentRefs: cf-tunnel
hostnames: app.example.com
backendRefs: my-svc:80"]
GW["Gateway
(namespace: cfgate-system)
gatewayClassName: cfgate
tunnel-ref: .../tun
listeners: http/80/All"]
GC["GatewayClass
(cluster-scoped)
controllerName:
cfgate.io/cloudflare-tunnel-controller"]
CT["CloudflareTunnel
(namespace: cfgate-system)
spec.tunnel.name: tun
spec.cloudflare: ..."]
HR -- parentRefs --> GW
GW -- gatewayClassName --> GC
GW -- tunnel-ref --> CT
  1. GatewayClass tells Kubernetes that cfgate handles Gateways with controllerName: cfgate.io/cloudflare-tunnel-controller.
  2. Gateway creates the tunnel binding via the cfgate.io/tunnel-ref annotation. The controller sets the Gateway status to Programmed when the tunnel is ready.
  3. Routes define which hostnames and paths map to which backend services. cfgate collects routes attached to Gateways it manages and pushes them as cloudflared ingress rules.
  4. CloudflareDNS (optional) watches routes and creates CNAME records pointing hostnames to the tunnel domain.

cfgate installs into cfgate-system by default:

  • Helm: --namespace cfgate-system --create-namespace creates it automatically
  • Kustomize: The install.yaml manifest includes the namespace definition

CloudflareTunnel and CloudflareDNS resources typically live in cfgate-system alongside the controller. Routes and the services they reference can be in any namespace.

To allow routes from other namespaces to attach to a Gateway in cfgate-system, set allowedRoutes.namespaces.from: All on the Gateway listener:

spec:
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All # Routes from any namespace can attach

Without this, only routes in the same namespace as the Gateway can attach. You can also use Selector with label selectors for finer control.

ConceptIngressGateway API (cfgate)
Controller selectionIngressClassGatewayClass
Runtime instanceImplicit (Ingress resources create it)Explicit Gateway resource
Routing rulesIngress resource (host + path rules)HTTPRoute
Per-route configAnnotations on IngressAnnotations on Route
Multi-tenancyNamespace isolation onlyGateway allowedRoutes with namespace selectors
Protocol supportHTTP/HTTPS onlyHTTPRoute-driven HTTP routing
Role separationNone (one resource does everything)GatewayClass (infra), Gateway (ops), Route (dev)
Cross-namespace routingNot supportedBuilt-in via parentRefs with namespace

If you are migrating from an Ingress-based Cloudflare operator:

  1. Create a GatewayClass and Gateway. These replace the implicit infrastructure that Ingress-based operators manage behind the scenes.
  2. Convert Ingress resources to HTTPRoutes. Each Ingress host/path rule becomes an HTTPRoute. The parentRefs field replaces the IngressClass binding.
  3. Move annotations. Ingress annotations on the Ingress resource move to per-route annotations on HTTPRoute resources. Annotation names may differ; see Annotations Reference.
  4. Set up CloudflareDNS. Ingress operators often handle DNS automatically. With cfgate, DNS management is a separate CRD (CloudflareDNS) that you configure explicitly.

cfgate tunnel ingress supports one Kubernetes Service backend per rule, hostname matching, and omitted, Exact, PathPrefix, or Go-compatible RegularExpression path matches. Any positive weight for the single backend receives all matching traffic. A zero-weight backend receives no traffic; cfgate emits an HTTP 500 response for its match so requests cannot fall through to a broader rule. A rule without a backend also returns HTTP 500. Multiple backend references cannot be forwarded because weighted distribution is not implemented; their matches return HTTP 500.

Method, header, and query matches, rule or backend filters, request timeouts, retries, and session persistence are unsupported. A route using any of these fields receives Accepted=False with reason UnsupportedValue and contributes no forwarding rules. cfgate does not silently drop a restriction. Other valid HTTPRoutes remain active. Missing, unauthorized, or unsupported backend references set ResolvedRefs=False. Their specific matches return HTTP 500, while valid sibling rules remain active. This prevents invalid matches from falling through to a broader forwarding route. Only the canonical empty API group denotes a core Service; the literal group core is unsupported. A missing port defaults to 80 only when the Service actually exposes port 80.

Routes are evaluated by current GatewayClass ownership, listener permissions, namespace selectors, hostname intersection, and cross-namespace backend ReferenceGrants. Existing status is not used as authorization. A transient Kubernetes read failure aborts configuration synchronization rather than publishing a partially resolved configuration.

More specific hostnames precede overlapping wildcard hostnames. Within a hostname, exact paths precede regular expressions, followed by prefixes with the longest source path first. Regular-expression precedence is implementation-defined: longer expressions precede shorter expressions. Equal matches use the oldest route creation timestamp, then lexical namespace/name, then the first matching rule. Regular expressions remain active ahead of a catch-all prefix. For prefixes, trailing slashes are ignored: /foo and /foo/ both match /foo and /foo/bar, but not /foobar. Exact paths retain trailing-slash significance. Configuration hashing preserves this ordered evaluation.