Use when deploying a single-node rk8s cluster (Xline + RKS + RKL) on one machine, testing SlayerFS volumes with containers, or running nginx/busybox pods via rk8s on a single host. Not for multi-node cluster deployments.
This skill deploys a complete rk8s cluster on a single machine with 4 pods (1 nginx + 3 busybox), each with a SlayerFS persistent volume. Acceptance criteria: busybox pods ping each other and volume read/write works.
┌─ Single Machine ──────────────────────────────────┐
│ Xline (3 Docker containers) ← state storage │
│ RKS (control plane) ← scheduler, CSI │
│ RKL daemon (worker node) ← container mgmt │
│ libfuse overlay (per-container) ← rootfs mount │
│ SlayerFS (FUSE, in-process) ← volume backend │
└────────────────────────────────────────────────────┘
Pods deployed:
nginx (nginx:latest) + slayerfs volume at /data
busybox-1 (busybox:latest) + slayerfs volume at /data
busybox-2 (busybox:latest) + slayerfs volume at /data
busybox-3 (busybox:latest) + slayerfs volume at /data
Before doing anything, ask the user:
user@host and verify connectivitysudo~/rk8s/project or ~/yrj/rk8s/project)Based on the answers, set these variables for all subsequent steps:
SSH_CMD: empty for local, ssh <host> for remotePROJECT_DIR: path to rk8s/projectBIN: $PROJECT_DIR/target/releaseFor remote hosts, prefix all commands with $SSH_CMD.
Check these in parallel — skip any step where the prerequisite is already met:
# Check if binaries are compiled (CRITICAL: skip compilation if they exist, it takes 15-20 min)
ls $BIN/rkl $BIN/rks 2>/dev/null && echo "Binaries exist" || echo "NEED COMPILATION"
# Check CNI config
sudo ls /etc/cni/net.d/test.conflist 2>/dev/null && echo "CNI configured" || echo "NEED CNI SETUP"
# Check rkforge config
sudo cat ~/.config/rk8s/rkforge.toml 2>/dev/null | grep insecure || echo "NEED RKFORGE CONFIG"
# Check if services are already running
ps aux | grep -E 'rks.*start|rkl.*daemon' | grep -v grep
docker ps --format '{{.Names}}' | grep -E 'node[123]'
Only proceed with steps that are actually needed. Never recompile if binaries exist.
cd $PROJECT_DIR && cargo build --release
Timeout: 20 minutes. This produces rkl, rks in target/release/.
CNI plugins are embedded in the rkl binary — no external binaries needed. Only config files:
sudo mkdir -p /etc/cni/net.d
sudo cp $PROJECT_DIR/test/test.conflist $PROJECT_DIR/test/subnet.env /etc/cni/net.d/
The default registry is 47.79.87.161:8968 (the project's distribution server). It uses HTTP, but rkforge defaults to HTTPS. Fix with:
mkdir -p ~/.config/rk8s
cat > ~/.config/rk8s/rkforge.toml << 'EOF'
entries = []
[registry]
insecure-registries = ["47.79.87.161:8968"]
[image]
EOF
Important: When running rkl with sudo, rkforge reads the config of $SUDO_USER (the original non-root user), not root's config.
Available images on the default registry:
nginx:latest (~60MB)busybox:latest (~2MB)pause:3.9 (~300KB, used internally for pod sandbox)Note: busybox:1.36 is NOT available on the default registry. Always use busybox:latest.
CRITICAL: If restarting from a failed or previous deployment, you MUST clean all state before re-deploying. Leftover containers in /run/youki/ cause "container already exists" errors that block pod creation permanently.
# Stop services
sudo killall rkl rks 2>/dev/null
sleep 2
# Unmount all rkl-related FUSE and overlay mounts (MUST do before rm)
mount | grep -E "fuse.*rkl|slayerfs|overlay.*rkl" | awk '{print $3}' | while read mnt; do
sudo umount -l "$mnt" 2>/dev/null
done
# Clean container runtime state
sudo find /run/youki -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null
# Clean rkl data directories
sudo find /var/lib/rkl/bundle -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null
sudo find /var/lib/rkl/pods -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null
sudo find /var/lib/rkl/volumes -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null
# Clean CNI bridge
sudo ip link set cni0 down 2>/dev/null
sudo brctl delbr cni0 2>/dev/null
Why find -exec rm instead of rm -rf /path/*: Over SSH, glob expansion with * can silently fail. find -exec is reliable.
Why unmount first: FUSE mounts prevent rm -rf from deleting directories. Always unmount before cleaning.
Skip if Xline containers are already running (docker ps | grep node1).
If restarting from a failed deployment, stop and remove existing Xline containers first to clear stale pod data from etcd:
docker stop node1 node2 node3 2>/dev/null
docker rm node1 node2 node3 2>/dev/null
Then start fresh:
docker network create --subnet=172.20.0.0/24 xline_net 2>/dev/null || true
MEMBERS="node1=172.20.0.3:2380,node2=172.20.0.4:2380,node3=172.20.0.5:2380"
for i in 1 2 3; do
IP="172.20.0.$((i+2))"
docker run -d --name "node$i" --net xline_net --ip "$IP" --cap-add=NET_ADMIN \
ghcr.io/xline-kv/xline:latest xline \
--name "node$i" --members "$MEMBERS" \
--data-dir /usr/local/xline/data-dir --storage-engine rocksdb \
--initial-cluster-state new \
--client-listen-urls "http://$IP:2379" --peer-listen-urls "http://$IP:2380" \
--client-advertise-urls "http://$IP:2379" --peer-advertise-urls "http://$IP:2380"
done
sleep 5
Verify: docker logs node1 2>&1 | tail -5 should show leader election.
Note: Alternatively, you can build Xline from the repo at $PROJECT_DIR/Xline/Xline/ with cargo build -p xline --release and run it natively. The Docker approach is simpler for single-machine testing.
Skip if already running (ps aux | grep 'rks.*start').
cat > /tmp/rks-config.yaml << 'EOF'