CRD Reference¶
This document provides the API reference for Custom Resource Definitions (CRDs) used by the Cloudflare Tunnel Gateway Controller. The controller ships three project-owned CRDs — GatewayClassConfig, ExternalBackend, and GatewayConfig (per-Gateway data planes) — and watches the standard Gateway API resources.
The field-by-field reference for the three project CRDs, with types and OpenAPI validation, is the API Reference, generated from the Go types the CRDs are built from. It does not render CEL rules, such as the accountId format or the replicas/autoscaling exclusion; those live in the CRD schemas under charts/cloudflare-tunnel-gateway-controller/crds/ as x-kubernetes-validations. This page covers what that one does not: examples, status conditions, and the Gateway API resources.
GatewayClassConfig¶
API Version: cf.k8s.lex.la/v1alpha1 Kind: GatewayClassConfig Scope: Cluster
GatewayClassConfig provides tunnel configuration for the controller. It is referenced by a GatewayClass via spec.parametersRef.
Spec¶
The spec carries only the contract the controller needs for Cloudflare API calls: tunnelID, cloudflareCredentialsSecretRef, and the optional accountId, allowSharedTunnels and maxDataPlanesPerNamespace. Every field is described in GatewayClassConfigSpec. Proxy-side configuration (tunnel token, replicas, liveness probes) lives in the Helm chart proxy.* values; see Helm chart reference. For the data-plane cap, see also the Per-Gateway Isolation guide.
Example¶
apiVersion: cf.k8s.lex.la/v1alpha1
kind: GatewayClassConfig
metadata:
name: cloudflare-tunnel-config
spec:
tunnelID: "550e8400-e29b-41d4-a716-446655440000"
# accountId: "0123456789abcdef0123456789abcdef" # Optional 32-char hex; auto-detected if omitted
cloudflareCredentialsSecretRef:
name: cloudflare-credentials
key: api-token
Status¶
GatewayClassConfig has a status.conditions subresource. The reconciler emits:
SecretsResolved—Truewhen the referenced credentials Secret exists and carries the expected key,Falseotherwise.Valid—Truewhen all validation checks pass;Falsewith the first failure message otherwise.
GatewayConfig¶
GatewayConfig is a namespaced CRD carrying per-Gateway data-plane parameters, referenced from Gateway.spec.infrastructure.parametersRef (group cf.k8s.lex.la, kind GatewayConfig, same namespace). Its presence opts the Gateway into a dedicated proxy Deployment and a dedicated Cloudflare Tunnel.
GatewayConfig Spec¶
The only required field is tunnelTokenSecretRef, a connector-token Secret in the same namespace; the tunnel ID and account are parsed from the token. replicas and autoscaling are mutually exclusive. Every field is described in GatewayConfigSpec.
Replica counts (replicas, minReplicas, maxReplicas) are capped at 100: they are tenant-controlled input on a shared cluster, and an unbounded value is a noisy-neighbour attack. The cap bounds one Gateway, not a tenant — use a per-namespace ResourceQuota for the aggregate.
GatewayConfig Example¶
apiVersion: cf.k8s.lex.la/v1alpha1
kind: GatewayConfig
metadata:
name: edge-config
namespace: tenant-a
spec:
tunnelTokenSecretRef:
name: edge-tunnel-token
autoscaling:
maxReplicas: 10
targetInflightPerPod: 50
See the Per-Gateway Isolation guide for the full workflow.
ExternalBackend¶
API Version: cf.k8s.lex.la/v1alpha1 Kind: ExternalBackend Scope: Namespaced
ExternalBackend defines an out-of-cluster HTTP(S) endpoint that an HTTPRoute or GRPCRoute may target as a backendRef. The in-process L7 proxy ultimately dials a URL, so a route can point at an arbitrary external origin without a Service standing in for it. Unlike a Service of type ExternalName (which only carries a DNS name and infers the scheme from the port), ExternalBackend makes the scheme explicit and lets the host be an address that is not a valid Service name. It is a spec-only resource (no status): a route referencing a missing or malformed ExternalBackend surfaces the failure on the route's own ResolvedRefs condition, mirroring how an unresolvable Service backendRef is reported. See ExternalBackend for usage details.
ExternalBackend Spec¶
scheme, host and port are required; path is an optional base path. Every field is described in ExternalBackendSpec.
ExternalBackend Example¶
apiVersion: cf.k8s.lex.la/v1alpha1
kind: ExternalBackend
metadata:
name: external-api
namespace: default
spec:
scheme: https
host: api.example.com
port: 443
path: /v1
Gateway API Resources¶
The controller watches standard Gateway API resources. For their full specification, see the Gateway API documentation.
GatewayClass¶
Standard Gateway API GatewayClass with parametersRef pointing to GatewayClassConfig:
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: cloudflare-tunnel
spec:
controllerName: cf.k8s.lex.la/tunnel-controller
parametersRef:
group: cf.k8s.lex.la
kind: GatewayClassConfig
name: cloudflare-tunnel-config
Gateway¶
Standard Gateway API Gateway:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cloudflare-tunnel
namespace: cloudflare-tunnel-system
spec:
gatewayClassName: cloudflare-tunnel
listeners:
- name: http
port: 80
protocol: HTTP
HTTPRoute¶
Standard Gateway API HTTPRoute:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
namespace: default
spec:
parentRefs:
- name: cloudflare-tunnel
namespace: cloudflare-tunnel-system
hostnames:
- app.example.com
rules:
- backendRefs:
- name: my-service
port: 80
GRPCRoute¶
GRPCRoute is served by the in-process L7 proxy — gRPC service/method matches map onto /{service}/{method} path rules and the upstream hop is h2c. See GRPCRoute for the full field support matrix.
Standard Gateway API GRPCRoute:
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: my-grpc-service
namespace: default
spec:
parentRefs:
- name: cloudflare-tunnel
namespace: cloudflare-tunnel-system
hostnames:
- grpc.example.com
rules:
- backendRefs:
- name: grpc-server
port: 50051
ReferenceGrant¶
Standard Gateway API ReferenceGrant for cross-namespace references:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-cross-namespace
namespace: backend
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: frontend
to:
- group: ""
kind: Service
Status Conditions¶
Gateway Status¶
| Condition | Status | Reason | Description |
|---|---|---|---|
Accepted | True | Accepted | Gateway accepted by controller |
Accepted | True or False | ListenersNotValid | One or more own listeners are invalid: they conflict (every listener of a conflicting set carries Conflicted: True), use a protocol other than HTTP or HTTPS, or have an allowedRoutes.namespaces.selector that does not parse. False only when no listener is valid; per-listener status names the problem without quoting the selector. The message names the conflicted listeners and the accepted ones |
Accepted | False | InvalidParameters | The Gateway's configuration cannot be resolved: the GatewayClass parametersRef chain is invalid or names a GatewayClassConfig or Secret that does not exist, the per-Gateway parametersRef is invalid, no proxy image is configured, or the Gateway claims a Cloudflare Tunnel it does not own. A read that fails for any other reason, Forbidden included, is retried and leaves the status and address unchanged |
Accepted | False | DataPlaneQuotaExceeded | The Gateway's namespace already holds as many dedicated data planes as maxDataPlanesPerNamespace allows. Implementation-specific reason; the oldest Gateways by creation timestamp keep their planes |
Programmed | True | Programmed | Gateway configured in Cloudflare |
Programmed | False | Invalid | The Gateway is not accepted: its configuration cannot be resolved, it was refused the tunnel it claimed, or it has no valid listener of its own and no attached ListenerSet (see the Accepted reason above) |
Programmed | False | NoResources | The Gateway's namespace is at its dedicated data-plane cap, so no plane was scheduled for it |
A Gateway whose allowedListeners.namespaces.selector does not parse refuses every ListenerSet. No Gateway condition reports it, because the Gateway's own listeners keep serving and the Gateway API defines no condition for it. The Gateway gets a Warning Event with reason InvalidAllowedListeners instead, and each ListenerSet reports Accepted: False with reason NotAllowed. Neither quotes the selector.
HTTPRoute/GRPCRoute Status¶
| Condition | Status | Reason | Description |
|---|---|---|---|
Accepted | True | Accepted | Route accepted and synced |
Accepted | False | NoMatchingParent | No listener or ListenerSet entry matches the parentRef's sectionName or port; every listener or entry it matches is conflicted; or the parent Gateway's allowedListeners refuses the parent ListenerSet, including when that selector does not parse |
Accepted | False | NoMatchingListenerHostname | Route hostnames do not intersect with the hostname of any listener the parentRef selects, whether or not it pins a sectionName or port |
Accepted | False | NotAllowedByListeners | Route namespace or kind not allowed by the listeners the parentRef selects, including one it pins by sectionName or port. A listener whose allowedRoutes.namespaces.selector does not parse is treated as not allowing the route and the other listeners are still evaluated; when none of them admits the route, the rejection message, whatever its reason, names that listener, and the parse error goes to the controller log |
Accepted | False | Pending | Set on every route when the GatewayClassConfig does not resolve, for example when its credentials Secret is missing. Set when the parent Gateway's dedicated data plane is refused (a tunnel claim it does not own, or its namespace over the data-plane quota) or did not resolve; the condition message names the refusal. Also set on a parent the controller could not evaluate, such as one whose ListenerSet or GatewayClass cannot be read, or whose listener admits routes by namespace selector while the route's namespace cannot be read and no other listener the parentRef selects admits the route, when the controller can read that Gateway for status; for those the controller log names the error, and the sync is retried until the parent can be evaluated. A route that another listener admits stays Accepted on it while the sync retries the unreadable one. Proxy-push failures are best-effort: they are logged and counted via the cftunnel_sync_errors_total{error_type="proxy_push"} counter but do not flip Accepted to False / Reason=Pending, and neither does a failed write of the tunnel ingress document, which only feeds the Cloudflare dashboard (see Limitations) |
Accepted | False | UnsupportedProtocol | GRPCRoute only: gRPC cannot be served over an explicit proxy.tunnel.protocol: quic tunnel (cloudflared drops HTTP trailers over QUIC, losing grpc-status). Switch to http2, or auto/unset which the proxy upgrades to http2 for gRPC |
Accepted | False | Conflicted | An HTTPRoute and a GRPCRoute conflict on the same Gateway with intersecting hostnames; the oldest Route by creationTimestamp (ties broken by {namespace}/{name}) is accepted and the other is rejected |
ResolvedRefs | True | ResolvedRefs | Backend references resolved |
ResolvedRefs | False | RefNotPermitted | Cross-namespace reference denied |
ResolvedRefs | False | BackendNotFound | Backend Service not found |
ResolvedRefs | False | InvalidKind | Backend ref group/kind is not a core Service |
Controller-specific advisory conditions¶
Beyond the standard Gateway API conditions above, the controller surfaces domain-prefixed (cf.k8s.lex.la/) advisory conditions for situations the Gateway API defines no condition for — they are informational and do not flip Accepted/Programmed. On routes: cf.k8s.lex.la/RouteShadowed, cf.k8s.lex.la/ProxyConfigPushed, cf.k8s.lex.la/TunnelShared. On a Gateway listener or ListenerSet entry: cf.k8s.lex.la/PermissiveHostname (the allowedRoutes.namespaces.from: All + unpinned-hostname capture combination). Each is described in full on the Limitations page.
API Versions¶
| Resource | API Group | Version | Status |
|---|---|---|---|
| GatewayClassConfig | cf.k8s.lex.la | v1alpha1 | Alpha |
| ExternalBackend | cf.k8s.lex.la | v1alpha1 | Alpha |
| GatewayConfig | cf.k8s.lex.la | v1alpha1 | Alpha |
| GatewayClass | gateway.networking.k8s.io | v1 | GA |
| Gateway | gateway.networking.k8s.io | v1 | GA |
| HTTPRoute | gateway.networking.k8s.io | v1 | GA |
| GRPCRoute | gateway.networking.k8s.io | v1 | GA |
| ReferenceGrant | gateway.networking.k8s.io | v1beta1 | Beta |
Installing CRDs¶
Gateway API CRDs¶
kubectl apply --filename https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.2/standard-install.yaml
Project CRDs¶
Installed automatically by the Helm chart. For manual installation: