Exposing a Kubernetes cluster to the internet has traditionally involved a trade-off between accessibility and security. The standard approach (port forwarding, Dynamic DNS, and handling TLS certificates on the cluster) leaves the network perimeter vulnerable to scanning and exploitation.
In this case study, I detail the migration from a traditional "Edge Ingress" model using Cert-Manager and Let's Encrypt to a Zero Trust model using Cloudflare Tunnels, Traefik, and GitOps principles.
The Challenge: The Open Port Problem
The initial infrastructure relied on a classic ingress pattern:
- Network: Ports 80 and 443 exposed on the physical router.
- Resolution: Dynamic DNS pointing to the residential IP.
- Termination: Traefik handling SSL/TLS via Cert-Manager (Let's Encrypt).
Friction Points
Attack Surface
The IP address was public, inviting port scanners and DDoS attempts.
Maintenance Overhead
Managing ClusterIssuers, certificate renewals, and ACME challenges added complexity to the Kubernetes manifest surface area.
Dependency
The cluster required direct knowledge of its public IP state.
The Solution: Tunnel-First Architecture
The core solution was a migration to a Cloudflare Tunnel architecture. This inverts the connection model: instead of the internet connecting *in* to the cluster, a lightweight daemon (`cloudflared`) connects *out* to Cloudflare's edge.
1. Architectural Topology
I chose to retain Traefik as the internal router while delegating the "transport" layer to Cloudflare. This "Gateway" pattern allows the Kubernetes Ingress manifests to remain mostly unchanged while significantly hardening the transport layer.
Zero-Exposure Request Flow
Secure Tunnel Architecture
2. Infrastructure as Code & Secret Management
A critical requirement was maintaining a strict GitOps workflow via ArgoCD. This presented a challenge: the Cloudflare Tunnel requires sensitive authentication tokens (`TUNNEL_TOKEN`).
To solve this without compromising the Git repository, I utilized Bitnami Sealed Secrets.
- The raw token is encrypted locally using the controller's public key.
- The resulting `SealedSecret` CRD is committed to Git.
- Inside the cluster, the operator decrypts the token into a standard Kubernetes Secret, which is then injected into the `cloudflared` Deployment as an environment variable.
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
spec:
template:
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
args:
- tunnel
- run
env:
- name: TUNNEL_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-tunnel-token
key: tokenThis ensures the repository remains public-safe while the infrastructure remains fully declarative.
3. The Hybrid DNS Strategy (Split-Horizon)
A unique constraint of this deployment was the existence of a decoupled frontend hosted on Vercel, while the backend API resides in the Kubernetes Home Lab. This required a precise DNS strategy to prevent routing conflicts.
Why this Hybrid Approach?
This architecture is a deliberate trade-off. Hosting the frontend on Vercel provides global Edge Network caching and High Availability (HA) out of the box, ensuring the site remains fast and accessible even if my home lab goes dark. Conversely, hosting the backend and data services on my own infrastructure keeps running costs minimal for the "heavy lifting" components that don't require the same strict 99.99% uptime SLAs.
A split-horizon DNS configuration was implemented at the authoritative level (Cloudflare DNS):
Split-Horizon DNS Strategy
Handling Frontend & Backend Routing
This setup allows Vercel to serve the UI with low latency, while the UI makes fetch requests to `https://api.benbosco.dev`, which securely tunnels into the cluster.
4. Hardening the Gateway
With the tunnel active, Traefik no longer needs to handle certificate generation. The `cert-manager` annotations were stripped from the Ingress resources, simplifying the manifests.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
# No more cert-manager annotations!
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
rules:
- host: api.benbosco.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-gateway
port:
number: 80Furthermore, the Traefik Dashboard, previously exposed via Ingress, was identified as a security risk. The external route for the dashboard was disabled entirely. Operational observability is now achieved securely via `kubectl port-forward` on an as-needed basis, adhering to the principle of least privilege.
Conclusion
By decoupling the transport layer (Cloudflare) from the routing layer (Traefik), the result is a robust, production-grade ingress architecture.
Key Wins
- ✓Zero Open Ports:The firewall now blocks all incoming traffic.
- ✓Simplified Manifests:No more ClusterIssuer or ACME challenge configurations.
- ✓High Availability:The cloudflared deployment runs with multiple replicas, ensuring tunnel resilience.
The infrastructure is now prepared for the next phase: deploying a Go-based microservices API that acts as the logic core for the Vercel frontend.