Hosting Public Unifi Controller on Private Kubernetes Cluster

I manage a few Unifi deployments on the side, and part of this is hosting a Unifi controller. For the past year, I have been hosting this controller on a DigitalOcean Kubernetes cluster – which has worked really well. DOKS is a good service (great if you’re a cheapskate like me and don’t want to pay for your controlplane). I have no complaints about their offerings whatsoever.

Given that I recently build a nice shiny new homelab, I wanted to put it to use. I also could reduce my cloud bill by hosting things locally instead. However, one hurdle remained to be overcome – granting public access to the Unifi install from behind my NAT.

I knew about frp from previous research on this topic, but I recently came across https://github.com/b4fun/frpcontroller. User bcho had done the legwork to build a k8s controller to run frp and create tunnels from within Kubernetes. Their code was a bit old though, and needed some updating. I forked their project and re-implemented it onto kubebuilder v3, along with upgrading to Go v1.17. You can check it out at https://github.com/ebauman/frpcontroller.

Here’s the steps I followed to put together these pieces and host Unifi behind NAT.

FRP Server

  1. Spin up a t2.micro instance on EC2
  2. Acquire an elastic IP and associate it with the instance
  3. On this micro instance, download the latest frp release and tar xzvf
  4. Move frps into /usr/local/bin
  5. Create /etc/frp/frps.ini with the following content:
    [common]
    bind_port = 7000
    bind_addr = [PRIVATE IPV4 ADDR]
    token = [YOUR TOKEN]
    
  6. Create a new unit file for the frps service. This file should be /etc/systemd/system/frps.service and should have the following content:
    [Unit]
    Description=fast reverse proxy
    
    [Service]
    ExecStart=/usr/local/bin/frps -c /etc/frp/frps.ini
    
    [Install]
    WantedBy=multi-user.target
    
  7. Execute systemctl daemon-reload, followed by systemctl enable frps --now

FRP Client on Private K8s Cluster

Setting up the private k8s cluster is outside of the scope of this guide.

With the cluster setup, install frpcontroller by calling kubectl apply -f https://raw.githubusercontent.com/ebauman/frpcontroller/main/release/v0.0.2/install.yaml (check for a more recent version in github.com/ebauman/frpcontroller/tree/main/release and use that instead)

Installing frpcontroller also installs two CRDs into your cluster – Endpoint and Service. An Endpoint is the client-side reference for an FRP server. In this case, we’ll make one to point to our newly-created t2.micro instance.

First, create the namespace into which you will eventually install the Unifi controller. kubectl create namespace unifi

Next, create a new file called endpoint.yaml and place into it the following contents:

apiVersion: frp.1eb100.net/v1
kind: Endpoint
metadata:
    name: unifi
    namespace: unifi
spec:
    addr: '1.2.3.4' # your elastic ip. include the quotes
    port: 7000
    token: yourtoken

Create this endpoint by calling kubectl apply -f endpoint.yaml.

Next, you’ll need to install the Unifi controller. There is plenty of documentation on how to accomplish this – I use https://artifacthub.io/packages/helm/k8s-at-home/unifi to do this.

Once Unifi is installed, there are various ports you will need to connect using FRP. Most commonly, these are:

PortUsage
tcp/8080device and app communication
udp/10001device discovery
tcp/8443controller web ui
tcp/6789unifi mobile speed test
udp/3478STUN
udp/5514remote syslog capture
Full list available at https://help.ui.com/hc/en-us/articles/218506997-UniFi-Required-Ports-Reference

Create service.yaml with the following content:

apiVersion: frp.1eb100.net/v1
kind: Service
metadata:
    name: unifi
    namespace: unifi
spec:
    endpoint: unifi
    ports:
    - name: tcp-8080
      localPort: 8080
      remotePort: 8080
      protocol: TCP
    - name: udp-10001
      localPort: 10001
      remotePort: 10001
      protocol: UDP
    - name: tcp-8443
      localPort: 8443
      remotePort: 8443
      protocol: TCP
    - name: tcp-6789
      localPort: 6789
      remotePort: 6789
      protocol: TCP
    - name: udp-3478
      localPort: 3478
      remotePort: 3478
      protocol: UDP
    - name: udp-5514
      localPort: 5514
      remotePort: 5514
      protocol: UDP
    selector:
        [dependent upon your setup, see notes below]

The spec.selector field here works just like it does on a regular k8s service. The values here are dependent upon how you installed Unifi. For instance, my selector(s) are:

selector:
    app.kubernetes.io/name: unifi

Yours may differ. Typically the Unifi chart deploys services for you so you will see selectors implemented on those services – you can copy those.

Create the service in k8s by calling kubectl apply -f service.yaml.

If all goes successfully, you should see a pod created in the unifi namespace that runs the frpc software. Looking at the logs of this pod should show the connection being established to your frps server.

That’s it! Now you can browse to https://your-elastic-ip:8443/ and get to the Unifi page.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s