https://github.com/saiyam1814/kiac
Local Kubernetes on Apple's container framework - every node is its own lightweight VM. Metrics, storage, and LoadBalancer included.
https://github.com/saiyam1814/kiac
apple-silicon containers kubernetes macos
Last synced: 2 days ago
JSON representation
Local Kubernetes on Apple's container framework - every node is its own lightweight VM. Metrics, storage, and LoadBalancer included.
- Host: GitHub
- URL: https://github.com/saiyam1814/kiac
- Owner: saiyam1814
- License: mit
- Created: 2026-06-10T18:10:36.000Z (25 days ago)
- Default Branch: main
- Last Pushed: 2026-06-11T16:18:32.000Z (24 days ago)
- Last Synced: 2026-06-11T18:11:22.861Z (24 days ago)
- Topics: apple-silicon, containers, kubernetes, macos
- Language: Go
- Homepage: https://saiyam1814.github.io/kiac/
- Size: 306 KB
- Stars: 13
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
Local Kubernetes clusters where every node is its own lightweight VM.
Native on Apple silicon, powered by apple/container. No Docker Desktop. No Lima. No QEMU.
```bash
brew install saiyam1814/tap/kiac
kiac create cluster --workers 2
```
---
## Why this matters
Running a local Kubernetes cluster on a Mac has always meant a quiet compromise. Your "nodes" were containers sharing one kernel inside one hidden Linux VM, all pretending to be separate machines. It worked until you tried to test a node failure, or `kubectl top`, or a `type: LoadBalancer` service, and the illusion cracked.
A Kubernetes node wants to *be* a machine: its own kernel, its own kubelet, its own cgroups, its own IP that can come and go on its own. **kiac gives every node exactly that** by booting each one as its own lightweight virtual machine on Apple's native runtime. The result is a local cluster that behaves like a real one, created with a single command in a couple of minutes.
## Why Apple containers
When Apple shipped `container` 1.0, most people read it as "Docker, but from Apple." It is something more interesting underneath: **every container is its own lightweight virtual machine.**
The [Containerization](https://github.com/apple/containerization) framework boots a separate, minimal Linux VM for each container on Apple's `Virtualization.framework`:
- **The image becomes a disk.** The OCI image is turned into an EXT4 filesystem and handed to the VM as its root block device. No overlay mount layered on a shared host kernel.
- **A dedicated kernel boots.** Each container gets its own minimal, optimized Linux kernel. It is not shared with the host or any other container.
- **`vminitd` is PID 1.** A tiny Swift init system comes up first, then launches and supervises your process. The host drives it through a gRPC API over `vsock`.
- **virtio devices, direct networking.** No BIOS, no legacy device emulation, so the VM boots in about a second and gets its own IP you can reach from your Mac.
You get the developer experience of containers with the isolation boundary of a virtual machine. That combination is exactly what a Kubernetes node wants.
## Why Kubernetes on Apple containers: real isolation
When local Kubernetes tools run "nodes" as Docker containers, those nodes are processes sharing one Linux kernel, separated only by namespaces. Namespaces are a software boundary *inside* a single shared kernel. With kiac, the boundary between nodes is the **hypervisor** itself.
That difference is not academic. It changes what the cluster can actually do:
- **Blast radius.** A container escape that reaches the shared kernel reaches every node on it. With a VM per node, an escape is contained to one VM.
- **Failure domains.** A shared kernel is a shared fate: one panic or runaway sysctl takes everything down together. With kiac, a kernel problem stays inside the VM that caused it.
- **Real node failure.** Stop one node VM and it behaves like an actual node going offline: NotReady detection, eviction, rescheduling. You cannot meaningfully test that when "stopping a node" means killing one of several processes that share a kernel.
- **Per-node kernel reality.** Each node has its own `/proc`, `/sys`, modules, and sysctls. Node-level behavior is real, not simulated.
Containers are great for packaging software, and kiac depends on them. The point is narrower: when the workload you are isolating is itself a machine, a machine-grade boundary is the right tool.
## Features
- ๐ **Hardware-grade isolation** โ each node is one lightweight VM with its own kernel and cgroups, not namespaces sharing a daemon.
- ๐ **Metrics out of the box** โ `kubectl top nodes` works the moment the cluster is up. metrics-server ships preconfigured.
- ๐พ **PVCs that just bind** โ a default StorageClass (local-path-provisioner) is installed on create, so StatefulSets and `volumeClaimTemplates` work immediately.
- โ๏ธ **`type: LoadBalancer` works** โ MetalLB ships by default, pooled on node IPs, so Services get a real EXTERNAL-IP you can curl from your Mac. No ``, no tunnels.
- ๐ **Direct networking** โ every node gets a routable IP on macOS 26+. Hit NodePorts directly, no port-mapping flags.
- ๐งฑ **Multi-node, day one** โ `--workers N` gives a real topology: scheduling, cross-node pod networking, node failures you can practice on.
- ๐ฅ๏ธ **A console when you want one** โ `kiac ui` opens a local web console to create, watch, and delete clusters, same engine as the CLI.
- ๐ **Native stack** โ one Swift runtime from Apple, one Go binary from us. Coexists with Docker Desktop, kind, and k3d; never touches the Docker socket.
## Quickstart
### Requirements
- An Apple silicon Mac
- macOS 26+ for multi-node clusters (single-node works on macOS 15, with limitations)
- [apple/container](https://github.com/apple/container/releases) 1.0.0+
- `kubectl`
### Install
```bash
brew install saiyam1814/tap/kiac
```
Other install methods
```bash
# With Go
go install github.com/saiyam1814/kiac@latest
# From source
git clone https://github.com/saiyam1814/kiac && cd kiac && make build
```
### Create your first cluster
```bash
kiac doctor # check your setup
kiac create cluster --name dev --workers 2 # 1 control plane + 2 workers
```
```text
โฌข kiac v0.1.0 ยท Kubernetes in Apple Containers
โ Preflight checks (0.3s)
โ Pulling node image kindest/node:v1.36.1 (8.4s)
โ Booting 3 node VM(s) (9.8s)
โ Initializing Kubernetes control plane (49.6s)
โ Joining 2 worker(s) (13.5s)
โ Installing CNI (kindnet) (0.4s)
โ Installing storage (local-path-provisioner) (0.5s)
โ Installing metrics-server (0.4s)
โ Installing LoadBalancer (MetalLB) (0.6s)
โ Waiting for nodes to be Ready (10.7s)
โ Configuring LoadBalancer IP pool (3.2s)
โ Writing kubeconfig (0.2s)
Cluster "dev" is ready in 2m26s. Every node is its own lightweight VM.
```
The kubeconfig is merged into `~/.kube/config` as context `kiac-dev` (your existing config is backed up to `~/.kube/config.kiac.bak` the first time).
### See the isolation pay off
```bash
$ kubectl get nodes -o wide
NAME STATUS ROLES VERSION INTERNAL-IP KERNEL-VERSION CONTAINER-RUNTIME
kiac-dev-control-plane Ready control-plane v1.36.1 192.168.64.2 6.12.28 (arm64) containerd://2.3.1
kiac-dev-worker-1 Ready v1.36.1 192.168.64.3 6.12.28 (arm64) containerd://2.3.1
kiac-dev-worker-2 Ready v1.36.1 192.168.64.4 6.12.28 (arm64) containerd://2.3.1
$ kubectl top nodes
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
kiac-dev-control-plane 269m 5% 828Mi 20%
kiac-dev-worker-1 35m 0% 288Mi 7%
kiac-dev-worker-2 52m 1% 359Mi 9%
$ kubectl expose deploy web --port=80 --type=LoadBalancer
$ kubectl get svc web
NAME TYPE EXTERNAL-IP PORT(S) AGE
web LoadBalancer 192.168.64.3 80:30495/TCP 15s
$ curl http://192.168.64.3 # HTTP 200, straight from your Mac
```
## Usage
```bash
kiac doctor # check your setup
kiac create cluster # single node, everything included
kiac create cluster --name dev --workers 2 # 1 control plane + 2 workers
kiac create cluster --k8s-version 1.34 # pick your Kubernetes (1.32-1.36 pinned)
kiac ui # local web console to create/manage clusters
kiac get clusters
kiac get nodes --name dev
container build -t myapp:dev . # build with apple/container
kiac load image myapp:dev --name dev # push it into every node
kiac delete cluster --name dev
```
### Flags for `create cluster`
| Flag | Default | Description |
|---|---|---|
| `--name` | `dev` | cluster name |
| `--workers` | `0` | worker count; control plane is untainted when 0 |
| `--k8s-version` | `1.36` | Kubernetes minor, pinned digests for 1.32-1.36 |
| `--image` | resolved from `--k8s-version` | explicit node image override |
| `--cni` | `kindnet` | pod network: `kindnet` or `none` (Flannel/Calico/Cilium need kernel features missing from Apple's stock node kernel; custom kernels are on the roadmap) |
| `--cpus` | `4` | vCPUs per node VM |
| `--memory` | `4G` | memory per node VM |
| `--no-metrics` | `false` | skip metrics-server |
| `--no-storage` | `false` | skip the local-path default StorageClass |
| `--no-lb` | `false` | skip MetalLB (`type: LoadBalancer` support) |
| `--wait` | `5m` | node readiness timeout |
## How it works
kiac drives the `apple/container` CLI to boot one lightweight VM per node from the standard `kindest/node` image (systemd, containerd, kubeadm preinstalled), initializes the control plane with `kubeadm`, joins the workers over the `vmnet` network, applies the kindnet CNI, and installs metrics-server, local-path storage, and MetalLB by default. It talks only to the `apple/container` runtime and never touches the Docker socket, so it coexists with Docker Desktop, Rancher Desktop, kind, and k3d.
## Roadmap
- **Node chaos** โ `kiac stop|start node` to test real NotReady detection, eviction, and rescheduling
- **Custom node kernels** (`--kernel`) to unlock Flannel, Calico, Cilium, and eBPF
- **Persistent clusters** backed by `container machine` (WWDC26 persistent Linux environments), so a cluster survives a reboot
- **Built-in LoadBalancer controller** to replace MetalLB (node-IP allocation needs no ARP speaker)
- **HA control planes** and an ingress helper
## Contributing
Issues and PRs are welcome. New here? Look for issues labeled [`good first issue`](https://github.com/saiyam1814/kiac/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) โ they are scoped with context and acceptance criteria to be a clean first contribution.
## Credits
kiac stands on other people's work: the [`apple/container`](https://github.com/apple/container) and [Containerization](https://github.com/apple/containerization) teams at Apple built the runtime; Akihiro Suda's [`kina`](https://github.com/AkihiroSuda/kina) proved Kubernetes on `apple/container` was viable; and the node experience reuses the [`kindest/node`](https://github.com/kubernetes-sigs/kind) image from the kind project.
## License
[MIT](LICENSE)