Introduction

I have been running UniFi hardware at home for the past year and I'm pretty happy with how it has been performing. I was previously running Cisco Meraki for all my home networking needs, until the subscription renewal was due and I didn't fancy selling one of my kidneys.

When initally looking into the UniFi solution, I noticed that the Cloud Key product was optional and you could actually run and manage the features it provides yourself. The thought of saving money and hacking about with a Raspberry Pi was just too enticing!

This setup has been, touch wood, pretty rock solid for me and not something I was looking to change until I came across a SaaS product called Hostifi. This product was a fully managed cloud version of the controller, no more backup worries and securely accessible from anywhere! I really love this as a solution and I can see why it's such a good fit for many companies.

Hello Civo...

Having worked with Civo for over a year on their Kubernetes project as a Beta tester and Ambassador, I thought why not try and deploy the UniFi controller to their managed Kubernetes platform. Also going one better and adding it to the Civo Kubernetes Marketplace for quick and simple deployment for others.

Getting Started

If you haven't already, get yourself over to www.civo.com and sign up, there is a $250 free credit offer on at the moment too!

Prerequisites

This guide assumes you are up to speed with the basics of the Civo platform and are proficient in using Kubectl. If you are new to Civo and Kubernetes then I recommend reading following "getting started" guide:

Getting Started

Spinning up the cluster

In this guide I will spin up a new cluster using Civo CLI without the default Traefik ingress controller installed, this is so I can deploy Traefik2 later in this guide. This is the command to run in your terminal:

civo k3s create --save --merge -s g3.k3s.medium -w -r Traefik

This will create a new cluster, save and merge it to your KUBECONFIG file.

Once it's created, switch to the new cluster using kubectx (replace the name with your own, mine in this example is called spring-shape):

kubectx spring-shape

Once the new cluster is running we will need to deploy the UniFi Controller itself:

civo k3s application add Unifi-network-controller --cluster spring-shape

After a few minutes you should see all the applications installed and running when you run:

kubectl get pods -A

Deploying Traefik

At time of writing I could only get this to work using my own Traefik manifest and not with the Civo marketplace app. If you happen to get this working with the marketplace app then please let me know and I will update this guide.

Create a new file called traefik-deployment.yaml, copy and paste the following code substituting the the email address for the SSL certificate with your own.

apiVersion: traefik.containo.us/v1alpha1
kind: ServersTransport
metadata:
  name: mytransport
  namespace: default

spec:
    insecureSkipVerify: true
# All resources definition must be declared
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressrouteudps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteUDP
    plural: ingressrouteudps
    singular: ingressrouteudp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsstores.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSStore
    plural: tlsstores
    singular: tlsstore
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: traefikservices.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TraefikService
    plural: traefikservices
    singular: traefikservice
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: serverstransports.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: ServersTransport
    plural: serverstransports
    singular: serverstransport
  scope: Namespaced
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - ingressrouteudps
      - tlsoptions
      - tlsstores
      - serverstransports
    verbs:
      - get
      - list
      - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller

---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: traefik
  labels:
    app: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
        - name: traefik
          image: traefik:v2.4
          args:
            - --log.level=DEBUG
            - --serversTransport.insecureSkipVerify=true
            - --accesslog
            - --entrypoints.web.address=:80
            - --entrypoints.websecure.Address=:443
            - --entrypoints.unifistun.Address=:3478/udp
            - --entrypoints.unifiinform.Address=:8080           
            - --providers.kubernetescrd
            - --certificatesresolvers.myresolver.acme.tlschallenge
            - --certificatesresolvers.myresolver.acme.email=me@email.co.uk
            - --certificatesresolvers.myresolver.acme.storage=acme.json          
          ports:
            - name: web
              containerPort: 80
            - name: admin
              containerPort: 8080
            - name: tcpep
              containerPort: 8000
            - name: udpep
              containerPort: 9000
            - name: udp-unifi-stun
              containerPort: 3478              

---
apiVersion: v1
kind: Service
metadata:
  name: traefik-tcp
spec:
  type: LoadBalancer
  selector:
    app: traefik
  ports:
    - protocol: TCP
      port: 80
      name: web
      targetPort: 80
    - protocol: TCP
      port: 443
      name: websecure
      targetPort: 443
    - protocol: TCP
      port: 8443
      name: unifisecure
      targetPort: 8443
    - protocol: TCP
      port: 8080
      name: admin
      targetPort: 8080                    

---
apiVersion: v1
kind: Service
metadata:
  name: traefik-udp
spec:
  type: LoadBalancer
  selector:
    app: traefik
  ports:
    - protocol: UDP
      port: 9000
      name: udpep
      targetPort: 9000    
    - protocol: UDP
      port: 3478
      name: udp-unifi-stun
      targetPort: 3478

Apply the above file:

kubectl apply -f traefik-deployment

Next you will need to create the ingress rules:

Remember to substitute the url with your own cluster's URL where required below. You can get this from the cluster dashboard or by running civo k3s show your-cluster-name:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroute-unifi-ui
  namespace: unifi
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`unifi.your_cluster_address`) 
    kind: Rule
    services:
    - name: unifi-srv
      port: 8443
      scheme: https      
  tls:
    certResolver: myresolver
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroute-unifi-commms
  namespace: unifi

spec:
  entryPoints:
    - unifiinform

  routes:
  - match: Host(`unifi.your_cluster_address`) && PathPrefix(`/inform`)
    kind: Rule
    services:
    - name: unifi-srv
      port: 8080
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
  name: ingressroute-unifi-stun
  namespace: unifi

spec:
  entryPoints:
    - unifistun

  routes:
  - match: Host(`unifi.your_cluster_address`) && PathPrefix(`/inform`)
    kind: Rule
    services:
    - name: unifi-srv
      port: 3478 

You can now test the UI by opening the URL in a web browser:

https://unifi.yourclusteraddress

Controller Setup

You should now be greeted by the UniFi Controller setup page:

UniFi Controller Setup Interface)

You can follow through the steps and select the options you require, if you are already running a controller you can also restore the config from a backup.

Re-pointing your devices

Because the controller is not on the same network as the devices, the easiest thing to do is manually re-point each one to the new controller address. This can be done by connecting via SSH into each device and running the following, substituting the URL for your new cluster's URL:

set-inform http://unifi.example.com:8080/inform

Once you have done this, the devices should start to turn green on your dashboard.

If you have any questions about this guide then please reach out to me on twitter:

Keith Hubner or catch me in the Civo community Slack!