Kairos and vk-cocoon: a fleet of edge clusters, isolated down to the kernel
Kairos holds the physical host as an immutable system, vk-cocoon isolates every workload in a MicroVM. Two complementary layers for running untrusted code on sites nobody guards.
The two previous articles each dealt with half the problem. Kairos solves the management of a distributed physical fleet. vk-cocoon solves workload isolation. Combining them produces an architecture that answers one precise and increasingly common case: running code whose origin you do not control, on machines nobody watches physically.
That is the situation of an operator hosting customer processing at the edge, of a manufacturer welcoming supplier software modules onto its sites, or of a public body pushing processing out to accessible premises.
How the roles divide
┌────────────────────────────────────────────────────────────┐
│ SITE PHYSIQUE — local technique, usine, emprise │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Kairos — système immuable, image OCI signée │ │
│ │ • racine en lecture seule, deux partitions │ │
│ │ • démarrage vérifié, TPM, disque chiffré │ │
│ │ • starts K3s and joins the site mesh │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ K3s — the site autonomous cluster │ │ │
│ │ │ │ │ │
│ │ │ Charges internes ──▶ conteneurs standard │ │ │
│ │ │ │ │ │
│ │ │ Charges third-party ──▶ vk-cocoon │ │ │
│ │ │ └─▶ MicroVM │ │ │
│ │ │ noyau dédié │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
│ GitOps sortant uniquement
▼
Plan de contrôle central (Fleet / ArgoCD)Each layer answers a distinct threat.
| Threat | Layer that answers | Mechanism |
|---|---|---|
| System tampering | Kairos | Read-only root, signed image, verified boot |
| Physical theft of the machine | Kairos | Disk encryption sealed to the TPM |
| Container escape | vk-cocoon | A separate kernel per workload |
| Lateral movement between workloads | vk-cocoon + network policies | Hypervisor isolation and segmentation |
| Control-plane compromise | Architecture | The site only calls out, never in |
Preparing the host inside the image
The hypervisor, the guest kernel and the base file system all go into the Kairos image. Nothing gets installed at boot.
FROM quay.io/kairos/ubuntu:24.04-standard-amd64-generic-v3.6.0-k3sv1.31.4-k3s1
# Hypervisor and MicroVM tooling
RUN apt-get update && apt-get install -y --no-install-recommends cloud-hypervisor qemu-utils iproute2 && rm -rf /var/lib/apt/lists/*
# Minimal guest kernel, built for fast boot
COPY --chmod=0644 vmlinux-6.12-microvm /var/lib/cocoon/vmlinux-6.12-microvm
COPY --chmod=0644 rootfs-base.img /var/lib/cocoon/rootfs-base.img
# The kvm group exists before the first boot
RUN groupadd -f -g 993 kvm
# Modules loaded at boot
RUN printf 'kvm
kvm_intel
vhost_net
tun
' > /etc/modules-load.d/cocoon.conf
# Hardening: no cross-process ptrace, no unprivileged BPF
COPY durcissement/99-cocoon.conf /etc/sysctl.d/The image weighs around 1.6 GiB. That is the price of autonomy: a machine that boots is immediately able to run MicroVMs, with no download and no configuration.
The site configuration
#cloud-config
install:
device: "auto"
auto: true
reboot: true
image: "oci:registry.interne/edge/platform-cocoon:v1.4.0"
encrypted_partitions: ["COS_PERSISTENT"] # sealed to the TPM 2.0
kairos:
network_token: "{{ site_mesh_token }}"
k3s:
enabled: true
args:
- --disable=traefik
- --disable=servicelb
- --flannel-backend=wireguard-native
- --node-label=site={{ code_site }}
- --node-label=cocoon.io/hypervisor=enabled
- --secrets-encryption # secrets encrypted at rest in etcd
stages:
boot:
- name: "Horloge"
commands: ["chronyc waitsync 30 0.1 || true"]
- name: "Vérifier KVM"
commands:
- |
if [ ! -c /dev/kvm ]; then
logger -t cocoon "KVM unavailable — check virtualisation in the BIOS"
exit 1
fi
after-install:
- name: "Taint the virtual node"
commands:
- kubectl taint node cocoon-$(hostname) cocoon.io/microvm=true:NoSchedule --overwriteEncryption sealed to the TPM deserves a pause. The decryption key is released only if the boot chain matches the expected measured state. A disk pulled out of the machine stays unreadable; a modified system does not boot.
Isolating third-party workloads on the network
Hypervisor isolation offers no protection on the network. One compromised MicroVM that reaches the Kubernetes API or the industrial network cancels the benefit.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: cloisonnement-payloads-third-party
namespace: payloads-externes
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
ingress:
- from:
- namespaceSelector:
matchLabels: { role: passerelle-entry }
ports: [{ protocol: TCP, port: 8080 }]
egress:
# Résolution DNS interne seulement
- to:
- namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: kube-system } }
podSelector: { matchLabels: { k8s-app: kube-dns } }
ports: [{ protocol: UDP, port: 53 }]
# Result collector, nothing else
- to:
- podSelector: { matchLabels: { app: collecteur-results } }
ports: [{ protocol: TCP, port: 9443 }]
# Everything else is denied: no Kubernetes API, no industrial network, no internetThe policy gets completed by a Kyverno rule that rejects any pod in that namespace which does not tolerate the Cocoon taint.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: payloads-third-party-en-microvm
spec:
validationFailureAction: Enforce
background: false
rules:
- name: exiger-tolerance-cocoon
match:
any:
- resources:
kinds: [Pod]
namespaces: ["payloads-externes", "payloads-partenaires"]
validate:
message: "Toute payload de cet espace doit s'exécuter en MicroVM Cocoon."
pattern:
spec:
tolerations:
- key: cocoon.io/microvm
operator: Equal
value: "true"
effect: NoSchedule
=(hostNetwork): "false"
=(hostPID): "false"
=(hostIPC): "false"Without that rule, a manifest with no toleration would land on an ordinary node and run as a classic container — exactly what we set out to prevent. The declarative policy closes the door the scheduler leaves open.
Running the fleet
Every site carries an autonomous K3s cluster. Rancher Fleet reconciles them from the centre, pulling the configuration rather than pushing it.
apiVersion: fleet.cattle.io/v1alpha1
kind: GitRepo
metadata:
name: platform-edge
namespace: fleet-default
spec:
repo: https://gitlab.interne/edge/platform-edge.git
branch: main
paths: ["platform/", "politiques/"]
targets:
- name: canari
clusterSelector:
matchLabels: { canal-maj: canari }
- name: stable
clusterSelector:
matchLabels: { canal-maj: stable }
correctDrift:
enabled: true # any local change gets revertedThe direction of flow is structural. Sites pull their configuration; the centre opens no inbound connection. A compromised site grants no access to the control plane nor to the other sites.
What the stack costs
| Item | Overhead |
|---|---|
| Memory per third-party workload | 25 to 40 MiB (guest kernel) |
| Workload start-up | +100 to 250 ms |
| System image weight | 1.6 GiB instead of 1.2 GiB |
| CPU | 2 to 5 % |
| Operational complexity | two layers to master |
On a site running forty third-party workloads, the memory overhead reaches about 1.6 GiB. On a 128 GiB server that is marginal. On an 8 GiB box at the constrained edge, it counts.
When not to do it
This architecture is justified when you run third-party code on physically exposed hardware. In every other case, it adds complexity for nothing in return.
If all your workloads are internal, Kairos alone already brings the essentials: immutability, atomic upgrade, the end of configuration drift. The MicroVM layer only earns its place against a real escape threat.
If your machines sit in a guarded data centre, physical theft is not a question, and TPM-sealed encryption loses its main purpose.
The decision criterion. Ask yourself one question: if one of these workloads escapes its container, what does it reach? If the answer is "the host kernel, then the other workloads on the site", the stack is justified. If the answer is "a test environment with no data", it is not.