tl;dr The code for the complete Unifi setup is available in the niels-s/unifi-terraform-example repo
This post is part of a small series, go and read the previous post to setup the firewall
Setting up this Unifi Controller was quite a journey for me. I didn’t immediately settle on the solution I’m using at the moment. And I’ve tried to look for alternative proxies as well without much luck, so I’m going got share my failed attempts.
Kubernetes
Since I’m a heavy user of Kubernetes at work and for other hobby projects, I initially wanted to use Linode’s Kubernetes offering, but unfortunately, this isn’t publicly available yet.
Instead, I’ve turned to Digital Ocean. I could set up a Kubernetes cluster very quickly using their Terraform provider and their command-line tool, doctl made it very straightforward to configure authentication for kubectl.
Using the Helmfile to set up all the necessary component:
- Unifi controller same image as I use now
- MongoDB, because I wanted to use a separate DB
- finally, a loadbalancer.
Everything went fine until I reached setting up the loadbalancer on Digital Ocean. I didn’t do my research correctly in advance, so I didn’t notice that the Digital Ocean Loadbalancers only support TCP connections for now, which isn’t going to work. Since the Unifi Controller needs to receive UDP connections to be able to use its full potential.
PS: the persistent volume claim, on the other hand, worked perfectly with Digital Ocean without the need to configure any storage class first. Unlike I’m used to with GKE (Google Kubernetes Engine).
Searching for the right Reverse Proxy
After I abandoned the Kubernetes approach, I’ve settled on using a single CoreOS host instead. Since I already found out the hard way that I can’t use a Digital Ocean Loadbalancer to proxy UDP traffic, I needed to look for a reverse proxy alternative.
Traefik
I liked to use Traefik because it was the only proxy that comes with Let’s Encrypt integration, which would make my job a lot easier to have proper SSL certificates. The main reason why we are setting up the proxy in the first place.
But after some digging around and being misguided between v1 and v2 documentation, I figured out it doesn’t support UDP traffic either (at the time of writing).
Envoy
After I ruled out Traefik, I further investigated Envoy. Since Envoy is the backbone of Istio, any gained knowledge about Envoy would be great. Envoy comes with many xDS API’s one of which is the SDS (Secret Discovery Service), this service can fetch TLS certificates and secrets from a remote SDS server.
Unfortunately, I couldn’t find a simple SDS server solution that offers Let’s Encrypt integration. I only stumbled on SPIRE, but that seemed like a too complicated solution for what I’m trying to achieve.
Another solution would be to write my own SDS server which integrates with Let’s Encrypt but that’s too much work for now. Perhaps it would be a nice project for in the future. 😛
Naturally, we could also opt to provide the certificates using the Terraform ACME provider, but that means the certificates would only be updated once we apply our Terraform code again in the future. The chances are pretty high that we won’t do that before the short-lived Let’s Encrypt certificates expire, so this is not a proper alternative.
Nginx, the hard way
Eventually, I ended up using Nginx again because it supports TCP and UDP proxying, and there is proper integration to set up rolling Let’s Encrypt certificates. Unfortunately, there aren’t projects which integrate both Nginx and Certbot into one.
I stumbled on the subdavis/coreos GitHub repo, which implements an Nginx Service using the jwilder/nginx-proxy image. This image automatically generates Nginx configuration on the hand of other Docker containers metadata. Together with the Let’s Encrypt service, which uses the jrcs/letsencrypt-nginx-proxy-companion image, it retrieves SSL certificates for all our services.
This approach is an excellent solution if you only need to configure services for HTTP and HTTPS traffic. However, configuring proxies for the other ports required by the Unifi controller looked overly complicated.
Because we are only setting up a single service on this host, the dynamic nature of this approach is not needed. I would only make the setup less evident, so eventually, I abandoned this approach.
By the end, we did use Nginx but with a much simpler, straightforward approach.