Skip to content

matheuscscp/cloudflare-gateway-controller

Repository files navigation

cloudflare-gateway-controller

release SLSA 3 FIPS 140-3 compliant Distroless

A Kubernetes Gateway API controller that manages Cloudflare Tunnels to expose cluster services to the internet.

The controller watches Gateway, HTTPRoute, and GRPCRoute resources and automatically provisions Cloudflare tunnels and DNS records to route external traffic to Kubernetes services — no public IPs or LoadBalancer-type Services required.

How It Works

The diagram below illustrates the topology for a single Gateway resource. A cluster can have multiple independent Gateways, each will have its own Cloudflare tunnel and set of tunnel replicas in the namespace of the Gateway.

A single Cloudflare tunnel handles all traffic, and proxied CNAME records point each hostname directly to the tunnel. Multiple HTTPRoutes and GRPCRoutes can attach to the same Gateway — each hostname gets its own CNAME. The tunnel container embeds both cloudflared and a reverse proxy to route requests to the correct backend Service by hostname, path, and protocol, with per-request load balancing through kube-proxy.

flowchart LR
    C((Client))
    C -->|Host: app.example.com| CNA
    C -->|Host: api.example.com| CNB
    subgraph cfe[Cloudflare edge · L7 proxy]
        CNA[CNAME app.example.com]
        CNB[CNAME api.example.com]
    end
    CNA -->|uuid.cfargotunnel.com| T
    CNB -->|uuid.cfargotunnel.com| T
    subgraph Kubernetes
        T[Tunnel replica]
        T -->|Host: app.example.com| SA[Service app]
        T -->|Host: api.example.com| SB[Service api]
    end
    T -->|four HTTP/2 connections| cfe
Loading

Usage

A minimal setup needs a credentials Secret, a GatewayClass, a Gateway, and an HTTPRoute — no CloudflareGatewayParameters required. Credentials come from the GatewayClass parametersRef Secret, and DNS management is enabled for all hostnames by default:

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-creds
  namespace: default
stringData:
  CLOUDFLARE_API_TOKEN: "your-api-token"
  CLOUDFLARE_ACCOUNT_ID: "your-account-id"
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: cloudflare
spec:
  controllerName: cloudflare-gateway-controller.io/controller
  parametersRef:
    group: ""
    kind: Secret
    name: cloudflare-creds
    namespace: default
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
spec:
  gatewayClassName: cloudflare
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-route
  namespace: default
spec:
  parentRefs:
  - name: my-gateway
  hostnames:
  - app.example.com
  rules:
  - backendRefs:
    - name: my-service
      port: 80

For more control, reference a CloudflareGatewayParameters to configure tunnel replicas for high availability, vertical autoscaling, restrict DNS zones, and more:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
spec:
  gatewayClassName: cloudflare
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
  infrastructure:
    parametersRef:
      group: cloudflare-gateway-controller.io
      kind: CloudflareGatewayParameters
      name: my-params
---
apiVersion: cloudflare-gateway-controller.io/v1
kind: CloudflareGatewayParameters
metadata:
  name: my-params
  namespace: default
spec:
  tunnel:
    replicas:
      - name: us-east-1a
        zone: us-east-1a
      - name: us-east-1b
        zone: us-east-1b
      - name: us-east-1c
        zone: us-east-1c
    autoscaling:
      enabled: true
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: "1"
        memory: 512Mi
  dns:
    zones:
      - name: example.com
      - name: other.com

See the CloudflareGatewayParameters docs for all options.

Features

DNS: The controller creates a CNAME record for each hostname declared in the attached routes (HTTPRoute and GRPCRoute). Each CNAME points directly to the tunnel address (<tunnelID>.cfargotunnel.com). When a route hostname is removed, its CNAME is deleted. If a hostname's CNAME already points to another Gateway's tunnel, the route is rejected with Accepted=False to prevent DNS conflicts across Gateways.

Cloudflare resources: 1 tunnel, 1 CNAME record per route hostname.

Kubernetes resources: Per Gateway, the controller creates a tunnel Deployment, a tunnel token Secret, a routes ConfigMap, a ServiceAccount, a Role, and a RoleBinding.

Replicas: Multiple named replicas can be configured per tunnel for high availability. Each replica creates a separate Deployment with optional placement controls (zone, nodeSelector, affinity). See CloudflareGatewayParameters for configuration details.

Deployment patches: RFC 6902 JSON Patch operations can be applied to the cloudflared Deployment for advanced customization (e.g. tolerations, node selectors). See CloudflareGatewayParameters for details.

Container resources and autoscaling: CPU and memory requests/limits are configurable for the tunnel container. Vertical Pod Autoscaler (VPA) support is available for automatic resource tuning. See CloudflareGatewayParameters for details.

Token rotation: The controller automatically rotates tunnel tokens on a cron schedule (default: every Thursday at 6 PM America/Los_Angeles time, configurable via CloudflareGatewayParameters). On-demand rotation is also available via the CLI. Rotation updates the in-cluster Secret and performs a rolling restart of the tunnel pods so they pick up the new token.

Observability: The controller creates a CloudflareGatewayStatus (short name: cgs) per Gateway, providing a quick view of tunnel info, conditions, and managed resources:

$ kubectl get cgs
NAME         TUNNEL ID    DNS       READY
my-gateway   abcd-1234…   Enabled   True

CLI

The cfgwctl CLI provides operational commands for managing Gateways. Binaries are available from GitHub releases.

# Suspend/resume reconciliation
cfgwctl suspend gateway my-gateway
cfgwctl resume gateway my-gateway

# Trigger on-demand reconciliation
cfgwctl reconcile gateway my-gateway

# Rotate the tunnel token on-demand
cfgwctl rotate gateway token my-gateway

# Watch an ongoing token rotation
cfgwctl watch gateway token my-gateway

See the CLI documentation for all available commands.

Embedded reverse proxy

The tunnel container embeds a reverse proxy that solves the load-balancing problem with cloudflared's persistent connections. Without it, cloudflared opens a single long-lived TCP connection to each backend Service, bypassing kube-proxy and pinning all traffic to one pod.

The embedded reverse proxy receives all traffic from cloudflared and routes requests by hostname, path prefix, and protocol (HTTP vs gRPC) to the correct backend Service. HTTP requests use HTTP/1.1 with keep-alives disabled so every request opens a fresh connection through kube-proxy for proper pod-level load balancing. gRPC requests use HTTP/2 cleartext (h2c) to preserve streaming semantics. When a route rule has multiple backendRefs with weight fields, the proxy distributes requests across backends according to their weights (traffic splitting). The proxy also supports session persistence via cookie-based or header-based affinity to pin a client to the same backend across requests.

API Token Permissions

The Cloudflare API token must have the following permissions:

Permission Scope Purpose
Cloudflare Tunnel: Edit Account Create, configure, and delete tunnels in the account
DNS: Edit Zone(s) Create, update, and delete CNAME records in the zone(s)

About

A (solid!) Gateway API controller for Cloudflare tunnels.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages