Your first native pod
Install k3sm, start a node, run a Pod, and find that Pod in ps as an ordinary macOS process.
About 15 minutes, most of it the install. By the end you have a single-node k3sm cluster on
your Mac, one running Pod, and that same Pod visible in ps as a native Darwin process owned by
_k3sm.
On every other Kubernetes-on-a-Mac, the thing you started is a process inside a Linux VM, and your Mac cannot see it. On k3sm it is a process on your Mac.
1. Install#
curl -fsSL https://k3sm.io/install.sh | sh
The script preflights for Apple Silicon and macOS 26+, downloads the release tarball and its
checksums, verifies the sha256, prints what it is about to do, and then runs sudo k3sm install for
you. That one admin step creates the unprivileged _k3sm user, installs the root networking helper
and the launchd daemons, and writes an admin kubeconfig into your home directory. Everything after
it runs with no sudo.
The script installs the newest published release, a pre-release build that is ad-hoc signed and
not notarized. Set K3SM_INSTALL_DOWNLOAD_ONLY=1 to download and verify without running anything
privileged. install covers how to pin a version and the three install channels,
of which only the script is published so far.
2. Control Plane and Node#
The install registered the server as a launchd daemon, so the control plane and the node are already running and come back after a reboot. Do not start a second copy. The command below is for a throwaway foreground cluster on a Mac with no install, and running it beside the daemon makes the two fight over the work directory and the apiserver port:
k3sm server
Either way, one process supervises the upstream kube-apiserver, controller-manager, and scheduler
over kine and SQLite, and runs the Virtual Kubelet node beside them. To check the installed one,
run k3sm status: one screen covering the daemons, the apiserver, the node, workloads and the data
root, with an exit code a script can branch on.
3. Talk to the Cluster#
k3sm kubectl get nodes
k3sm kubectl is the bundled passthrough, using the admin context the install merged into your
kubeconfig, so your own client reaches the cluster too. If you write the admin kubeconfig to a file
of its own, point KUBECONFIG at that file; kubectl access shows how.
The rest of these pages write k3sm kubectl. A standalone kubectl behaves identically, because
the API server is the genuine upstream one.
4. Run a Pod#
On this default path a k3sm workload is a native arm64 Darwin executable. image: native is a
sentinel meaning “the workload is command, whose first element is an absolute path to a host
binary”.
apiVersion: v1
kind: Pod
metadata:
name: hello-native
namespace: default
spec:
nodeSelector:
kubernetes.io/os: darwin
tolerations:
- key: k3sm.io/provider
operator: Exists
effect: NoSchedule
restartPolicy: Never
containers:
- name: hello
image: native
command:
- /bin/sh
- -c
- |
echo 'hello from a k3sm native pod'
/usr/bin/sw_vers
exec /usr/bin/tail -f /dev/null
Save it as hello-native.yaml (it is the same manifest as examples/hello-native.yaml in the
k3sm repository) and apply it:
k3sm kubectl apply -f hello-native.yaml
k3sm kubectl get pods -o wide
k3sm kubectl logs hello-native
The logs carry the greeting and the output of sw_vers, which is your Mac’s own macOS version.
5. See It in ps#
Find the Pod’s process:
pgrep -fl tail
That prints the pid and command line of the tail -f /dev/null the Pod is running. It is a plain
process in your Mac’s process table. Ask who owns it:
ps -o user=,pid=,command= -p "$(pgrep -f 'tail -f /dev/null' | head -1)"
The owner is _k3sm, the unprivileged service user the install step created. Every Pod on the node
runs as that same user, so there is no per-Pod uid isolation and same-node Pods share one OS trust
domain. What that costs you, and the way out of it, are covered in
what will not work and
the vm RuntimeClass.
6. Clean Up#
k3sm kubectl delete pod hello-native
On the default runtime, deletion follows the upstream sequence. The preStop hook runs, the
process gets SIGTERM, and after the grace period (terminationGracePeriodSeconds, 30 seconds by
default) anything still alive gets SIGKILL. Check with pgrep -fl tail that it is gone.
What to Know Before You Build on This#
- Two fields are mandatory. The
kubernetes.io/os: darwinnode selector is enforced by admission, and thek3sm.io/providertoleration is what lets the scheduler place the Pod. Drop either and the Pod fails to apply or never runs. restartPolicyis honored on the default runtime. An exited container is restarted in place per its policy, andAlwaysrestarts even a clean exit 0. The restart count and aCrashLoopBackOffbackoff read as they do upstream. The--runtime hostprocessopt-out keeps the old reap-only behavior, where a container exits once and is never respawned until a controller replaces the Pod. See limitations.- The workload is a binary you built. For your own app, build a darwin/arm64 binary with your usual
toolchain and point
command[0]at its absolute path, or setimage: /abs/pathand omitcommandentirely.
Next#
- deployments and services puts a controller and a VIP in front of it.
- limitations is the full gap inventory. Read it before the second tutorial rather than after the twelfth.