I recently changed ISP, and I wanted to monitor its performance and make sure I get what I’m paying for. I initially started writing a bash script that I was running in a crontab, then writing the results in an md file.
But that’s not very sexy. I wanted something graphical with a nice UI.

It turns out that there is a project called Speedtest-tracker, written and maintained by Alex Justesen on GitHub that does just what I was looking for. Behind the scenes, speedtest-tracker uses the official Ookla CLI.

Speedtest Tracker is a self-hosted application that monitors the performance and uptime of your internet connection. Built using Laravel and Speedtest CLI from Ookla®, deployable with Docker.

The cool thing is that Speedtest Tracker is containerized; you can run it anywhere you want! At first, I had installed it as a Docker container on my Synology, but the NAS I own only has 1Gbps Ethernet ports, and my ISP advertises DL/UL speeds of 5 Gbps / 900 Mbps

I have a mini PC that I use for my YaK projects, which embeds a 2.5Gbps Ethernet card. I’d rather use this machine than the NAS. Even though I will not be able to test the full speed my ISP provides, at least I will be able to see if I get near 2.5Gbps, which is already a great download speed.

OK, enough talking, let’s get our hands dirty and let’s deploy Speedtest-tracker!

Your list of ingredients

Here is what you need to add to your recipe:

  • A hypervisor, in my case I’m using Proxmox
  • A virtual machine (on which I’m using SUSE Linux, but any distro will work just fine)
  • A Kubernetes cluster. Keep it simple, install a single node RKE2
  • A persistent volume: local-path provisioner does the job

I’m passing these steps here, but you can find them in my other blog dedicated to installing the YaK, if you need.

Everything is well documented on Speedtest tracker web page

There is already a community manifest available for Kubernetes, written and maintained by Maxime Moreillon.

How to install speedtest-tracker?

Installing speedtest tracker is as simple as deploying 2 yaml files:

  • 1 for the postgreSQL database
  • 1 for the frontend app

The PG database and the application manifests are available here.
All the credit goes to Maxime Moreillon, who wrote these manifests and made them available to the community.
All I had to do was save the files to my “speedtest” folder and adjust them to my context.

localhost:~ # cd speedtest
localhost:~/speedtest # ls -ltrh
total 8.0K
-rw-r--r-- 1 root root 1.1K Jul 29 16:29 postgres.yaml
-rw-r--r-- 1 root root 2.2K Aug  3 18:48 speedtest-tracker.yaml

My Postgres manifest

I haven’t changed a single line from Maxime Moreillon’s code. The manifest includes the PVC definition, the PostgreSQL deployment itself, and a service:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15.1
          env:
            - name: POSTGRES_PASSWORD
              value: password
            - name: POSTGRES_DB
              value: speedtest_tracker
            - name: POSTGRES_USER
              value: speedy
          volumeMounts:
            - mountPath: /var/lib/postgresql/data
              name: postgres
      volumes:
        - name: postgres
          persistentVolumeClaim:
            claimName: postgres

---
apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  ports:
    - port: 5432
  selector:
    app: postgres
  type: ClusterIP

My speedtest-tracker manifest

With a few adjustments from Maxime’s code to fit my needs. It comes with the PVC definition, the application deployment, and the service:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: speedtest-tracker
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: local-path  # Adjust if you're using a different StorageClass

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: speedtest-tracker
spec:
  replicas: 1
  revisionHistoryLimit: 0
  selector:
    matchLabels:
      app: speedtest-tracker
  template:
    metadata:
      labels:
        app: speedtest-tracker
    spec:
      containers:
        - name: speedtest-tracker
          image: lscr.io/linuxserver/speedtest-tracker:latest
          ports:
            - containerPort: 80
          env:
            - name: PUID
              value: "1000"
            - name: PGID
              value: "1000"
            - name: DB_CONNECTION
              value: pgsql
            - name: DB_HOST
              value: postgres
            - name: DB_PORT
              value: "5432"
            - name: DB_DATABASE
              value: speedtest_tracker
            - name: DB_USERNAME
              value: speedy
            - name: DB_PASSWORD
              value: password

########MY PERSONAL ENV VARIABLES########
            - name: APP_NAME
              value: home-speedtest-k8s
            - name: APP_KEY
              value: <generate your own app key>
            - name: DISPLAY_TIMEZONE
              value: Europe/Paris
            - name: SPEEDTEST_SERVERS
              value: "62493"
            - name: SPEEDTEST_SCHEDULE
              value: '*/30 * * * *'
            - name: PUBLIC_DASHBOARD
              value: "true"
#########################################

          volumeMounts:
            - mountPath: /config
              name: speedtest-tracker
      volumes:
        - name: speedtest-tracker
          persistentVolumeClaim:
            claimName: speedtest-tracker

---
apiVersion: v1
kind: Service
metadata:
  name: speedtest-tracker
  labels:
    app: speedtest-tracker
spec:
  selector:
    app: speedtest-tracker
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 30080  # You can change this to any port in the 30000-32767 range

Now, generate your own APP KEY and paste the value in the placeholder in the code above (including the base64: prefix), here is how:


echo -n 'base64:'; openssl rand -base64 32;

And that’s it!
Once your manifests are ready and once you are happy with the environment variables you want (the list of env. variables is available here), you just need to create your namespace on your cluster and apply the configuration:

kubectl create ns speedtest
kubectl apply -f speedtest/postgres.yaml -n speedtest
kubectl apply -f speedtest/speedtest-racker.yaml -n speedtest

After a few seconds, your pods will come up:

localhost:~/speedtest # kubectl get pods -n speedtest
NAME                                 READY   STATUS    RESTARTS        AGE
postgres-6c8499b968-rbwlw            1/1     Running   2 (4d18h ago)   5d21h
speedtest-tracker-7997cbdc8f-64n7c   1/1     Running   0               19h

Enjoy !

If you did things right, you should be able to monitor your internet speed and display the results on a neat UI. In my case, I fire a speedtest every 30 minutes (I know, that’s overkill, but I just wanted to play a bit. I will reduce the frequency to something more reasonable, I promise 😉 )

Speedtest-tracker UI

Cool, no?

To go further

I’d love to monitor the full bandwidth my ISP advertises, but I’m limited by my hardware: my router does not support link aggregation, and it only comes with one 10G fiber-optic WAN interface + one 2.5 Gbps and two 1Gbps LAN interfaces. There is no chance I can test the full fiber-optic capacity with this hardware.

In the future, I might buy a switch that supports LACP and configure my router in bridge mode to be able to reach the full WAN bandwidth, or invest in a router that provides more high-speed interfaces. But to be honest, the investment is not really worth it.

One thing I could do however would be to enable HTTPS and add a Let’s Encrypt certificate to secure the connections to my frontend. That’s an improvement I could make soon.