Using K3S: Overcoming CGNAT with Cloudflare Tunnel

If you want to deploy with Terraform, look here.

I purchased three mini computers on Black Friday to use for K3S learning and self hosting. Naturaly, I want to access the cluster from the internet but there are some issues. The house only has copper run to it so the only internet options are cellular and Starlink. In both cases, port forwarding is next to impossible due to firmware restrictions and CGNAT. I will be explaining how I accessed the HTTP services via Cloudflare Tunnel.

K3S

K3S is a lightweight version of Kubernetes, tailored for simplicity and a smaller resource footprint, making it ideal for environments with limited resources. Unlike the full-fledged Kubernetes, K3S is designed to run efficiently on as few as three nodes, which is perfect for compact setups like mini computers. This minimalistic approach doesn’t compromise on functionality, maintaining high compatibility with standard Kubernetes features. It’s an excellent choice for edge computing, development, testing, or small-scale production environments. If you’re exploring efficient and manageable orchestration solutions, K3S could be your go-to option. For an in-depth understanding, see the K3s documentation.

CGNAT

There’s NAT for a home router and theres NAT as scale, Carrier Grade NAT. To extend the life of IPv4, CGNAT is used by some ISPs to reduce the amount of used IPv4 addresses by having multiple devices(customers) share an address. This makes port forwarding for residental customers impossible.

The Solutions

Since there’s no way to directly connect to devices behind a CGNAT, the only real solution is to forward that traffic over a virtual network. There are commercial services for this offering port-to-port forwards targeting gaming and self hosting, DIY VPS setups, and enterprise offerings like Cloudflare Tunnel. I’m choosing Cloudflare Tunnel in this case because it’s free, has great reliability, and can be automated with Terraform and Ansible.

I use OpenVPN and Caddy as the current fix which works but is tied into other infrastructure I plan on refactoring.

Another solution out of this scope is using IPv6 if the ISP supports changing network settings. Starlink’s router bypass feature allows for one to request and allocate a public IPv6 block to clients and route the traffic directly to them. IPv6 has a truly massive supply of addresses with ISPs and datacenters handing out /64 blocks(~18,446,744,073,709,551,616 IPs) to each customer as it’s the minimum subnet allocation.

Requirements

  • A k3s or kubernetes cluster
  • A pod with HTTP or use the example below
  • kubectl access to said cluster
  • Cloudflare account
    • Your choice of secret management(I use KeepassXC for personal use)

It’s easy enough to install cloudflared on a Linux sever and achieve the same goal. Cloudflare has good instructions for this as well.

Create a Cloudflare Account

Login to Cloudflare at https://one.dash.cloudflare.com.

Note: Cloudflare ZeroTrust isn’t in the same dashboard as standard Cloudflare.

Create a Tunnel and Generate the Cloudflared Token(non-Terraform)

In the menu on the left, go to Access. You should see the Tunnels option. Create a tunnel and name it. Cloudflare Tunnel You should now see install instructions. The install command has your cloudflared token. Copy the token. Cloudflare Tunnel

Create a Kubernetes Secret

Create a secret using kubectl and the secret copied above. If you don’t have kubectl configured for your cluster, look here for instructions.

kubectl create secret generic tunnel-secret --from-literal=token=YOURTOKENHERE

Create Public Hostnames

Now that you’ve created a tunnel, you can to create hostnames to access your resources. Go access the tunnel page and select Public Hostname and add a public hostname.

Cloudflare Tunnel

The entries are fairly self-explanatory. The service is the internal protocol and url as if accessed from inside the cluster(which it’s doing). For my personal website, the ingress is named personalsite and accessed via port 3000.

Cloudflare Tunnel

You can also access external resources.

Cloudflare Tunnel

With a public hostname created pointing to your Kubernetes resources, we can deploy cloudflared to your cluster.

Create the Kubernetes Deployment Manifest

Modify the manifest to suit your needs.

cloudflared-deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: cloudflared
  name: cloudflared-deployment
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      pod: cloudflared
  template:
    metadata:
      labels:
        pod: cloudflared
    spec:
      containers:
      - name: cloudflared
        image: cloudflare/cloudflared:latest
        command:
          - cloudflared
          - tunnel
          - --metrics
          - 0.0.0.0:2000
          - run
        args:
          - --token
          - $(TOKEN)
        env:
          - name: TOKEN
            valueFrom:
              secretKeyRef:
                name: tunnel-secret
                key: token
        livenessProbe:
          httpGet:
            path: /ready
            port: 2000
          failureThreshold: 1
          initialDelaySeconds: 10
          periodSeconds: 10

Deploy the Manifest

kubectl apply -f cloudflared-deployment.yml

Check on the deployment: kubectl get deployment cloudflared-deployment

Check on the pods: kubectl get pods -l pod=cloudflared

Check on the logs: kubectl logs -l pod=cloudflared

2023-12-12T04:09:54Z INF Registered tunnel connection connIndex=3 connection=in534653-e39b-4a3b-ad86-ien643 event=0 ip=your-public-ip location=clt01 protocol=quic

Check for Success

Try to access your resource. In my case it’s https://testing.daniel-mcdonough.com

Also check the Tunnel dashboard. It should show “HEALTHY” if cloudflared is connected. Tunnel status

Troubleshooting

  • If the tunnel is not healthy, check the cloudflared pod logs: kubectl logs -l pod=cloudflared. If the secret is causing the error, try modifying or recreating the Kubernetes secret.

  • If the tunnel is healthy, make sure you have the correct pod name and port in your public hostname config on the tunnel dashboard.

https://docs.k3s.io/cluster-access

https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/

https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/tunnel

updatedupdated2023-12-172023-12-17