To expose services in a Kubernetes cluster, you typically need an Ingress backed by a cloud provider's load balancer, and often a NAT Gateway. For small projects, these costs add up fast (though someone may argue small projects shouldn't use Kubernetes at all).
What if you could ditch the Ingress, Load Balancer, and Public IP entirely? Enter Cloudflare Tunnel (by the way, it costs $0).
How Cloudflare Tunnel Works
Cloudflare Tunnel relies on a lightweight daemon called cloudflared that runs within your cluster to establish secure, persistent outbound connections to Cloudflare's global network (edge servers). Instead of opening inbound firewall ports or configuring public IP addresses, cloudflared initiates traffic from inside your cluster to the Cloudflare edge servers. This outbound-only model creates a bidirectional tunnel that allows Cloudflare to route requests to your private services while blocking all direct inbound access to your origin servers.
So basically Cloudflare Tunnel acts as a reverse proxy that routes traffic from Cloudflare edge servers to your private services: Internet -> Cloudflare Edge Server -> Tunnel -> cloudflared -> Service -> Pod.
ref:
https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/
Create a Tunnel
A tunnel links your origin to Cloudflare's global network. It is a logical connection that enables secure, persistent outbound connections to Cloudflare's global network (Cloudflare Edge Servers).
- Go to https://one.dash.cloudflare.com/ -> Networks -> Connectors -> Create a tunnel -> Select cloudflared
- Tunnel name:
your-tunnel-name - Choose an operating system:
Docker
Instead of running any installation command, simply copy the token (starts with eyJ...). We will use it later.
Configure Published Application Routes
First of all, make sure you host your domains on Cloudflare, so the following setup can update your domain's DNS records automatically.
Assume you have the following Services in your Kubernetes cluster:
apiVersion: v1
kind: Service
metadata:
name: my-blog
spec:
selector:
app: my-blog
type: NodePort
ports:
- name: http
port: 80
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
app: frontend
type: NodePort
ports:
- name: http
port: 80
targetPort: http
You need to configure your published application routes based on your Services, for instance:
- Route 1:
- Domain:
example.com - Path:
blog - Type:
HTTP - URL:
my-blog.default:80=> format:your-service.your-namespace:your-service-port
- Domain:
- Route 2:
- Domain:
example.com - Path:
(leave it blank) - Type:
HTTP - URL:
frontend.default:80=> format:your-service.your-namespace:your-service-port
- Domain:
Deploy cloudflared to Kubernetes
We will deploy cloudflared as a Deployment in Kubernetes. It acts as a connector that routes traffic from Cloudflare's global network directly to your private services. You don't need to expose any of your services to the public Internet.
apiVersion: v1
kind: Secret
metadata:
name: cloudflared-tunnel-token
stringData:
token: YOUR_TUNNEL_TOKEN
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tunnel
spec:
replicas: 3
selector:
matchLabels:
app: tunnel
template:
metadata:
labels:
app: tunnel
spec:
terminationGracePeriodSeconds: 25
nodeSelector:
cloud.google.com/compute-class: "autopilot-spot"
securityContext:
sysctls:
# Allows ICMP traffic (ping, traceroute) to resources behind cloudflared
- name: net.ipv4.ping_group_range
value: "65532 65532"
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
command:
- cloudflared
- tunnel
- --no-autoupdate
- --loglevel
- debug
- --metrics
- 0.0.0.0:2000
- run
env:
- name: TUNNEL_TOKEN
valueFrom:
secretKeyRef:
name: cloudflared-tunnel-token
key: token
livenessProbe:
httpGet:
# Cloudflared has a /ready endpoint which returns 200 if and only if it has an active connection to Cloudflare's network
path: /ready
port: 2000
failureThreshold: 1
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
kubectl apply -f cloudflared/deployment.yml
That's it! Check the Cloudflare dashboard, and you should see your tunnel status as HEALTHY.
You can now safely delete your Ingress and the underlying load balancer. You don't need them anymore. Enjoy your secure, cost-effective cluster!