In my quest to get everything that was previously running in Docker on my Synology to (micro)K8s, I now want to run my Unifi controller on it.

Of course I need persistent storage for this, for that I will be using NFS, just like my SonarQube setup. Because the Unifi devices need a fixed IP to talk to I also will be using the MetalLB loadbalancer that comes with MicroK8s.

The image that I'm using will be the same as on my Synology: this one. So I quickly came up with a yaml that will deploy this on K8s:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-unifi
spec:
  storageClassName: "" # same storage class as pvc
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.1.2 # ip addres of nfs server
    path: "/volume1/nfs/unifi" # path to directory
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-unifi
spec:
  storageClassName: ""
  accessModes:
    - ReadWriteMany #  must be the same as PersistentVolume
  resources:
    requests:
      storage: 8Gi
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: unifi-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      name: unifi-controller
  template:
    metadata:
      name: unifi-controller
      labels:
        name: unifi-controller
    spec:
      volumes:
        - name: nfs-unifi
          persistentVolumeClaim:
            claimName: nfs-unifi
      containers:
        - name: unifi-controller
          image: 'jacobalberty/unifi:6.0.45'
          ports:
            - containerPort: 3478
              protocol: UDP
            - containerPort: 8080
              protocol: TCP
            - containerPort: 8443
              protocol: TCP
            - containerPort: 8843
              protocol: TCP
            - containerPort: 8880
              protocol: TCP
            - containerPort: 6789
              protocol: TCP
          volumeMounts:
            - name: nfs-unifi
              mountPath: /unifi
---
kind: Service
apiVersion: v1
metadata:
  name: unifi-controller
  labels:
    app: unifi-controller
spec:
  ports:
    - name: '3478'
      protocol: UDP
      port: 3478
      targetPort: 3478
    - name: '8080'
      protocol: TCP
      port: 8080
      targetPort: 8080
    - name: '8443'
      protocol: TCP
      port: 8443
      targetPort: 8443
    - name: '8843'
      protocol: TCP
      port: 8843
      targetPort: 8843
    - name: '8880'
      protocol: TCP
      port: 8880
      targetPort: 8880
    - name: '6789'
      protocol: TCP
      port: 6789
      targetPort: 6789
  selector:
    name: unifi-controller

Now the configuration of the loadbalancer took me some research, because the Unifi controller uses both UDP and TCP. And you cannot mix those in a LB configuration. The solution is to create two configs with the same IP. To allow this you need a special annotation metallb.universe.tf/allow-shared-ip: 'true'. With this the whole LB config will become this:

kind: Service
apiVersion: v1
metadata:
  name: lb-unifi
  annotations:
    metallb.universe.tf/allow-shared-ip: 'true'
spec:
  ports:
    - name: '8080'
      protocol: TCP
      port: 8080
      targetPort: 8080
    - name: '8443'
      protocol: TCP
      port: 8443
      targetPort: 8443
    - name: '8843'
      protocol: TCP
      port: 8843
      targetPort: 8843
    - name: '8880'
      protocol: TCP
      port: 8880
      targetPort: 8880
    - name: '6789'
      protocol: TCP
      port: 6789
      targetPort: 6789
  selector:
    name: unifi-controller
  type: LoadBalancer
  loadBalancerIP: 192.168.1.21
---
kind: Service
apiVersion: v1
metadata:
  name: lb-unifi-udp
  annotations:
    metallb.universe.tf/allow-shared-ip: 'true'
spec:
  ports:
    - name: '3478'
      protocol: UDP
      port: 3478
      targetPort: 3478
  selector:
    name: unifi-controller
  type: LoadBalancer
  loadBalancerIP: 192.168.1.21

I'm already running a Ingress with wildcard certificates, so I also want to expose the Unifi dashboard through this. Easy enough with this yaml:

kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
  name: ingress-unifi
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
spec:
  rules:
    - host: unifi.app.singel.home
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: unifi-controller
                port:
                  number: 8443

This and giving it a DNS record will make it available on port 443 with my existing ingress certificate.

Now, I'm already running the Unifi controller so all my devices point to the Docker (Synology) IP. I've found numerous ways to migrate to a new Unifi controller ranging from logging in on all devices and starting all over again. The easiest one that worked for me is:

  • Backup the config on the old controller
  • Restore this config on the new controller
  • Use the UI in the OLD controller to announce the new one

This last step is done by entering the new IP/hostname here and checking the Override inform host with controller hostname/IP:

After a few minutes all your devices will log on to the new controller, and you are done!

The Unifi controller is running on K8s. Hope this helps.