Table of contents
- RKE2 Control Plane Setup
- Example deployment
- 1. Update the control-plane host
- 2. Verify the networking backend
- 3. Become root and install RKE2
- 4. Create the RKE2 configuration
- 5. Start RKE2
- 6. Verify the control plane locally
- 7. Protect the worker join token
- 8. Configure kubectl in WSL
- Create a temporary copy on the control-plane host
- Copy and update it in WSL
- Merge with the existing kubeconfig
- Optional troubleshooting from this deployment
- Deployment outcome
- Acronyms and abbreviations
RKE2 Control Plane Setup
This runbook documents a completed RKE2 control-plane installation and the subsequent WSL kubectl configuration. I chose a NucBox G5 for my control-plane node, but the procedure is intended to be reusable with another Ubuntu host.
Prerequisite: This walkthrough assumes a basic knowledge of Kubernetes. If any terminology is unfamiliar, see Acronyms and abbreviations at the end.
Replace these placeholders with values for your environment:
| Placeholder | Meaning |
|---|---|
<user-name> |
Linux account on the control-plane host |
<control-plane-ip> |
Control-plane host's LAN IP address |
<node-name> |
Kubernetes node name |
<control-plane-hostname> |
Hostname included in the Kubernetes API certificate |
<physical-lan-interface> |
WSL interface that reaches the physical LAN |
<conflicting-interface> |
WSL or Hyper-V interface incorrectly claiming the same LAN subnet |
<physical-lan-cidr> |
Physical LAN subnet in CIDR notation |
NOTE: Do not paste angle-bracket placeholders into commands or configuration unchanged.
Example deployment
| Item | Choice |
|---|---|
| Host | NucBox G5 |
| OS | Ubuntu 24.04 LTS |
| CPU | Intel N97 |
| RAM | Approximately 12 GB |
| Role | RKE2 server, etcd, and Kubernetes control plane |
| Installed Kubernetes version | v1.35.7+rke2r1 |
The node was given a control-plane taint so normal application workloads cannot schedule there. Services that share the same physical host but run outside Kubernetes remain outside the Kubernetes failure boundary.
The RKE2 version was not pinned. The standard installer selected v1.35.7+rke2r1 at the time of this deployment; a later rebuild may install a newer version.
1. Update the control-plane host
Run on the control-plane host:
sudo apt update
sudo apt upgrade -y
2. Verify the networking backend
Check whether the host uses systemd-networkd:
systemctl is-active systemd-networkd
If the command returns active, continue to the next step. If it does not, the host may use NetworkManager and require a Canal networking workaround that is beyond the scope of this walkthrough.
3. Become root and install RKE2
Run on the control-plane host:
sudo -s
The prompt should now indicate a root shell. Run the standard installation command from the official RKE2 documentation directly as root:
curl -sfL https://get.rke2.io | sh -
NOTE: Check the RKE2 Official Documentation if this command doesn't work. It may have changed.
This installation did not set INSTALL_RKE2_VERSION. Remain in the root shell for the configuration and startup steps.
4. Create the RKE2 configuration
Create the configuration directory:
mkdir -p /etc/rancher/rke2
Create /etc/rancher/rke2/config.yaml, replacing each placeholder with the selected value:
write-kubeconfig-mode: "0644"
node-name: "<node-name>"
tls-san:
- "<control-plane-ip>"
- "<control-plane-hostname>"
node-taint:
- "node-role.kubernetes.io/control-plane=true:NoSchedule"
cni: canal
ingress-controller: none
node-namecontrols the name displayed bykubectl get nodes.tls-sanmakes the API certificate valid for the chosen IP address and hostname.- The taint keeps ordinary workloads off the control plane.
- Canal is the RKE2 CNI.
- The bundled ingress controller is disabled because UDS Core will eventually provide Istio tenant and admin gateways.
5. Start RKE2
Still in the root shell:
systemctl enable --now rke2-server
Watch startup if needed:
journalctl -u rke2-server -f
Exit the root shell when finished:
exit
6. Verify the control plane locally
Run on the control-plane host:
sudo /var/lib/rancher/rke2/bin/kubectl get nodes
sudo /var/lib/rancher/rke2/bin/kubectl get pods -A
Expected node state:
NAME STATUS ROLES VERSION
<node-name> Ready control-plane,etcd <installed-version>
In this deployment, the installed version was v1.35.7+rke2r1.
After the initial installation, several RKE2 Helm installer pods remained Pending, including metrics-server, runtimeclasses, and snapshot-controller jobs. This was expected because the control-plane node was tainted NoSchedule and the cluster did not yet have any worker nodes. Those workloads will have somewhere to schedule after the first worker joins.
7. Protect the worker join token
The RKE2 worker join token is stored on the control-plane host at:
sudo cat /var/lib/rancher/rke2/server/node-token
Warning: This token grants cluster-join access. Never commit it to a public repository or wiki.
8. Configure kubectl in WSL
The Windows workstation in this deployment already had a kubeconfig from an older Hyper-V Kubernetes cluster. The RKE2 configuration was therefore added without unnecessarily destroying the existing configuration.
Create a temporary copy on the control-plane host
Run on the control-plane host after setting your account name:
USER_NAME="<user-name>"
sudo cp /etc/rancher/rke2/rke2.yaml "/home/${USER_NAME}/rke2.yaml"
sudo chown "${USER_NAME}:${USER_NAME}" "/home/${USER_NAME}/rke2.yaml"
Copy and update it in WSL
Run in WSL after setting the control-plane account and IP:
USER_NAME="<user-name>"
CONTROL_PLANE_IP="<control-plane-ip>"
scp "${USER_NAME}@${CONTROL_PLANE_IP}:/home/${USER_NAME}/rke2.yaml" ~/.kube/rke2.yaml
sed -i "s/127\.0\.0\.1/${CONTROL_PLANE_IP}/" ~/.kube/rke2.yaml
The generated RKE2 kubeconfig defaults to https://127.0.0.1:6443. A remote management client must instead use https://<control-plane-ip>:6443.
Merge with the existing kubeconfig
First identify the current configuration location:
echo $KUBECONFIG
For an existing ~/.kube/config, merge and flatten both configurations:
KUBECONFIG=~/.kube/config:~/.kube/rke2.yaml \
kubectl config view --flatten > ~/.kube/config.merged
After verifying the merged file:
mv ~/.kube/config.merged ~/.kube/config
If the existing kubeconfig uses another filename, such as ~/.kube/config.yaml, use that actual path in both the merge command and final mv instead.
List the available contexts:
kubectl config get-contexts
If necessary, rename the new RKE2 context to homelab:
kubectl config rename-context <current-rke2-context> homelab
Select and verify it:
kubectl config use-context homelab
kubectl get nodes
kubectl get pods -A
Expected remote node state:
NAME STATUS ROLES AGE VERSION
<node-name> Ready control-plane,etcd ... <installed-version>
Optional troubleshooting from this deployment
The SSH and routing problems below were specific issues encountered while configuring this homelab. They are not required RKE2 installation steps, and many environments will not encounter them. Use these checks only when the symptoms match.
SSH public-key authentication
ssh-copy-id initially failed because the control-plane host allowed only public-key authentication:
Permission denied (publickey).
The WSL public key was copied manually. Do not copy a private key.
Run in WSL to display the public key:
cat ~/.ssh/id_ed25519.pub
Run as the target account on the control-plane host:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Paste only the WSL public key into authorized_keys, then verify from WSL:
ssh <user-name>@<control-plane-ip>
Duplicate WSL route
WSL initially had two interfaces advertising the same physical LAN subnet:
<physical-lan-cidr> dev <conflicting-interface>
<physical-lan-cidr> dev <physical-lan-interface>
A Hyper-V-related interface incorrectly occupied the physical LAN subnet. Traffic to the control-plane host sometimes used the wrong interface and returned:
Destination Host Unreachable
Verify in WSL:
ip route get <control-plane-ip>
ping -I <physical-lan-interface> -c 3 <control-plane-ip>
ping -I <conflicting-interface> -c 3 <control-plane-ip>
The temporary fix was to remove the duplicate route from the conflicting interface:
sudo ip route del <physical-lan-cidr> dev <conflicting-interface>
The permanent solution is to ensure Hyper-V and internal virtual networks do not use the same subnet as the physical LAN. Do not change the physical LAN subnet solely to work around a conflicting virtual network.
Deployment outcome
- RKE2 control plane operational.
- Control-plane node
Ready. - Installed version in this deployment:
v1.35.7+rke2r1. - Control plane tainted against normal workloads.
- RKE2 bundled ingress disabled.
- Canal networking operational.
- WSL
kubectlaccess operational. - Next task: join the first RKE2 worker.
- Planned worker roles include general compute, game-server capacity, and GPU workloads.
Acronyms and abbreviations
| Term | Meaning |
|---|---|
| API | Application Programming Interface |
| CIDR | Classless Inter-Domain Routing |
| CNI | Container Network Interface |
| CPU | Central Processing Unit |
| GB | Gigabyte |
| GPU | Graphics Processing Unit |
| HTTPS | Hypertext Transfer Protocol Secure |
| IP | Internet Protocol |
| LAN | Local Area Network |
| LTS | Long-Term Support |
| OS | Operating System |
| RAM | Random Access Memory |
| RKE2 | Rancher Kubernetes Engine 2 |
| SAN | Subject Alternative Name |
| SCP | Secure Copy Protocol |
| SSH | Secure Shell |
| TLS | Transport Layer Security |
| UDS | Unified Defense Stack |
| WSL | Windows Subsystem for Linux |
| YAML | YAML Ain't Markup Language |
Homelab Wiki
- Home
- Homelab Operations Status
- Application Delivery Automation
- RKE2 Control Plane Setup
- RKE2 Worker Setup
- MetalLB Layer 2 Load Balancing
- TrueNAS CSI Storage Integration
- UDS Core Prerequisites and Readiness
- UDS Application Delivery and Repository Architecture
- Final Architecture
- Node Roles
- Storage Strategy
- Migration Roadmap
- Mat Homelab Application Migration Plan
- Security and Secrets
- Homelab Roadmap
- Platform issues