k3sm licensed apache-2.0 (DCO)

Joining a second Mac

Mint a join token, bring up a second Mac as an agent over the wireguard mesh, and see two nodes serving one cluster: cross-node NodePort, PVC persistence, and in-pod kubectl and DNS from the joined worker all work on two Macs.


Thirty minutes, for two machines. You come away with a second Mac joined to the first one’s cluster over the wireguard mesh, and a clear line around what the two-node path can and cannot do yet. Multi-node is experimental, and preview-quality until it graduates in v0.3.

The join flow, the bootstrap credentials, and the mesh peer model work on a pair of Macs. With two Macs you get two Ready nodes, a cross-node NodePort round trip, a PersistentVolumeClaim that keeps its data across a pod restart, and a pod on the joined worker resolving cluster DNS and completing an authenticated apiserver call against the server.

Mesh Model#

One Mac runs the control plane with k3sm server. Additional Macs join as agents running the Virtual Kubelet node. Nodes are connected by a wireguard mesh, so Pod and Service traffic can cross machines. Private keys never leave a node; the peer records held in the datastore carry public keys only.

1. On the Server Mac#

Give the install its own mesh address so other nodes can join it:

sudo k3sm install --mesh-ip <this-macs-mesh-address>

Then mint a join token:

sudo k3sm token create

Run that one with sudo. The token pins the hash of the cluster CA, which lives in the control-plane state root owned by the _k3sm service user. Without sudo the work directory resolves to your own home and the command exits non-zero rather than inventing a CA there.

The control plane only starts accepting joins once its own mesh enrolment has finished, and that can take up to a minute after the install command returns. If step 2 reports that the control plane cannot be reached, wait a little and run it again.

2. On the Second Mac#

Put the token from step 1 in a file only root can read:

sudo sh -c 'umask 077; cat > /var/root/k3sm-join-token'

Paste the token, then press Ctrl-D. Now install and join in one step:

sudo k3sm install --agent --server <server-lan-ip-or-name> --token-file /var/root/k3sm-join-token

--server is the control-plane Mac’s own LAN address (an underlay IP or hostname, no scheme, no port); the join dials it on 9345, before this Mac has any mesh to route over. --node-ip is optional and, when given, is this Mac’s mesh address instead: the join assigns that address itself, so pass --node-ip only to assert the value you expect. The token file must not be group- or world-readable, and once the node shows Ready it is yours to delete. See multi-node for predicting or asserting the mesh address a joining node will get.

The agent presents the bootstrap token, receives its node credentials, and registers its wireguard public key as a mesh peer. The worker gets its own agent launchd daemon, starts at boot, and rejoins with its stored credential, no token needed. Uninstalling k3sm on it removes the node from the cluster.

3. Confirm#

From the server Mac:

k3sm kubectl get nodes -o wide

Both Macs appear as ready nodes, and a Service resolves to backends on either one. If the join fails, re-mint the token (tokens expire) and confirm the agent can reach the server on 9345. See troubleshooting.

What Changes Once There Are Two Nodes#

  • Stateful Pods are pinned by their data. A local-path volume lives on one Mac’s disk, and the PV’s node affinity sends every future Pod for that claim back to the same machine. Plan placement around the data. See storage.
  • Per-pod IP identity and headless or StatefulSet DNS records are present. Check cross-node resolution with your own workload before you depend on pod names.
  • Upgrades are a node-by-node rolling restart of the launchd daemons, which opens a brief version-skew window between machines. See upgrade.
  • A joined worker enforces no NetworkPolicy, so its Pods accept connections whatever a policy says. The server node enforces policy for its own Pods, on Service-VIP traffic only; direct pod-IP traffic bypasses it. Do not use a second Mac as a tenancy boundary; see the vm RuntimeClass.
  • Routing a Service to a vm Pod on another node is not wired yet. A vm Pod reaches other Services by ClusterIP like any Pod, and a Service routes to one on the same node.

High Availability#

A control plane on more than one Mac is not available in this release: a second server cannot join yet, and the installer does not expose the embedded etcd flags. One Mac runs the control plane and the others join as workers. high availability describes the design.

Next#