diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-07 16:05:39 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-07 16:05:39 +0000 |
| commit | d138800d7925a007fc61366bb20a3c90412a45c6 (patch) | |
| tree | d3efabf18f940d8dfade096c40eab97dcdbd99f4 /recipes-containers/k3s | |
| parent | d1a4f655e390c76402c416714607bc3b3b85ac2f (diff) | |
| download | meta-virtualization-d138800d7925a007fc61366bb20a3c90412a45c6.tar.gz | |
k3s: add multi-node cluster support with role-based boot
Add infrastructure for booting the same container-image-host image
as either a k3s server or agent, controlled via kernel cmdline
parameters (k3s.role=server|agent).
k3s-role-setup.service / k3s-role-setup.sh:
- Reads k3s.role, k3s.server, k3s.token, k3s.node-name, k3s.node-ip,
k3s.iface from kernel cmdline
- Configures cluster network interface IP via networkd drop-in
- For agent role: masks k3s.service, writes agent environment file,
starts k3s-agent.service
- For server role: masks k3s-agent.service (default)
10-k3s-cluster.network:
- Claims the cluster interface (eth1) via virt_networking bbclass
- Disables DHCP to prevent systemd-networkd from interfering
- Static IP added at boot by role-setup via drop-in
k3s-get-token.sh:
- Helper script to display the server join token
- Waits for token file if k3s is still starting
k3s-agent.service:
- Add EnvironmentFile for /etc/default/k3s-agent (K3S_URL, K3S_TOKEN)
- Add After=k3s-role-setup.service and network-online.target
k3s.service:
- Add After=k3s-role-setup.service
packagegroup-kubernetes.bb:
- k3s-host packagegroup now includes k3s-agent (both roles available)
- Both k3s-host and k3s-node include k3s-net-conf
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-containers/k3s')
| -rw-r--r-- | recipes-containers/k3s/k3s/10-k3s-cluster.network | 20 | ||||
| -rw-r--r-- | recipes-containers/k3s/k3s/k3s-agent.service | 6 | ||||
| -rw-r--r-- | recipes-containers/k3s/k3s/k3s-get-token.sh | 33 | ||||
| -rw-r--r-- | recipes-containers/k3s/k3s/k3s-role-setup.service | 16 | ||||
| -rw-r--r-- | recipes-containers/k3s/k3s/k3s-role-setup.sh | 113 | ||||
| -rw-r--r-- | recipes-containers/k3s/k3s/k3s.service | 2 | ||||
| -rw-r--r-- | recipes-containers/k3s/k3s_git.bb | 16 |
7 files changed, 203 insertions, 3 deletions
diff --git a/recipes-containers/k3s/k3s/10-k3s-cluster.network b/recipes-containers/k3s/k3s/10-k3s-cluster.network new file mode 100644 index 00000000..c821a8fe --- /dev/null +++ b/recipes-containers/k3s/k3s/10-k3s-cluster.network | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | # K3s cluster network interface configuration | ||
| 2 | # | ||
| 3 | # Claims the secondary interface (eth1) for k3s cluster networking, | ||
| 4 | # preventing systemd-networkd's default catch-all from configuring | ||
| 5 | # it with DHCP. The static IP address is configured at boot by | ||
| 6 | # k3s-role-setup.service via a runtime drop-in based on the | ||
| 7 | # k3s.node-ip= kernel cmdline parameter. | ||
| 8 | # | ||
| 9 | # If no k3s.node-ip= is set, the interface stays up with no address | ||
| 10 | # (link-local only), which is harmless. | ||
| 11 | |||
| 12 | [Match] | ||
| 13 | Name=eth1 | ||
| 14 | |||
| 15 | [Network] | ||
| 16 | DHCP=no | ||
| 17 | LinkLocalAddressing=ipv6 | ||
| 18 | |||
| 19 | [Link] | ||
| 20 | RequiredForOnline=no | ||
diff --git a/recipes-containers/k3s/k3s/k3s-agent.service b/recipes-containers/k3s/k3s/k3s-agent.service index 40d0564b..0304d640 100644 --- a/recipes-containers/k3s/k3s/k3s-agent.service +++ b/recipes-containers/k3s/k3s/k3s-agent.service | |||
| @@ -3,13 +3,17 @@ | |||
| 3 | Description=Lightweight Kubernetes Agent | 3 | Description=Lightweight Kubernetes Agent |
| 4 | Documentation=https://k3s.io | 4 | Documentation=https://k3s.io |
| 5 | Requires=containerd.service | 5 | Requires=containerd.service |
| 6 | After=containerd.service | 6 | After=containerd.service k3s-role-setup.service |
| 7 | After=network-online.target | ||
| 8 | Wants=network-online.target | ||
| 7 | 9 | ||
| 8 | [Install] | 10 | [Install] |
| 9 | WantedBy=multi-user.target | 11 | WantedBy=multi-user.target |
| 10 | 12 | ||
| 11 | [Service] | 13 | [Service] |
| 12 | Type=notify | 14 | Type=notify |
| 15 | EnvironmentFile=-/etc/default/%N | ||
| 16 | EnvironmentFile=-/etc/sysconfig/%N | ||
| 13 | # Ensure CNI plugin binaries are discoverable | 17 | # Ensure CNI plugin binaries are discoverable |
| 14 | Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/cni/bin:/usr/libexec/cni | 18 | Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/cni/bin:/usr/libexec/cni |
| 15 | KillMode=control-group | 19 | KillMode=control-group |
diff --git a/recipes-containers/k3s/k3s/k3s-get-token.sh b/recipes-containers/k3s/k3s/k3s-get-token.sh new file mode 100644 index 00000000..359ea2ff --- /dev/null +++ b/recipes-containers/k3s/k3s/k3s-get-token.sh | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # k3s-get-token — Display the k3s server join token | ||
| 6 | # | ||
| 7 | # Waits for the token file to be created (k3s server generates it | ||
| 8 | # on first start) and prints it. Useful for setting up agent nodes. | ||
| 9 | |||
| 10 | TOKEN_FILE="/var/lib/rancher/k3s/server/node-token" | ||
| 11 | TIMEOUT=60 | ||
| 12 | |||
| 13 | if [ ! -f "$TOKEN_FILE" ]; then | ||
| 14 | echo "Waiting for k3s server to generate token..." | ||
| 15 | i=0 | ||
| 16 | while [ ! -f "$TOKEN_FILE" ] && [ $i -lt $TIMEOUT ]; do | ||
| 17 | sleep 2 | ||
| 18 | i=$((i + 2)) | ||
| 19 | done | ||
| 20 | fi | ||
| 21 | |||
| 22 | if [ -f "$TOKEN_FILE" ]; then | ||
| 23 | echo "" | ||
| 24 | echo "=== K3s Join Token ===" | ||
| 25 | cat "$TOKEN_FILE" | ||
| 26 | echo "" | ||
| 27 | echo "To join an agent node:" | ||
| 28 | echo " run-k3s-multinode.sh agent --token \$(k3s-get-token)" | ||
| 29 | echo "" | ||
| 30 | else | ||
| 31 | echo "Token not found. Is k3s server running?" | ||
| 32 | echo " systemctl status k3s" | ||
| 33 | fi | ||
diff --git a/recipes-containers/k3s/k3s/k3s-role-setup.service b/recipes-containers/k3s/k3s/k3s-role-setup.service new file mode 100644 index 00000000..26d51ba3 --- /dev/null +++ b/recipes-containers/k3s/k3s/k3s-role-setup.service | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | [Unit] | ||
| 2 | Description=K3s Role Setup from Kernel Cmdline | ||
| 3 | Documentation=https://k3s.io | ||
| 4 | After=local-fs.target | ||
| 5 | Before=systemd-networkd.service network-online.target | ||
| 6 | |||
| 7 | # Only run if k3s.role= is on the kernel cmdline | ||
| 8 | ConditionKernelCommandLine=k3s.role | ||
| 9 | |||
| 10 | [Service] | ||
| 11 | Type=oneshot | ||
| 12 | RemainAfterExit=yes | ||
| 13 | ExecStart=/usr/local/bin/k3s-role-setup.sh | ||
| 14 | |||
| 15 | [Install] | ||
| 16 | WantedBy=multi-user.target | ||
diff --git a/recipes-containers/k3s/k3s/k3s-role-setup.sh b/recipes-containers/k3s/k3s/k3s-role-setup.sh new file mode 100644 index 00000000..fa4ffb05 --- /dev/null +++ b/recipes-containers/k3s/k3s/k3s-role-setup.sh | |||
| @@ -0,0 +1,113 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # k3s-role-setup.sh — Configure k3s role from kernel cmdline | ||
| 6 | # | ||
| 7 | # Reads k3s.role= from /proc/cmdline and configures the system | ||
| 8 | # to run as either a k3s server or agent. | ||
| 9 | # | ||
| 10 | # Kernel cmdline parameters: | ||
| 11 | # k3s.role=server — run k3s server (default if not specified) | ||
| 12 | # k3s.role=agent — disable k3s server, enable k3s agent | ||
| 13 | # k3s.server=IP — server address for agent mode | ||
| 14 | # k3s.token=TOKEN — join token for agent mode | ||
| 15 | # k3s.node-name=N — override node name (useful when multiple VMs | ||
| 16 | # boot from the same image) | ||
| 17 | # k3s.node-ip=IP — set node IP address | ||
| 18 | # k3s.iface=IFACE — flannel interface (default: eth1) | ||
| 19 | |||
| 20 | CMDLINE=$(cat /proc/cmdline) | ||
| 21 | |||
| 22 | get_param() { | ||
| 23 | echo "$CMDLINE" | tr ' ' '\n' | grep "^$1=" | cut -d= -f2- | ||
| 24 | } | ||
| 25 | |||
| 26 | ROLE=$(get_param k3s.role) | ||
| 27 | SERVER=$(get_param k3s.server) | ||
| 28 | TOKEN=$(get_param k3s.token) | ||
| 29 | NODE_NAME=$(get_param k3s.node-name) | ||
| 30 | NODE_IP=$(get_param k3s.node-ip) | ||
| 31 | IFACE=$(get_param k3s.iface) | ||
| 32 | |||
| 33 | # Default role is server | ||
| 34 | ROLE="${ROLE:-server}" | ||
| 35 | IFACE="${IFACE:-eth1}" | ||
| 36 | |||
| 37 | # Configure cluster network interface IP via networkd drop-in. | ||
| 38 | # The base 10-k3s-cluster.network (installed by virt_networking bbclass) | ||
| 39 | # claims eth1 and disables DHCP. This drop-in adds the static address. | ||
| 40 | if [ -n "$NODE_IP" ] && [ -n "$IFACE" ]; then | ||
| 41 | echo "Configuring $IFACE with $NODE_IP/24 via networkd drop-in" | ||
| 42 | DROPIN_DIR="/etc/systemd/network/10-k3s-cluster.network.d" | ||
| 43 | mkdir -p "$DROPIN_DIR" | ||
| 44 | cat > "$DROPIN_DIR/address.conf" << EOF | ||
| 45 | [Network] | ||
| 46 | Address=${NODE_IP}/24 | ||
| 47 | EOF | ||
| 48 | networkctl reload 2>/dev/null || true | ||
| 49 | fi | ||
| 50 | |||
| 51 | case "$ROLE" in | ||
| 52 | server) | ||
| 53 | # Server is the default — k3s.service is already enabled. | ||
| 54 | # Mask the agent service to prevent it from starting. | ||
| 55 | systemctl mask k3s-agent.service 2>/dev/null || true | ||
| 56 | echo "k3s role: server" | ||
| 57 | ;; | ||
| 58 | |||
| 59 | agent) | ||
| 60 | # Mask the server service to prevent it from starting | ||
| 61 | systemctl mask k3s.service 2>/dev/null || true | ||
| 62 | |||
| 63 | # Wipe any server state from previous boot | ||
| 64 | rm -rf /var/lib/rancher/k3s/server/tls \ | ||
| 65 | /var/lib/rancher/k3s/server/cred \ | ||
| 66 | /var/lib/rancher/k3s/server/token \ | ||
| 67 | /var/lib/rancher/k3s/server/agent-token \ | ||
| 68 | /var/lib/rancher/k3s/server/node-token \ | ||
| 69 | /var/lib/rancher/k3s/server/db \ | ||
| 70 | /etc/rancher/k3s/k3s.yaml | ||
| 71 | |||
| 72 | # Remove server-only config (disable-cloud-controller is | ||
| 73 | # server-only and crashes the agent) | ||
| 74 | rm -f /etc/rancher/k3s/config.yaml | ||
| 75 | |||
| 76 | # Build agent environment file with cmdline parameters | ||
| 77 | mkdir -p /etc/default | ||
| 78 | { | ||
| 79 | echo "# Generated by k3s-role-setup from kernel cmdline" | ||
| 80 | [ -n "$SERVER" ] && echo "K3S_URL=https://${SERVER}:6443" | ||
| 81 | [ -n "$TOKEN" ] && echo "K3S_TOKEN=${TOKEN}" | ||
| 82 | } > /etc/default/k3s-agent | ||
| 83 | |||
| 84 | # Build extra args | ||
| 85 | AGENT_ARGS="" | ||
| 86 | [ -n "$NODE_NAME" ] && AGENT_ARGS="$AGENT_ARGS --node-name $NODE_NAME" | ||
| 87 | [ -n "$NODE_IP" ] && AGENT_ARGS="$AGENT_ARGS --node-ip $NODE_IP" | ||
| 88 | [ -n "$IFACE" ] && AGENT_ARGS="$AGENT_ARGS --flannel-iface $IFACE" | ||
| 89 | |||
| 90 | if [ -n "$AGENT_ARGS" ]; then | ||
| 91 | # Create systemd override for extra args | ||
| 92 | mkdir -p /run/systemd/system/k3s-agent.service.d | ||
| 93 | cat > /run/systemd/system/k3s-agent.service.d/cmdline.conf << EOF | ||
| 94 | [Service] | ||
| 95 | ExecStart= | ||
| 96 | ExecStart=/usr/local/bin/k3s agent $AGENT_ARGS | ||
| 97 | EOF | ||
| 98 | fi | ||
| 99 | |||
| 100 | # Reload systemd to pick up overrides, then unmask and start | ||
| 101 | # the agent. --no-block queues the start without waiting, | ||
| 102 | # avoiding deadlock with the After= ordering. | ||
| 103 | systemctl daemon-reload | ||
| 104 | systemctl unmask k3s-agent.service 2>/dev/null || true | ||
| 105 | systemctl start k3s-agent.service --no-block | ||
| 106 | |||
| 107 | echo "k3s role: agent (server: ${SERVER:-not set})" | ||
| 108 | ;; | ||
| 109 | |||
| 110 | *) | ||
| 111 | echo "Unknown k3s.role=$ROLE, defaulting to server" | ||
| 112 | ;; | ||
| 113 | esac | ||
diff --git a/recipes-containers/k3s/k3s/k3s.service b/recipes-containers/k3s/k3s/k3s.service index f56110a4..e7402867 100644 --- a/recipes-containers/k3s/k3s/k3s.service +++ b/recipes-containers/k3s/k3s/k3s.service | |||
| @@ -4,7 +4,7 @@ Description=Lightweight Kubernetes | |||
| 4 | Documentation=https://k3s.io | 4 | Documentation=https://k3s.io |
| 5 | Requires=containerd.service | 5 | Requires=containerd.service |
| 6 | After=containerd.service | 6 | After=containerd.service |
| 7 | After=network-online.target | 7 | After=network-online.target k3s-role-setup.service |
| 8 | Wants=network-online.target | 8 | Wants=network-online.target |
| 9 | 9 | ||
| 10 | [Install] | 10 | [Install] |
diff --git a/recipes-containers/k3s/k3s_git.bb b/recipes-containers/k3s/k3s_git.bb index e2d0a33e..1b5f3430 100644 --- a/recipes-containers/k3s/k3s_git.bb +++ b/recipes-containers/k3s/k3s_git.bb | |||
| @@ -11,6 +11,10 @@ SRC_URI = "git://github.com/rancher/k3s.git;branch=release-1.35;name=k3s;protoco | |||
| 11 | file://k3s-clean \ | 11 | file://k3s-clean \ |
| 12 | file://cni-flannel.conflist \ | 12 | file://cni-flannel.conflist \ |
| 13 | file://k3s-killall.sh \ | 13 | file://k3s-killall.sh \ |
| 14 | file://k3s-role-setup.sh \ | ||
| 15 | file://k3s-role-setup.service \ | ||
| 16 | file://k3s-get-token.sh \ | ||
| 17 | file://10-k3s-cluster.network \ | ||
| 14 | " | 18 | " |
| 15 | 19 | ||
| 16 | # Traefik Helm charts — downloaded and embedded into the k3s binary | 20 | # Traefik Helm charts — downloaded and embedded into the k3s binary |
| @@ -34,6 +38,11 @@ PV = "v1.35.2+k3s1+git" | |||
| 34 | # K3s uses flannel for CNI networking, not the containerd bridge config | 38 | # K3s uses flannel for CNI networking, not the containerd bridge config |
| 35 | CNI_NETWORKING_FILES ?= "${UNPACKDIR}/cni-flannel.conflist" | 39 | CNI_NETWORKING_FILES ?= "${UNPACKDIR}/cni-flannel.conflist" |
| 36 | 40 | ||
| 41 | # Claim the cluster network interface (eth1) so systemd-networkd's | ||
| 42 | # default catch-all doesn't configure it with DHCP. The static IP | ||
| 43 | # is set at boot by k3s-role-setup.service via a networkd drop-in. | ||
| 44 | VIRT_NETWORKING_FILES ?= "${UNPACKDIR}/10-k3s-cluster.network" | ||
| 45 | |||
| 37 | PACKAGECONFIG ??= "traefik" | 46 | PACKAGECONFIG ??= "traefik" |
| 38 | PACKAGECONFIG[traefik] = ",,," | 47 | PACKAGECONFIG[traefik] = ",,," |
| 39 | 48 | ||
| @@ -63,6 +72,7 @@ inherit go | |||
| 63 | inherit goarch | 72 | inherit goarch |
| 64 | inherit systemd | 73 | inherit systemd |
| 65 | inherit cni_networking | 74 | inherit cni_networking |
| 75 | inherit virt_networking | ||
| 66 | inherit go-mod-discovery | 76 | inherit go-mod-discovery |
| 67 | 77 | ||
| 68 | BB_GIT_SHALLOW = "1" | 78 | BB_GIT_SHALLOW = "1" |
| @@ -140,8 +150,11 @@ do_install() { | |||
| 140 | if ${@bb.utils.contains('DISTRO_FEATURES','systemd','true','false',d)}; then | 150 | if ${@bb.utils.contains('DISTRO_FEATURES','systemd','true','false',d)}; then |
| 141 | install -D -m 0644 "${UNPACKDIR}/k3s.service" "${D}${systemd_system_unitdir}/k3s.service" | 151 | install -D -m 0644 "${UNPACKDIR}/k3s.service" "${D}${systemd_system_unitdir}/k3s.service" |
| 142 | install -D -m 0644 "${UNPACKDIR}/k3s-agent.service" "${D}${systemd_system_unitdir}/k3s-agent.service" | 152 | install -D -m 0644 "${UNPACKDIR}/k3s-agent.service" "${D}${systemd_system_unitdir}/k3s-agent.service" |
| 153 | install -D -m 0644 "${UNPACKDIR}/k3s-role-setup.service" "${D}${systemd_system_unitdir}/k3s-role-setup.service" | ||
| 143 | sed -i "s#\(Exec\)\(.*\)=\(.*\)\(k3s\)#\1\2=${BIN_PREFIX}/bin/\4#g" "${D}${systemd_system_unitdir}/k3s.service" "${D}${systemd_system_unitdir}/k3s-agent.service" | 154 | sed -i "s#\(Exec\)\(.*\)=\(.*\)\(k3s\)#\1\2=${BIN_PREFIX}/bin/\4#g" "${D}${systemd_system_unitdir}/k3s.service" "${D}${systemd_system_unitdir}/k3s-agent.service" |
| 144 | install -m 755 "${UNPACKDIR}/k3s-agent" "${D}${BIN_PREFIX}/bin" | 155 | install -m 755 "${UNPACKDIR}/k3s-agent" "${D}${BIN_PREFIX}/bin" |
| 156 | install -m 755 "${UNPACKDIR}/k3s-role-setup.sh" "${D}${BIN_PREFIX}/bin/k3s-role-setup.sh" | ||
| 157 | install -m 755 "${UNPACKDIR}/k3s-get-token.sh" "${D}${BIN_PREFIX}/bin/k3s-get-token" | ||
| 145 | fi | 158 | fi |
| 146 | 159 | ||
| 147 | mkdir -p ${D}${datadir}/k3s/ | 160 | mkdir -p ${D}${datadir}/k3s/ |
| @@ -165,9 +178,10 @@ do_install() { | |||
| 165 | 178 | ||
| 166 | PACKAGES =+ "${PN}-server ${PN}-agent" | 179 | PACKAGES =+ "${PN}-server ${PN}-agent" |
| 167 | 180 | ||
| 168 | SYSTEMD_PACKAGES = "${@bb.utils.contains('DISTRO_FEATURES','systemd','${PN}-server ${PN}-agent','',d)}" | 181 | SYSTEMD_PACKAGES = "${@bb.utils.contains('DISTRO_FEATURES','systemd','${PN}-server ${PN}-agent ${PN}','',d)}" |
| 169 | SYSTEMD_SERVICE:${PN}-server = "${@bb.utils.contains('DISTRO_FEATURES','systemd','k3s.service','',d)}" | 182 | SYSTEMD_SERVICE:${PN}-server = "${@bb.utils.contains('DISTRO_FEATURES','systemd','k3s.service','',d)}" |
| 170 | SYSTEMD_SERVICE:${PN}-agent = "${@bb.utils.contains('DISTRO_FEATURES','systemd','k3s-agent.service','',d)}" | 183 | SYSTEMD_SERVICE:${PN}-agent = "${@bb.utils.contains('DISTRO_FEATURES','systemd','k3s-agent.service','',d)}" |
| 184 | SYSTEMD_SERVICE:${PN} = "${@bb.utils.contains('DISTRO_FEATURES','systemd','k3s-role-setup.service','',d)}" | ||
| 171 | SYSTEMD_AUTO_ENABLE:${PN}-agent = "disable" | 185 | SYSTEMD_AUTO_ENABLE:${PN}-agent = "disable" |
| 172 | 186 | ||
| 173 | FILES:${PN}-agent = "${BIN_PREFIX}/bin/k3s-agent" | 187 | FILES:${PN}-agent = "${BIN_PREFIX}/bin/k3s-agent" |
