I wanted to extend my Starlink Wi-Fi network to a router I have in my workshop without the use of any cables, or by having to purchase the Starlink ethernet adapter.
The issue I ran into was that my TP-Link ER706W is primarily a wired VPN router, and does not support Wi-Fi bridging or repeater modes.
Instead, I was able to get a similar effect by:
Using Raspberry Pi 4 to connect to the Starlink Wi-Fi network and forward connections to its ethernet port while having NAT
Have the TP-Link ER706W router use the Raspberry Pi ethernet connection as the WAN gateway
The below configuration was applied to a Raspberry Pi 4B using Ubuntu 24.04.1 LTS.
Any Wi-Fi router should work here so I’ve kept the Wi-Fi router instructions here generalised.
Set up Port Forwarding
Ubuntu on the Raspberry Pi must be set up to allow port forwarding between network interfaces.
To configure, add the following to /etc/sysctl.conf
net.ipv4.ip_forward=1
Then apply the configuration:
sudo sysctl -p
Configure NAT at the Wi-Fi interface
In this setup, the Raspberry Pi connects to the Starlink Wi-Fi network and acts as a gateway for devices connected to the TP-Link router.
When devices behind the TP-Link router send traffic to the internet, NAT (Network Address Translation) on the Raspberry Pi changes their private IP addresses to the Raspberry Pi’s Wi-Fi IP address.
Add the required rules to iptables with:
# replace wlan0 with your wifi interface if it differs
sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
With this configuration, the Starlink Wi-Fi network does not need to handle or deal with the TP-Link router network addresses at all.
Configure port forwarding between the Raspberry Pi interfaces
To forward traffic between the Raspberry Pi Wi-Fi interface and ethernet interface, add the required rules to iptables with:
# replace eth0 and wlan0 with your interface names if the names differ
# outbound forwarding
sudo iptables -A FORWARD -i eth0 -o wlan0 -j ACCEPT
# inbound forwarding
sudo iptables -A FORWARD -i wlan0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
Make all iptables changes persistent:
sudo apt install iptables-persistent
Set a static IP address for the ethernet interface
The ethernet interface needs to be configured with an IP address so it’s reachable as a gateway for the TP-Link router.
The Raspberry Pi ethernet interface and TP-Link router will share their own subnet, separate to the subnets used for the TP-Link router Wi-Fi network.
Here for example, I used:
192.168.10.0/24 for the TP-Link router to Raspberry Pi ethernet network.
192.168.10.10 for the Raspberry Pi ethernet interface IP address
192.168.10.11 for the TP-Link IP address (configured under WAN settings)
192.168.0.0/24 for the TP-Link router Wi-Fi network subnet
Ubuntu uses netplan to configure networking. Here’s a typical configuration for this purpose:
Before applying the configuration, check for existing configuration. Ubuntu is frequently packages with /etc/netplan/50-cloud-init.yaml for cloud init, which is a tool used to configure cloud instances or virtual machines during their first boot.
If that cloud init config exists, read the instructions within the /etc/netplan/50-cloud-init.yaml file for steps on how to disable it.
Configuration can be applied with:
sudo netplan apply
Configure the TP-Link router to use the Rasberry Pi as a gateway
The TP-Link router can now use the Raspberry Pi as a gateway. Under the WAN settings for the WAN port that connects to the Raspberry Pi, I used the following configuration:
IP Address (of TP-Link router): 192.168.10.11
Subnet Mask: 255.255.255.0
Default Gateway (the Raspberry Pi): 192.168.10.10
Primary DNS: 8.8.8.8
Secondary DNS: 8.8.4.4
DNS is set here because the Raspberry Pi hasn’t been configured to handle DNS requests. Any devices that are connected to the TP-Link Wi-Fi (as their default gateway), will use the DNS specified here.
Devices connected to the TP-Link router wireless (or wired) network should now be able to access the internet.
This page describes an installation process of vanilla Kubernetes on Ubuntu virtual machines. This will install a 3 node cluster, with one control plane node and two worker nodes.
Kubernetes components
Firstly, let’s take a look at each Kubernetes component and they function.
Kubernetes nodes can be divided into categories of control plane nodes and worker-nodes.
Each node, regardless if it operates the control plane or is a worker node contains a container runtime, kubelet and kube-proxy for the core operations of running and connecting containers.
The control plane nodes contain functions that enable the overall configuration and operation of the cluster.
The Container Runtime
The container runtime runs the containers on each node. This implementation uses Containerd and Runc.
Runc directly creates and runs containers as per the Open Container Initiative (OCI) Specification.
Containerd manages the container lifecycle on the host system. This includes (but not limited to) pulling and managing images, coordinating the creation of containers (through Runc or a similar process) and low level networking.
In short, containerd is the interface that is used for managing the underlying container process which is executed by runc.
Kubelet
The Kubelet is an agent that runs on each node within the cluster. It ensures that Kubernetes managed and specified containers are running within pods.
The Kubelet interacts with the container runtime to spawn and manage containers through a Container Runtime Interface (CRI) plugin. The CRI enables the CRI plugin enables the kubelet to interact with a wide variety of container runtimes. Though we are using containerd and runc specifically in this implementation.
Kube-proxy
Kube-proxy provides networking functionality for pods within each node, allowing for communication between components both inside and outside of the Kubernetes cluster.
Etcd
Etcd is a highly-available key value store for storing all Kubernetes cluster data, including but not limited to: Cluster state information, pod and deployment information, node information and secrets and ConfigMaps.
Kube-controller-manager
The kube-controller-manager is a core component which controls the various aspects of a Kubernetes cluster and ensures the Kubernetes cluster maintains its configured state.
While Etcd stores the desired state of the cluster, the kube-controller-manager ensures the cluster runs at that defined state.
Various controllers are used for managing the state of particular configuration items, such as controllers for Job objects that result in Pod creation and replication controllers that ensure the specified number of pods are running at all times.
Kube-scheduler
The kube-scheduler assigns pods against nodes. It does this by watching for pods that have no nodes assigned and then finds the best node to allocate against the unassigned pod. This is conducted in a 2-step operation:
A filtering operation which identifies which nodes the pod can be run on
A scoring operation which selects the best available node for the pod to run on.
Kube-apiserver
Finally, the Kubernetes API server, a central component that glues together all other Kubernetes components. The kube-api server provides an api that allows users, internal components and external components to communicate with each other.
Prerequisites
The prerequisites for following along with this page are:
3 virtual (or physical) machines, each with:
Ubuntu 24.x
2GB+ memory per machine
2 CPUs or more for the control plane VM (x1)
1 CPU or more for the worker node VMs (x2)
Hostnames set on each VM
Full network connectivity between all VMs
IPv4 packet forwarding enabled on all nodes, see below.
Network filtering (iptables) for bridged traffic, see below.
A number of ports open on each machine type (control or node), see below.
Ensure swap memory is turned off
Hostnames set on each VM
Setting the hostname is straightforward. Run the following:
# modify the hostnames within /etc/hostname. this implementation uses node-00, node-01 and node-02.
sudo vim /etc/hostname
IPv4 Packet Forwarding
IPv4 packet forwarding is required on all nodes which allows the proper routing and forwarding of packets between Kubernetes cluster components.
To enable, run the following on every node:
# configuration to enable config persistance across reboots
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF
# apply the packet forwarding config without a reboot
sudo sysctl --system
# confirm that the configuration has been applied
sysctl net.ipv4.ip_forward
Network Filtering for Bridged Traffic
This implementation uses Flannel as the overlay network. The overlay network creates a virtual network that sits over the top of the existing node network.
Containers on a node will communicate with each other on this overlay network through layer 2 bridged traffic, and communicate with other nodes through layer 3 IP address based traffic.
To enable the layer 2 bridged traffic to be subject to iptables, we need to enable networking for bridged traffic on each node that uses the Flannel overlay network (all of them).
To enable this network filtering for bridged traffic, run the following commands on each node:
# load the br_netfilter kernel module
sudo modprobe br_netfilter
lsmod | grep br_netfilter
# ensure the kernel module loads on boot
echo "br_netfilter" | sudo tee -a /etc/modules
# tell the kernal to filter bridged ipv4 and ipv6 traffic
sudo sysctl -w net.bridge.bridge-nf-call-iptables=1
sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=1
# make the change persistent
echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-ip6tables=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Port Requirements
The tcp ports that need to be open are:
Control plane node:
6443 – kube-apiserver
2379:2380 – etcd and etcd in a highly available cluster
10250 – kubelet api
10259 – kube-scheduler
10257 – kube-controller-manager
Worker node:
10250 – kubelet api
10256 – kube-proxy metrics
30000:32767 – NodePort services
In this implementation UFW is used to control the Ubuntu host firewall. Also ensure the ports for ssh are open so you don’t lock yourself out of the VM:
# open ports on control nodes
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow 6443/tcp
sudo ufw allow 2379:2380/tcp
sudo ufw allow 10250/tcp
sudo ufw allow 10259/tcp
sudo ufw allow 10257/tcp
# open ports on worker nodes
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow 10250/tcp
sudo ufw allow 10256/tcp
sudo ufw allow 30000:32767/tcp
# to check a particular port is working after a service has been started, for example the api server:
nc 127.0.0.1 6443 -v
Ensure Swap Memory is Turned Off
Kubernetes relies on the kubelet to monitor and manage node resources such as CPU and memory. Having dynamic (swap) memory turned on adds a layer of complexity that interferes with Kubernetes resource management.
To check if swapon is enabled an disable it if it is enabled:
# check if it's enabled, if swap is 0B in total it is not enabled
free -h
# or ensure there is no output from the following
sudo swapon --show
# if it is enabled, modify /etc/fstab and remove any swap related lines
sudo vim /etc/fstab
# look for and remove lines such as
# UUID=xxxx-xxxx-xxxx-xxxx none swap sw 0 0# reboot the machine and check for swap memory again
sudo reboot
free -h
Prerequisites Summary
These prerequisites were focused on configuration the underlying host OS and networking for Kubernetes. Once complete proceed to step 1.
Step 1 – Install Kubernetes tools and kubectl
Here, the kubelet, kubeadm and kubectl will be installed on each node.
Kubelet: The process that interacts with the container runtime for the management and operation of pods and containers
Kubectl: A command line interface for communicating with the kube-apiserver. This is not required on worker nodes but can be useful for debugging and learning.
Kubeadm: A tool used for bootstrapping Kubernetes clusters to a series of best practices. It is the tool used in this implementation for creating the control plane node and joining two worker nodes.
To install, run the below on all three nodes:
# Add the kubernetes apt sources
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
# If the directory `/etc/apt/keyrings` does not exist, it should be created before the curl command, read the note below.# sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
# Install the components
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# Enable and run the kubelet as a service
# enable kublet service before running kubeadm
sudo systemctl enable --now kubelet
Step 2 – Install the container runtime
Containerd and runc are used in this implementation for managing and running containers on the host OS.
To install containerd, run the below on each node. Note that we are setting the SystemdCgroup setting to true to enable containerd to manage containers through systemd.
# download containerd
wget https://github.com/containerd/containerd/releases/download/v1.7.24/containerd-1.7.24-linux-amd64.tar.gz
wget https://github.com/containerd/containerd/releases/download/v1.7.24/containerd-1.7.24-linux-amd64.tar.gz.sha256sum
# check the sha256sum
sha256sum -c containerd-1.7.24-linux-amd64.tar.gz.sha256sum
# install containered
sudo tar Cxzvf /usr/local containerd-1.7.24-linux-amd64.tar.gz
# generate the standard containerd configuration
sudo mkdir /etc/containerd
sudo containerd config default > /etc/containerd/config.toml
# set SystemdCgroup to true in config.tomlvim /etc/containerd/config.toml
# under: [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
# set SystemdCgroup: true
# enable containerd to run as a systemd service
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
sudo mkdir /usr/local/lib/systemd/system -p
sudo cp containerd.service /usr/local/lib/systemd/system/containerd.service
systemctl daemon-reload
systemctl enable --now containerd
Now run the following to install runc.
# install runc
wget https://github.com/opencontainers/runc/releases/download/v1.2.2/runc.amd64
install -m 755 runc.amd64 /usr/local/sbin/runc
# test container can be run with runc
sudo ctr images pull docker.io/library/alpine:latest
sudo ctr images ls
sudo ctr run --runtime io.containerd.runc.v2 --tty --rm docker.io/library/alpine:latest test-container /bin/sh
Step 3 – Bootstrap the cluster
Generate bootstrapping configuration
Before running kubeadm to bootstrap the cluster, we want to generate and modify the standard configuration to:
Set the correct control plane node hostname, node-00.
Set the ip address of the api endpoint. This is the control plane node ip address, and this is 10.0.0.10 in this example.
Specify the pod network subnet (the flannel overlay network). I’m choosing a 192.168.0.0./16 subnet.
Set the cluster to use systemd managed containers to match the containerd configuration.
If created successfully, you will see the following message:
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1b4da30281a13c5d1a93bf6fce9531bace93e9ccee8201aad375cdf9e41cd13a
Run the commands above to allow you to use kubectl as either a normal user or root user. Don’t run the command to join a worker node just yet.
Common Error
When generating the containerd config in the previous step, there might be a mismatch between versions of the pause container from the registry.k8s.io registry.
The pause container is the shell container which creates a network namespace for each pod.
When running kubeadm, during the prefilght chekcs it will point out any mismatches in versions. Change the version to the expected version within the containerd config and then restart containerd.
with sudo containerd config default > /etc/containerd/config.toml
# change the pause container to the expected version
sudo vim /etc/containerd/config.toml
# sandbox_image = "registry.k8s.io/pause:3.10"
Step 4 – Install the Flannel overlay network
Again, the implements an overlay network for pod-to-pod communication.
Generate the Flannel definition with the following commands, and ensure the Network is set to the pod network you set in the kubeadm configuration.
Now apply this configuration to the cluster to initiate the Flannel overlay network
# apply to cluster
kubectl apply -f kube-flannel.yml
You can check that the flannel pods started successfully by running:
kubectl get pods -n kube-flannel
# will show a ready and running kube-flannel-xx-xxxxx container if successful
Now you have a running control plane node with a pod overlay network. We are now ready to add worker nodes to the cluster.
Step 5 – Add nodes to the cluster
Adding a worker node is quick and straightforward assuming you have completed all the prerequisite steps for each worker node VM.
For each node, ensure that:
All prerequisites have been completed from the Prerequisites section
Ensure Step 1 has been completed for each node, installing kubeadm, the kubelet and optionally kubectl.
Once confirmed, you can run the node joining instructions from when you bootstrapped the cluster with kubeadm.
# install a node
kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1b4da30281a13c5d1a93bf6fce9531bace93e9ccee8201aad375cdf9e41cd13a
# if this fails (24hr expiry) get a new token
kubeadm token list
kubeadm token create
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | \
sed 's/^.* //'
kubeadm join 10.0.30.125:6443 --token the-new-token \
--discovery-token-ca-cert-hash sha256:1b4da30289a13c5d1c93bf6fce9531bace94e9ccee8201aad375cdf9e41cd13a
# repeat for all nodes
Note that if you don’t join the node within 24 hours of creating the control plane node, the initial token for joining the clusterwill have expired.
You won’t be able to join a node to the cluster until you generate and use another token. To generate another token, run the following on the control plane node:
# kubeadm token list (will be empty if the token expired)
kubeadm token create
# save the output
# if you need the discovery token certificate again, run:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | \
sed 's/^.* //'
# rerun the following with the new token and discovery token certificate
kubeadm join 10.0.0.10:6443 --token [the-new-token] \
--discovery-token-ca-cert-hash [the-discovery cert]
Repeat this for each node you want to join. You should be able to see each new node with:
kubectl get nodes
You should also be able to see a new flannel pod for each node with:
kubectl get pods -n kube-flannel
Summary
This concludes this guide to bootstrapping a Kubernetes cluster on Ubuntu 24.x VMs.
The steps completed here were:
A number of prerequisites, including a number of OS host networking configurations
Installing the container runtime
Installing the kubeadm, kubelet and kubectl Kubernetes tools and processes