March 31

Wireguard in Kubernetes – V2

One of the first things I added to Kubernetes was OpenVPN, so that I can use devices away from home in a secure manner. It worked well, but had some shortcomings…

First, it resulted in a slower connection. Granted, use of this meant that there were two passes across my home Internet connection (device to home via tunnel, home to destination, destination to home, home to device via tunnel), it still wasn’t too fast.

Second, when using OpenVPN, I couldn’t (or didn’t know how to) make use of the PI-Hole advertisement blocker running in my cluster. That meant even more content being processed, when using OPenVPN.

Third, it was a little tedious setting up clients. Creating client configurations on the OPenVPN pod, extracting them off, transferring them to devices, like phones, so I could load them into the OpenVPN client.

Fourth, there were some places, where I could not seem to establish a connection via the tunnel, using OpenVPN. I never really figured out why, as I was usually using my phone, versus laptop, so I couldn’t really diagnose what the issue was.

 

Enter Wireguard

Wireguard is touted as a faster, newer, more secure (latest cryptographic algorithms), and more performant than OpenVPN. I was anxious to get it running under Kubernetes. Through the process, I did learn a few things…

Speed! Running https://www.speedtest.net/ from my laptop at home, I would see these numbers:

Method Download Upload
Normal Wi-Fi connection 513.81 Mbps 41.68 Mbps
Proton (Free) VPN 278.05 Mbps 32.53 Mbps
OpenVPN at home 11.77 Mbps 9.94 Mbps
Wireguard at home 77.31 Mbps 40.72 Mbps

Obviously, Proton is faster as there is only one pass across my Internet connection, whereas OpenVPN and Wireguard require two passes across my connection. Wireguard is 4-6x faster that OpenVPN.

As part of the configuration with Wireguard, you specify the IP address of the DNS server to be used. In my case, I provided the LoadBalancer IP of my Pi-Hole server in my Kubernetes cluster, so that I had advertisement blocking available. This is really great addition.

Client setup with Wireguard involves a similar process of creating a configuration file, and getting it onto the client device, but it is a bit easier, as you can create keys and config files on your development machine. You still need to place a server config file on the Wireguard pod, but it is an easier process. I made scripts to simplify the process more. There is one nice feature with Wireguard, where you can convert the configuration file to a QR code, and then scan it from the device. This worked very well for phones and was easy to do.

One thing that OpenVPN had that I liked, was the display of connection information, along with a graph of the current transfer rate:

Wireguard just shows the cumulative packet received/sent count. Not a big deal, but was kind of nice.

I used information from https://blog.jamesclonk.io/posts/wireguard-on-kubernetes/, which had instructions on setting up Wireguard under Kubernetes and using Ad-Blocker to provide advertisement blocking. I created several scripts to help automate the process.

 

Prerequisites

To get Wireguard working, you’ll need the following…

  • A Kubernetes cluster running (obviously).
  • PI-Hole server running, if you want advertisement blocking.
  • Poetry installed on your development machine.
  • Python installed (I’m using 3.13, currently).
  • Install Wireguard clients on the devices you intend to use.
  • A registered domain name, so that your Wireguard server can be accessed remotely. If you had OpenVPN running, you’d already have this.
  • On your router, you need to forward packets for port 51820 to the LoadBalancer IP that you setup for the Wireguard service.

On your development machine, clone my Github repo…

cd ~/workspace/kubernetes
git clone https://github.com/pmichali/wireguard.git
cd wireguard

Run “poetry install” and then “poetry shell” to setup the needed packages. We want wireguard-tools installed on the development machine, so that public/private keys can be created. On MacOS, I invoked “brew install wireguard-tools”.

 

General Configuration

We’ll use a wireguard.ini file to hold some of the general configuration settings. There is a wireguard.ini.sample that you can copy and then fill out the values. The file has:

[DEFAULT]
subnet = #.#.#
external_ip = A_DOMAIN_NAME_OR_IP
dns = LOADBALANCER_IP_OF_PIHOLE_SERVICE
clients = NAMES,OF,CLIENT,DEVICES

We’re assuming that the tunnel will be a class C network, so the first three octets of the IP address will be provided here. For example, “10.10.10”. For each device, we will specify the last octet of the IP address, when we are doing device configurations.

Your registered domain name would be provided for the external_ip value. This allows devices to find your Wireguard service, when out on the Internet.

The IP of the DNS server to use, is specified for the “dns” value. You can use a public DNS, but to get the benefit of advertiser blocking, I used the LoadBalancer IP of my PI-Hole’s service.

Finally, provide a list of (arbitrary) client names that you want to use. No spaces, after the commas.

 

Create Device Keys

Public and private keys are needed for the server and each of the clients. There is a python script that will use the wg command from wireguard-tools to create the keys. You’ll run it for each device, using the syntax:

python create-device.py NAME INDEX#

Where NAME is an arbitrary name for the device (except “server” is used for the server keys). For the clients, these unique names will be what you listed under the clients key in the wireguard.ini file above. 

The INDEX# is an octet used for the device’s tunnel IP address. This will be 1 for the server, and 2-254 for clients. It gets appended to the “subnet” specified in the wireguard.ini to form the IP for each device.

This script will create a NAME.ini file in the data sub-directory (created, if needed). The file will contain the generated public and private keys, and the INDEX#.

So, at a minimum, this is run for the server, and then again for each client:

python create-device.py server 1
python create-device.py foo 4
python create-device.py bar 5

 

Create Configurations and QR Codes

Now that we have generated all the keys for the server and clients, and have identified the final IP address octet for each of the devices, the server configuration file, client configuration files, and the QR images that represent the client configuration files can be created.

Run the following command to create everything:

python build.py

This will create a data/wireguard-secrets.yaml file that is used to create a Kubernetes secret holding the server configuration. It has the server private key and IP address, IPTABLES rules for the tunnel, all the client public keys and IP addresses, and sets the tunnel MTU. I found that, by default, the tunnel MTU was 1400 and that was causing a problem with my MacOS laptop client. It is set to 1420 to resolve the problem (see notes later on how this was determined).

The server.tmpl file provides a template for the YAML file. You can customize the configuration, if needed.

For each client, it will create a data/client-NAME.conf file with the client private key, client tunnel IP address, DNS IP (from the wireguard.ini), server public key, domain name (or IP) and port used to access the Wireguard server from the Internet, and allowed IPs (0.0.0.0/0 and ::0, which specify that all traffic will use the tunnel).

You could specify the IPs used for the tunnel, if you wanted to have some traffic use the tunnel and some not use the tunnel.

The client.tmpl file is used to control the content in each of the client configuration files. You can customize the configuration, if needed.

The last step of this script is to use the generated client configuration files to create a PNG file with the QR code containing that configuration. You can either, load the .conf file into the client Wireguard app, or have it read the QR code.

 

Start Up The Wireguard Server

Now that we have the secret containing the server config, we can start the Wireguard server. Apply the secret YAML, and then the deployment:

kubectl apply -f data/wireguard-secrets.yanl
kubectl apply -f wireguard.yaml

You should see a deployment, service, and pod running. There is a exec-into-pod script that can be invoked to access the wireguard pod. From there, you can run “wg show” to see the list of peers and connection statuses, or “wg showconf wg0” to see the configuration settings. The show command has several options available. Use “-h” to see what can be performed.

 

Connect!

With a client, load its configuration file, or scan the QR code. Then, activate the link, and confirm that all traffic is now going through the tunnel. You can look at the server’s “wg show” output, use Wireshark, etc. Be sure to try going to several web sites to ensure that the MTU is correct for your client.

 

Final Notes

MTU

When I was trying my Mac laptop, most HTTPS websites would not load. It would hang at the authorization stage. That is when I found there was an issue with the MTU. I found a link on optimizing MTU that said to calculate the right MTU, do pings w/o fragmentation, specifying a size. When you determine the maximum size that works, add 28 and that is the tunnel MTU to use.

For MacOS, for example, I found that this command, with a size 1392, was the highest I could go:

ping -D -s 1392 cisco.com

Adding 28, gives 1420, so that is the MTU I used. The default of 1400, was causing problems.

Adding A Client

If you need to add a client, do these steps:

  • Run the create-device.py script to generate the keys.
  • Add the client name to the list of clients in the wireguard.ini file.
  • Re-run the build.py script to update the secret with server config and to create the client config.
  • Apply the data/wireguard-secrets.yaml file so the new server config is stored.
  • Run the delete-pod helper script to cause a new pod to be created with the updated server configuration.
  • Load the data/client-NAME.conf or read the data/NAME.png QR file in the Wireguard client.

Deleting A Client

Deleting a config would be similar, removing the client name from wireguard.ini, building a new secret, applying the YAML, and deleting the pod to load the new config.

 

 

Category: Kubernetes, Raspberry PI | Comments Off on Wireguard in Kubernetes – V2
March 6

Viewmaster Updates

I finally had a chance to improve the Viewmaster app, adding the following features (version v0.2.1, unless marked):

  • Movies include (optional) plot, actor(s), director(s), and poster with the help of IMDB info.
  • Persisting the display mode, whether details are seen, and whether Laser Discs are shown.
  • Improved the display of movie information (and show more info, when looking at detailed info).
  • Command line utility to update movies quickly, with IMDB information.
  • Updated to newer Poetry version for dependency management.
  • Local Development of Django app.
  • v0.2.2: Ability to manually enter IMDB ID during movie lookup for adding/editing movies.
  • v0.2.2: Python script to create bash script for local development use.
  • v0.2.2: Miscellaneous tweaks.
  • v0.2.3: Use a pull-down for display mode instead of buttons.
  • v0.2.3: Allow search by title, actors, directors, or plot.

 

Details On Changes

Here are the details of all the things that were changed in the app.

IMDB Info

By far, the biggest change was using the OMDB.com API to obtain info on the move. You can search by full/partial title, and get a list of media (movies, games, TV series, etc). Then, using the movie ID provided, you can get plot, actor(s), director(s), release date, genres, MPAA rating, duration, URL link to cover (poster), review ratings, and more.

The database was modified to add additional, optional, fields for the plot, actor(s) director(s), movie ID, and poster URL link.

When adding a new movie, you will first see a form to enter a partial title for the movie. A search is done, and the candidate movies are shown. Clicking on an entry will select and load the IMDB details into the form for completing the addition. The If there is no match in IMDB, or the choices are not correct, you can click the “Skip” button to continue without any IMDB info filled out.

With IMDB info, besides plot, actors, and directors, the release date, MPAA rating, and duration will be filled out. For the genre selection, the pull down list will show the entries that the IMDB had first, and then all the rest of the available genres.

When editing a movie, if there already is IMDB info, it will go directly to the edit form. If there is no IMDB info, the title will be used to do a search and the candidate movies will be shown. Like adding, you can select one, or click “Skip” to continue without IMDB info.

If the release year, rating, or duration in the IMDB database differ from what you had already, the values will be shown in RED (you can look at the log to see what the differences are). In v0.2.2, the old values are shown below the entry field, so that you can see the change. You can keep the change, or enter the original value into the field. For the genre selection, the pull down list will show the entries that the IMDB had first, and then all the rest, with your existing genre selection as the chosen one.

v0.2.2: When viewing the results of an IMDB lookup on movie title during add/edit operations, besides selecting one of the candidates, you can enter the IMDB ID (available in the URL, when looking at movie info at https://www.imdb.com/). This ID starts with the letters “tt” and then has a number. Once, selected, the add/edit movie form will be populated with that information.

Persisting Display Info

Now, when a display mode is selected (e.g. alpha, genre, date,…), this setting will persist, even after doing adds, edits, or searching.

Likewise, if you select to show Laser Disc info, and/or details for movies, this setting will persist, until you change the setting and select a mode.

Improved Movie List

For the list of movies, when you hover over a movie, it will be highlighted, and you can click anywhere on the row to select the item.

The summary view is similar to what was there before, but the detail view now has the cover displayed along with multi-line output showing the plot, actors, and directors info.

Importing IMDB Info

It’s a slow process editing all the movies to select IMDB info to be imported. So, a command line utility was created that will go through each movie that does not have IMDB info, show the movie title, release date, rating and duration, and then a list of candidates.

You can skip the candidates, or select one by number, for which it will show you the title, release date, rating, duration, IMDB ID, plot, actors, and directors. For the release date, rating, and duration it provides an indication of whether the info matches (and if not, what was different). You can confirm the choice or go back to select another candidate.

On each prompt, the default choice is shown in square brackets. For candidate selection, that is usually “0” (skip), and for the confirmation it is “S” (save). You can exit the process at several places, and each time this tool is run, it will continue with any movies missing IMDB info.

Poetry Update

As some time has passed, Poetry has gone through updates, and some made significant changes to the config files used, so I changed things up, and used this time to update versions of packages used.

Local Development

The whole point of the Viewmaster project work I initially did, was to port it from a Docker container to run under Kubernetes. However, during development work, it is nice to just tweak the app and be able to locally see the effects on your development machine, rather than re-building, pushing, restarting the pod.

I decided that I would have the locally run app use the “production” database, rather than creating a Postgres server on my Mac to provide a test database. Riskier, but I also setup a script to make easy backups of the database.

Miscellaneous Tweaks

For v0.2.2, added a “show-pod-log” script to allow easy viewing of the Viewmaster pod log in Kubernetes. There already are scripts to exec into the app and database pods. As mentioned below, there is a Python script that will create the dev-setup.bash script used for local development.

Display Mode

For v0.2.3, the display mode is a single pull-down menu, instead of a set of buttons (that are mutually exclusive). You can also change the check boxes, and then re-select the same mode, and the display will be refreshed.

Enhanced Search

For v0.2.3, a select pull-down was added to the search field so that the search can be relative to the title, actors, directors, or plot. When the search input is entered or magnifying glass clicked, the display will show the (case-insensitive) results.

 

How To Use New Version

I (finally) created a tag on the Github repo, called V0.1.2 for the original version, and then created a new tag v0.2.1 for the latest version (I should have done more tags along the way). In any case, this section will describe how update the existing app under Kubernetes, how to run the app locally, and how to setup to use the command line IMDB importing tool.

Backup First!

Before starting this venture, it’s a good idea to backup the database for Viewmaster. The new version has a script to help access the database pod, and you can then run the backup command, provide the password, exit, and then move the database export to your computer. Here are the steps:

cd ~/workspace/kubernetes/viewmaster
./exec-into-db-pod
pg_dump -U viewmasterer -W -F t viewmasterdb > viewmasterdb.tar
[enter password]
exit

./move-backup

This will rename the file with the date/time, so you have a copy of the database, in case things go awry.

Updating to v0.2.3

This is assuming you have an installation of the original Viewmaster app running. If you’re starting from scratch, you need to follow the instructions from the original article, and make a few changes, as described here, to work with the latest. Additional values for secrets, using the newer version are most of the differences.

The first step to update is to move to the GIT repo for the app (cloned from https://github.com/pmichali/viewmaster.git) and pull the latest code by checking out the tag v0.2.3.

You should already have a deploy/viewmaster-secrets.env file from the previous version. Use deploy/viewmaster-secrets.env.template to see the new fields. Currently, that is just the OMDB_API_KEY. To set that value, go to https://www.omdbapi.com/ and click on the API tab. Enter your email address and a free API key will be emailed to you. This allows 1,000 requests per day. If you want, you can become a patron member for unlimited access. Place this value in the viewmaster-secrets.env file. You need to replace the existing secret with this new one:

cd ~/workspace/kubernetes/viewmaster/deploy
kubectl delete secret -n viewmaster viewmaster-secrets
kubectl create secret generic viewmaster-secrets -n viewmaster \
--from-env-file=viewmaster-secrets.env

Next, you need to build and push this version of the app for Kubernetes (Note: on a Mac, you need to have Docker desktop running):

cd ~/workspace/kubernetes/viewmaster
docker buildx build . -t YOUR_DOCKER_ID/viewmaster-app:v0.2.3
docker push YOUR_DOCKER_ID/viewmaster-app:v0.2.3

Deploy the app again, so that it deletes the old pod, downloads the v0.2.3 pod, and runs it:

kubectl apply -d deploy/django.yaml

Check to make sure the pod comes up and check the log to make sure that Django collected the static files, and that the database migrations occurred to create all the new fields. You can use the show-pod-log script to check the log.

From the web site, you should see the new version number at the top of the movie list page, and a different look. Verify that you can add movies, edit movies, persist mode settings, and that there are searches (for add) and lookups (for edit) of movies in the IMDB.

Running Locally

Instead of going through long build/push/delete cycles to modify the app, it is nice to be able to run Django locally on your computer. Note: that I chose to use the production database, so I have live data to use. One could choose to spin up a Postgres server and have a dummy database, if desired. I’ll leave that as an exercise for the reader.

The Postgres database running in the kubernetes cluster does not have an externally visible IP and the viewmaster-postgres.viewmaster.svc.cluster.local domain name is not known from my laptop. To remedy this, we must expose an IP for the database pod. This can be done with:

kubectl expose service viewmaster-postgres -n viewmaster --port=5432 --target-port=5432 --name=viewmaster-postgres-dev --type=LoadBalancer

Do a “kubectl get svc -n viewmaster viewmaster-postgres-dev” and note the external IP for the service. Then, from the top of the tree, run:

python3 make-dev-setup-script.py EXTERNAL-IP-FOR-DBASE

This will create the file movie_library/dev-setup.bash, which can be sourced to setup an environment like what the Django app would see in a Kubernetes pod. Note: it does some special tweaks to the SECRET_KEY and POSTGRES_PASSWORD so that they can be used in bash export commands.

The Django server can now be run locally:

cd ~/workspace/kubernetes/viewmaster/movie_library
source dev-setup.bash
python3 manage.py runserver

You should be able to access the app from your browser at http://127.0.0.1:8000. You can also do “collectstatic”, “makemigrations”, “migrate”, “dbshell”, and other commands.

Using the IMDB importer

I created a command line app that will search the viewmaster database, collecting movies without an IMDB ID, and show a count of movies that need processing. For each movie, the IMDB database is searched with the title, and matching candidates are shown, allowing one to be selected for updating the viewmaster info. When done (or exiting) it will tell you how many movies have been processed.

To run the script, the same dev-setup.bash script must be sourced, so that environment variables are obtained, and then the python script can be run:

cd ~/workspace/kubernetes/viewmaster/movie_library
source dev-setup.bash
python3 viewmaster/imdb_import.py

Here is some sample execution:

Have 413 movies to process

Movie: 'Fallen ' (175)
Release: 1998 Rating: R Duration: 02:05:00


0) SKIP SELECTION FOR THIS MOVIE

Enter choice 0,x [0]:
Skipping movie

Movie: 'Fury' (200)
Release: 2014 Rating: R Duration: 02:15:00


0) SKIP SELECTION FOR THIS MOVIE
1) 2015 Mad Max: Fury Road (movie)
2) 2014 Fury (movie)
3) 2023 Shazam! Fury of the Gods (movie)
4) 2015 Kung Fury (movie)
5) 2007 Balls of Fury (movie)
6) 1972 Fist of Fury (movie)
7) 2014 Cuban Fury (movie)
8) 1989 Blind Fury (movie)
9) 1978 The Fury (movie)
10) 1936 Fury (movie)

Enter choice 0-10,x [0]: 2

TITLE: Fury
RELEASED: 2014 ✅
RATING: R ✅
DURATION: 02:14 ❌ (database had 02:15)
IMDB ID: tt2713180
A grizzled tank commander makes tough decisions as he and his crew fight their way across Germany in April, 1945.
ACTORS: Brad Pitt, Shia LaBeouf, Logan Lerman
DIRECTORS: David Ayer

Enter choice Save, Back, Ignore, eXit[S]:

Saved updates to movie

On the first movie, “Fallen”, the viewmaster database had “Fallen ” and with the extra space, it could not find the movie. The solution there, is to use the the web version, edit the movie, and correct the movie title so that a subsequent edit will allow the lookup.

On the second movie, there were numerous matches to “Fury”. I picked the second one (it usually is the first one), and then selected the default value in square brackets (save). You can see that IMDB had a slightly different duration.

v0.2.2: With this version, in addition to the candidates being listed, there is an additional selection, that allows you to manually enter the IMDB ID. If done, it will then show details of that selection, allowing you to save, ignore, got back, or exit. Here is an example:

Movie: 'MGM Sampler Disk' (365)
Release: 1993 Rating: NR Duration: 00:00:00


0) SKIP SELECTION FOR THIS MOVIE
1) Manually enter movie ID

Enter choice 0-1,x [0]: 1
Enter IMDB ID: tt0211493

TITLE: MGM/UA Home Video Laserdisc Sampler
RELEASED: 1992 ❌ (database had 1993)
RATING: NR ✅
2025-03-08T09:35:43 [WARNING ] Unable to parse time string 'N/A'
DURATION: 00:00 ✅
IMDB ID: tt0211493
N/A
ACTORS: N/A
DIRECTORS: N/A

 

Things To Be Aware Of

If you already have the original app installed, you’ll need to delete and re-add the K8s secrets, as there are more values being stored.

If you have existing movies in the database, there can be some challenges finding them in IMDB. I had a few titles misspelled, so they could not be matched. I would typically check the title and edit the movie to match what is shown in IMDB, and then edit the movie again so that the lookup would succeed.

With free access to OMDB, you are limited to 1,000 requests a day. Not a problem for this app, where we just need to go through the movies once, to obtain the info.

There were quite a few cases where the duration in IMDB database, was slightly different than what I had entered for the movie (from the back cover of the movie box).

There were some funky titles, like “Alien 3”, which has a superscript three character (I cut and pasted it from a test run doing an add movie operation, searching for “Alien”.

I had one case, of an old movie, that was not in IMDB.

I had another case of a title that I could not find a match for that year, but when visiting https://www.imdb.com, I could find the movie version of interest. For v0.2.2, I used the manual ID entry to resolve.

Lastly, I hit one case where a six disk TV series resolved to a single IMDB entry. To be able to integrate the IMDB info I had to take these steps for each of the “season” disks:

  1. Note the release year and duration.
  2. Rename the title to just have the show name, and not any season info.
  3. Edit again, so that the IMDB data can be found.
    1. Change the title back to include the season indication.
    2. Change the duration to what was there before (from the box cover).
    3. Set the release date for that season
  4. Save the movie.

In IMDB it had the release as 2010-2015, but the app can only extract a single date for a movie, so it would always show 2010. The duration was totally wrong in IMDB.

 

What’s Left To Do

There are a few more things that I’d like to do (maybe)…

  • Maybe save the movie cover (poster), instead of having to access the URL for image.
  • Decide if I want to enable remote access, and if so, maybe add OTP 2FA.
  • Show admin link, if user has admin privileges.
Category: Uncategorized | Comments Off on Viewmaster Updates