k3sm licensed apache-2.0 (DCO)

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 cp from a created container;
  • take a build produced on a Linux machine or CI runner;
  • build with GOOS=linux GOARCH=arm64 go build for 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 dowhat happens, and why
a /bin/sh entrypoint reads a mounted config file at its absolute paththe 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 restartit 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 nameit 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 portit 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#