{"slug": "show-hn-kubesilicon-linux-vms-and-mac-gpu-jobs-from-kubernetes-objects", "title": "Show HN: KubeSilicon – Linux VMs and Mac GPU Jobs from Kubernetes Objects", "summary": "A developer released KubeSilicon, an early-stage open-source project that runs Linux virtual machines from Kubernetes objects on Apple Silicon Macs by replacing KubeVirt's KVM layer with Apple's Virtualization.framework, and runs GPU jobs natively on the Mac via PyTorch MPS or MLX through a SiliconJob object. The project requires an Apple Silicon Mac, macOS 13 or newer, Go, qemu-img, a running kind cluster and kubectl, and its author states the API is \"v1 in name only and will change\" and that it is not production software. A kubesilicond daemon runs outside the cluster, watches SiliconVM objects and starts one VM per object, writing status.phase and status.ip back and publishing a headless Service so pods can reach VMs by IP or by name.", "body_md": "Run Linux virtual machines from Kubernetes objects on a Mac with Apple Silicon, the way KubeVirt does on Linux. It also runs GPU jobs natively on the Mac (PyTorch MPS or MLX) from a `SiliconJob` object, so a Kubeflow Pipelines step can train on the Apple GPU: see [GPU jobs](#gpu-jobs-siliconjob) and [benchmarks](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/docs/benchmarks.md).\n\n[KubeVirt](https://kubevirt.io) needs KVM, which does not exist on macOS. KubeSilicon replaces the KVM layer with Apple's [`Virtualization.framework`](https://developer.apple.com/documentation/virtualization): you declare a `SiliconVM`, a daemon on the Mac boots it, and pods in your local cluster (kind, for example) can reach it over the network.\n\n**Status: early.** It works end to end on one Mac with kind on Docker Desktop (see [docs/validation.md](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/docs/validation.md)). The API is `v1` in name only and will change. Not production software.\n\n## kubesilicon-tutorial.mp4\n\n```\n kind (Docker Desktop VM)              macOS host\n ┌──────────────────────┐              ┌────────────────────────────────────┐\n │ SiliconVM objects    │◄─ API watch ─┤ kubesilicond                        │\n │ (CRD kubesilicon.io) │── status ───►│   download + convert image          │\n │                      │              │   cloud-init ISO                    │\n │ pod ──────── ping/ssh ─────────────►│   Virtualization.framework  ──► VM  │\n └──────────────────────┘              └────────────────────────────────────┘\n```\n\n`kubesilicond` runs on the Mac, outside the cluster, watches `SiliconVM` objects and starts one VM for each, in its own detached process. It writes `status.phase` and `status.ip` back and publishes a headless Service, so pods reach the VM by IP or by name (`<name>.<namespace>.svc`).\n\nRequirements: Apple Silicon Mac, macOS 13 or newer, Go, `qemu-img` (`brew install qemu`), a running kind cluster, `kubectl`.\n\n```\nmake install CTX=kind-<your-cluster>    # build, install the CRD, start the daemon at login\n```\n\n`CTX` is required on purpose, so the daemon never acts on the wrong cluster. Undo with `make uninstall`.\n\nIn another terminal:\n\n```\n# vm.yaml\napiVersion: kubesilicon.io/v1\nkind: SiliconVM\nmetadata: { name: demo }\nspec:\n  cpu: 2\n  memory: 2Gi\n  disk:\n    source: https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-arm64.img\n    size: 10Gi\n  cloudInit:\n    userData: |\n      #cloud-config\n      users:\n        - name: ubuntu\n          sudo: ALL=(ALL) NOPASSWD:ALL\n          ssh_authorized_keys: [ssh-ed25519 AAAA... you@host]\nphp\nkubectl apply -f vm.yaml\nkubectl get svm -w          # Provisioning -> Starting -> Running, with IP\nmake bin/kubesilicon        # the client\n./bin/kubesilicon -context kind-<your-cluster> ssh demo\n# from a pod: ping/ssh demo.kubesilicon.svc (namespace of the SiliconVM)\nkubectl patch svm demo --type=merge -p '{\"spec\":{\"running\":false}}'   # stop, keeps the disk\nkubectl delete svm demo     # stops the VM and removes its disk\n```\n\nStopping `kubesilicond` does **not** stop the VMs. Stop a VM with `running: false`. When the daemon starts again it picks the running VMs back up.\n\nA Linux guest cannot reach the Mac GPU, so GPU work runs natively on macOS. A `SiliconJob` runs a Python script in its own `uv` venv, inside a `sandbox-exec` profile (writes only in the job directory, no reads of your home), with PyTorch MPS or MLX. One job runs at a time; the others wait as `Pending`.\n\n```\napiVersion: kubesilicon.io/v1\nkind: SiliconJob\nmetadata: { name: train }\nspec:\n  dependencies: [torch]          # binary wheels only\n  timeoutSeconds: 3600\n  inputs:  [{ url: \"https://.../data.csv\", path: data.csv }]      # GET before the job\n  outputs: [{ url: \"https://.../model.pt?X-Amz-...\", path: model.pt }]  # PUT after (presigned)\n  script: |\n    import torch\n    print(torch.backends.mps.is_available())\nkubectl get sjob -w\nkubesilicon -context kind-<cluster> logs train\nkubectl patch sjob train --type=merge -p '{\"spec\":{\"running\":false}}'   # cancel\nhack/e2e-job.sh kind-<cluster>                                          # end-to-end test\n```\n\n`kubectl apply -f examples/mlx-matmul.yaml` runs the same job with MLX. Measured on an M5 Pro (one run, float32 4096x4096 matmul, inside the sandbox):\n\n|  | per matmul | TFLOPS | \n|---|---|---|\n| GPU (Metal) | 8.2 ms | 16.7 | \n| CPU | 69 ms | 2.0 | \n\n`examples/bench-cnn.yaml` trains the same small CNN (2 conv layers, Fashion-MNIST, 5 epochs, same seed) on both devices. One run on an M5 Pro, with the kind cluster running at the same time and the job at `nice 10`:\n\n|  | 5 epochs | test accuracy | \n|---|---|---|\n| MPS | 7.7 s | 0.9008 | \n| CPU | 40.5 s | 0.9007 | \n\nAbout 5x faster on the GPU, same accuracy. The small MLP in the pipeline example is too small to tell the two apart. For three more benchmarks each on PyTorch and MLX (median of 5, with the cases where the CPU wins), see [docs/benchmarks.md](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/docs/benchmarks.md).\n\n```\nhack/kind-kubeflow.sh                       # kind (k8s 1.34) + cert-manager + KFP 2.18 + CRDs + RBAC; needs Docker with 12 GB\n./bin/kubesilicond -context kind-kubeflow   # or: make install CTX=kind-kubeflow\npython examples/kubeflow/pipeline.py        # needs `pip install kfp`; writes train_on_mac_gpu.yaml\nkubectl --context kind-kubeflow -n kubeflow port-forward svc/ml-pipeline-ui 8080:80   # upload the YAML at http://localhost:8080\n```\n\nThe `silicon_job` component in `examples/kubeflow/pipeline.py` creates a `SiliconJob` and waits for it; the job log appears in the step log. Jobs survive a daemon restart and are retried if the Mac reboots (`backoffLimit`). To run this beyond a laptop lab, read [docs/production.md](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/docs/production.md). Needs [`uv`](https://docs.astral.sh/uv/). See [docs/design.md](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/docs/design.md).\n\n`examples/kubeflow/fashion.py` is a 4-step pipeline: `prepare_data` (pod) puts the dataset in the KFP SeaweedFS, `presign` (pod) creates the presigned URLs, `silicon_job` trains on the Mac GPU (PyTorch MPS) and `evaluate` (pod) scores the model on CPU. The Mac job gets its data and returns the model through presigned URLs (`inputs` / `outputs` of the `SiliconJob`).\n\n```\nkubectl -n kubeflow port-forward svc/seaweedfs 8333:8333     # the Mac reaches the bucket here\npython examples/kubeflow/fashion.py                           # needs `pip install kfp kfp-kubernetes boto3`; writes fashion.yaml\n```\n\nUpload `fashion.yaml` in the KFP UI. Measured on an M5 Pro with Docker Desktop (5 epochs, 784-256-10 MLP): trained in 1.4 s on `mps`, test accuracy 0.8645 (the same number the training reported, so the artifacts round-trip intact).\n\n`make install` copies the daemon to `~/.kubesilicon/bin` and loads a LaunchAgent. `make uninstall` removes the agent; running VMs keep running. launchd restarts the daemon if it dies. After a real Mac reboot the daemon is back at login and waits for the API server, so with kind on Docker Desktop turn on \"Start Docker Desktop when you sign in\"; nothing else needs a manual start. The daemon's log is `~/.kubesilicon/kubesilicond.log`. Objects deleted while the daemon was down are cleaned up the next time it starts (their VM is shut down and its directory removed).\n\n```\nexport KUBESILICON_CONTEXT=kind-<your-cluster>     # or pass -context; there is no default on purpose\nkubesilicon ip demo                  # print the VM IP\nkubesilicon ssh demo                 # ssh as ubuntu; extra ssh args after --, user with -l\nkubesilicon ssh demo -- uname -a\nkubesilicon console demo             # serial console, leave with Ctrl-]\n```\n\n`console` needs a user with a password (cloud-init `plain_text_passwd` and `lock_passwd: false`). It is mostly for debugging boot problems; use `ssh` otherwise. The client calls `kubectl` for the lookup, so `kubectl` must be on the PATH. Host keys are not checked, because VM IPs get reused on a local lab.\n\nSerial output is also written to `~/.kubesilicon/vms/<namespace>_<name>/console.log`.\n\n| Works | Not yet | \n|---|---|\n| Linux ARM64 guests, EFI boot, cloud images | macOS guests | \n| cloud-init (NoCloud ISO), `kubesilicon` client (`ip` ,`ssh` ,`console` ) | Windowed or VNC console | \n| start, stop (ACPI, then forced), delete with finalizer | Automatic restart of a VM whose launcher died | \n| Disk reuse across stop/start, qcow2 to raw conversion | Multiple Macs as nodes | \n| Pods reach the VM over NAT, by IP or DNS name | Live migration, hotplug, snapshots, GPU | \n| One process per VM: the daemon can stop, restart or crash and VMs keep running |  | \n| Start at login (LaunchAgent), orphan cleanup |  | \n\nLimits that come from Apple's framework: no live migration, no graphical console without a window. Nested virtualization in the guest exists on M3 and newer with macOS 15, but is not enabled yet. See [docs/design.md](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/docs/design.md) for the mapping to KubeVirt and the roadmap.\n\n```\nmake test              # unit tests\nhack/e2e.sh            # full cycle against a cluster; needs a kubeconfig context named \"kubesilicon\"\n```\n\nContributions are welcome, see [CONTRIBUTING.md](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/CONTRIBUTING.md).\n\nKubeSilicon is an independent project. It is not affiliated with or endorsed by Apple Inc. or the KubeVirt project. Apple, Mac, macOS and Apple Silicon are trademarks of Apple Inc. Running macOS guests is subject to Apple's license terms; this project only runs Linux guests.\n\nApache License 2.0. See [LICENSE](https://github.com/Matheus-Thurler/KubeSilicon/blob/main/LICENSE).", "url": "https://wpnews.pro/news/show-hn-kubesilicon-linux-vms-and-mac-gpu-jobs-from-kubernetes-objects", "canonical_source": "https://github.com/Matheus-Thurler/KubeSilicon", "published_at": "2026-11-10 12:29:20+00:00", "updated_at": "2026-11-10 12:53:55.387300+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-tools", "mlops", "developer-tools"], "entities": ["KubeSilicon", "KubeVirt", "Apple Silicon", "Virtualization.framework", "PyTorch MPS", "MLX", "Kubeflow Pipelines", "kind"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-kubesilicon-linux-vms-and-mac-gpu-jobs-from-kubernetes-objects", "markdown": "https://wpnews.pro/news/show-hn-kubesilicon-linux-vms-and-mac-gpu-jobs-from-kubernetes-objects.md", "text": "https://wpnews.pro/news/show-hn-kubesilicon-linux-vms-and-mac-gpu-jobs-from-kubernetes-objects.txt", "jsonld": "https://wpnews.pro/news/show-hn-kubesilicon-linux-vms-and-mac-gpu-jobs-from-kubernetes-objects.jsonld"}}