For KC02, we securely expose Kubernetes services to the internet using Cloudflare Tunnel, powered by cloudflared
. This setup provides a zero-trust model for public access, avoiding the need to open ports on our firewall or expose services directly via public IP.
Note there is no inbound connect to our home network.
cloudflared
makes an outbound connection to Cloudflare’s servers.
Prerequisites
- A Cloudflare account
- A domain managed by Cloudflare DNS (e.g.
kyjung.com
) - A Kubernetes service already available locally that you want to safely expose to the internet. (e.g.
http://linkding:9090
)
Configuring tunnel from CLI
- Install
cloudflared
macOS:
brew install cloudflared
- Authenticate
cloudflared
# This will log in and create a ~/.cloudflared/cert.pem file
cloudflared tunnel login
A browser window will open prompting you to log in to your Cloudflare account. After logging in, select your host name.
- Create Tunnel
# Create tunnel with a name and generate a tunnel credentials file
cloudflared tunnel create <tunnel_name>
- From the output of the command, take note of the tunnel’s UUID and the path to your tunnel’s credentials file. ex.
~/.cloudflared/59c9756.json
- This command will create a subdomain of
.cfargotunnel.com
- Create kubernetes secret yaml file.
kubectl create secret generic tunnel-credentials --from-file=credentials.json=~/.cloudflared/59c9756.json --dry-run=client -o yaml > cloudflare-secret.yaml
- Encrypt secret yaml file using SOPS with age encryption. For more details, see Secrets Management.
# Add AGE_PUBLIC_KEY to environment variables
export AGE_PUBLIC_KEY=age1...
# Encrypt YAML manifest with age key
sops --encrypt --age $AGE_PUBLIC_KEY --encrypted-regex '^(data|stringData)$' --in-place cloudflare-secret.yaml
- Save to
apps/<cluster>/<app>/cloudflare-secret.yaml
Configure DNS
Go to Cloudflare DNS, add a new record to the kyjung.com
domain.
- Type:
CNAME
- Name: What you want the subdomain to be. For example, if I want to deploy the Linkding app to
links.kyjung.com
, the name would belinks
- Target: The .cfargotunnel.com url provided by the create tunnel command. ex.
ae7fc673-9039-438b-a94c-3f15e1847d86.cfargotunnel.com
- Proxy Status: Select
Proxied
Add Tunnel Manifests
Add yaml file for cloudflared
- Save as
apps/<cluster>/<app>/cloudflare.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
spec:
selector:
matchLabels:
app: cloudflared
replicas: 2 # You could also consider elastic scaling for this deployment
template:
metadata:
labels:
app: cloudflared
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
args:
- tunnel
# Points cloudflared to the config file, which configures what
# cloudflared will actually do. This file is created by a ConfigMap
# below.
- --config
- /etc/cloudflared/config/config.yaml
- run
livenessProbe:
httpGet:
# Cloudflared has a /ready endpoint which returns 200 if and only if
# it has an active connection to the edge.
path: /ready
port: 2000
failureThreshold: 1
initialDelaySeconds: 10
periodSeconds: 10
volumeMounts:
- name: config
mountPath: /etc/cloudflared/config
readOnly: true
# Each tunnel has an associated "credentials file" which authorizes machines
# to run the tunnel. cloudflared will read this file from its local filesystem,
# and it'll be stored in a k8s secret.
- name: creds
mountPath: /etc/cloudflared/creds
readOnly: true
volumes:
- name: creds
secret:
secretName: tunnel-credentials
# Create a config.yaml file from the ConfigMap below.
- name: config
configMap:
name: cloudflared
items:
- key: config.yaml
path: config.yaml
---
# This ConfigMap is just a way to define the cloudflared config.yaml file in k8s.
# It's useful to define it in k8s, rather than as a stand-alone .yaml file, because
# this lets you use various k8s templating solutions (e.g. Helm charts) to
# parameterize your config, instead of just using string literals.
apiVersion: v1
kind: ConfigMap
metadata:
name: cloudflared
data:
config.yaml: |
# Name of the tunnel you want to run
tunnel: links
credentials-file: /etc/cloudflared/creds/credentials.json
# Serves the metrics server under /metrics and the readiness server under /ready
metrics: 0.0.0.0:2000
no-autoupdate: true
ingress:
- hostname: links.kyjung.com
service: http://linkding:9090
# This rule matches any traffic which didn't match a previous rule, and responds with HTTP 404.
- service: http_status:404
- Update
apps/<cluster>/<app>/kustomization.yaml
file to includecloudflare-secret.yaml
andcloudflare.yaml
# apps/staging/linkding/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: linkding
resources:
- ../../base/linkding/
- cloudflare.yaml
- cloudflare-secret.yaml
- linkding-container-env-secret.yaml
Resources
This guide is adapted from the cloudflare official guide to use SOPS with age encryption as described in Secrets Management.