What will not work, and why
Watch a Linux binary fail to start as a k3sm Pod, see exactly why it happens, and take the documented way out.
About 15 minutes. You produce a failure, read the exact reason it happens, and walk out with the documented workaround.
Setup#
Get hold of a Linux binary, one that expects a Linux kernel and links against glibc. Any of these gives you one:
- copy an executable out of a Linux container image with
docker cpfrom a created container; - take a build produced on a Linux machine or CI runner;
- build with
GOOS=linux GOARCH=arm64 go buildfor a statically linked case, which fails for the same reason with fewer moving parts.
Put it somewhere absolute, say /opt/linux-demo/bin/app, and point a Pod at it.
Failure#
apiVersion: v1
kind: Pod
metadata:
name: linux-binary
namespace: default
spec:
nodeSelector:
kubernetes.io/os: darwin
tolerations:
- key: k3sm.io/provider
operator: Exists
effect: NoSchedule
restartPolicy: Never
containers:
- name: app
image: native
command: ["/opt/linux-demo/bin/app"]
k3sm kubectl apply -f linux-binary.yaml
k3sm kubectl get pod linux-binary
k3sm kubectl describe pod linux-binary
The Pod is admitted (it carries both mandatory fields) and it is scheduled onto your Mac. Then it
never reaches a running state, and the Pod’s status goes to ProviderFailed. Read the exact reason
on the Pod, and the daemon’s own account of it in the log:
k3sm kubectl describe pod linux-binary | sed -n '/Events/,$p'
log show --predicate 'subsystem BEGINSWITH "io.k3sm"' --last 5m
The reason names the binary, not your program:
image: binary rejected by signature policy: /opt/linux-demo/bin/app is unsigned (adhoc-ok requires a signature)
There was no image pull error, no CrashLoopBackOff, and no exit code from your program, because
the binary was never handed to the loader. k3sm’s image signature policy refused it before exec
was attempted.
Why#
A Pod on k3sm is a native Darwin process, not a Linux container.
k3sm has no Linux kernel anywhere in the default path. A container is posix_spawn’d at a host path
under a generated default-deny Seatbelt profile, with an APFS-cloned root. There are no namespaces,
no cgroups, no OverlayFS, and no CNI, because macOS has none of those primitives. The image field
is a sentinel for a host binary, not a reference to a root filesystem that gets unpacked around your
process.
Before k3sm will even try to run a binary, it checks its code signature. Your Linux ELF carries
none, because a Linux toolchain doesn’t produce one, and k3sm’s default policy accepts only an
ad-hoc-or-better signature. That check runs first and rejects the binary outright, which is the
ProviderFailed you just saw. exec is never reached, so there is no loader failure to read and no
exit status.
Sign the same ELF (codesign -s - /opt/linux-demo/bin/app) and the signature policy passes, which
only moves the failure one step later. Nothing on this machine can load a Linux ELF. Darwin’s loader
reads Mach-O, and a glibc-linked binary additionally wants a Linux syscall interface that does not
exist on this machine at any privilege level, so execve itself now fails with exec format error.
k3sm reports that as a terminated container, with reason: Error and a nonzero exit code that
belongs to the exec shim rather than to your program, and the Pod’s status reads Error instead of
ProviderFailed. Your code still never ran. It now looks like a process that started and
immediately died, rather than one refused before it began.
Either way, this is the trade the native path makes. A Linux VM would run that binary and hide the
Mac; k3sm keeps the Mac and refuses that binary as image: native. An arm64 Linux image can still
run through the vm RuntimeClass, covered next. Why k3sm makes the argument for the trade.
Way Out#
Rebuild for darwin/arm64. That is the supported route today and, for anything you own, usually a one-line change:
GOOS=darwin GOARCH=arm64 go build -o /opt/demo/bin/app ./cmd/app
Then point command[0] at the darwin build. Pods get the full treatment regardless of how the
binary was produced, with Seatbelt confinement, probes, volume mounts, and memory limits.
For code you cannot rebuild, the vm RuntimeClass boots it in its own guest instead. A Pod that
names runtimeClassName: vm boots a linux/arm64 OCI image in a per-pod micro-VM and reaches
Running. The guest kernel and initramfs are fetched from a
pinned release and digest-verified on every start. The path is single-node.
linux/amd64 does not run, because that needs
in-guest Rosetta translation, held for a later release. See
the vm RuntimeClass for exactly where the line falls. Until amd64
lands, a Linux-only amd64 binary is a workload k3sm does not run.
Four More Failures Worth Producing on Purpose#
All four are documented, and all four are faster to meet here than in production.
| what you do | what happens, and why |
|---|---|
a /bin/sh entrypoint reads a mounted config file at its absolute path | the file is not there. Volume mounts resolve through a DYLD_INSERT_LIBRARIES path-rebase shim, and macOS strips that variable from SIP platform binaries. Ship a compiled binary, see storage |
a bare Pod exits under --runtime hostprocess and you wait for it to restart | it stays exited. That opt-out reaps an exited container and never respawns it; a controller replaces the Pod. On the default runtime restartPolicy is honored, and the container restarts in place, restart count and CrashLoopBackOff included |
a /bin/sh script dials another Service by DNS name | it does not resolve. In-Pod cluster DNS is wired on the default runtime, but the resolver shim cannot load into SIP platform binaries (/bin/sh, /usr/bin/*), so a shell script’s lookups fall back to the host resolver. Ship a compiled binary |
| a Service exposes a UDP port | it blackholes. Only cluster DNS on :53 uses UDP; general UDP Services are unimplemented, ClusterIP and NodePort alike |
Where the Full List Lives#
Limitations is the full inventory, covering the resource model, the trust domain, the addresses Services answer on, NetworkPolicy’s scope, certificate rotation’s scope, and the DNS gaps. The formal self-assessment (which feature classes are met and which have a documented ceiling) is the conformance profile. k3sm cannot pass the CNCF conformance suite, by construction. Read both before you depend on k3sm.
Next#
- limitations is the inventory in full.
- troubleshooting tells a bug from a documented gap.