vk-cocoon : exécuter des pods Kubernetes en MicroVM plutôt qu'en conteneurs
Un fournisseur Virtual Kubelet qui intercepte les pods et les fait tourner en machines virtuelles légères sous Cloud Hypervisor ou Firecracker. Isolation matérielle sans quitter l'API Kubernetes.
Un conteneur Linux partage le noyau de son hôte. C'est ce qui le rend léger — et c'est aussi sa limite de sécurité. Une faille d'échappement dans le noyau compromet tout ce qui tourne sur la machine.
Pour la plupart des charges internes, ce risque reste acceptable. Il cesse de l'être dès qu'on exécute du code dont on ne maîtrise pas l'origine : charge d'un client tiers, fonction fournie par un partenaire, traitement en périphérie sur un site physiquement accessible.
Le projet vk-cocoon propose une réponse élégante. Plutôt que de remplacer le runtime de conteneur, il se présente au cluster comme un nœud virtuel. Les pods qui lui sont affectés ne deviennent pas des conteneurs mais des MicroVM, chacune avec son propre noyau.
Le mécanisme du Virtual Kubelet
Virtual Kubelet est un projet de la CNCF qui simule un kubelet. Il enregistre un Node dans le cluster, l'ordonnanceur lui affecte des pods, et le fournisseur derrière fait ce qu'il veut de ces pods.
kube-scheduler
│ affecte le pod au nœud « cocoon-edge-01 »
▼
┌─────────────────────────┐
│ vk-cocoon │ se présente comme un Node
│ (Virtual Kubelet) │ traduit PodSpec → MicroVM
└────────────┬────────────┘
▼
┌─────────────────────────┐
│ Cloud Hypervisor │ un noyau par charge
│ ou Firecracker │ démarrage ~125 ms
└─────────────────────────┘L'intérêt tient à ce qui ne change pas. Vos manifestes restent des manifestes Kubernetes. kubectl logs fonctionne, kubectl exec aussi. Les sondes, les secrets, les ConfigMap : tout se comporte normalement. Seul le mode d'exécution diffère.
Installation
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: vk-cocoon
namespace: cocoon-system
spec:
selector:
matchLabels: { app: vk-cocoon }
template:
metadata:
labels: { app: vk-cocoon }
spec:
nodeSelector:
cocoon.io/hypervisor: "enabled" # nœuds avec /dev/kvm accessible
hostNetwork: true
serviceAccountName: vk-cocoon
containers:
- name: provider
image: ghcr.io/cocoonstack/vk-cocoon:v0.9.3
args:
- --provider=cocoon
- --nodename=cocoon-$(NOM_HOTE)
- --hypervisor=cloud-hypervisor
- --kernel-image=/var/lib/cocoon/vmlinux-6.12-microvm
- --rootfs-template=/var/lib/cocoon/rootfs-base.img
- --vm-boot-timeout=20s
- --klog.v=2
env:
- name: NOM_HOTE
valueFrom: { fieldRef: { fieldPath: spec.nodeName } }
- name: KUBELET_PORT
value: "10260"
securityContext:
privileged: true # accès à /dev/kvm et à la création de tap
volumeMounts:
- { name: kvm, mountPath: /dev/kvm }
- { name: cocoon, mountPath: /var/lib/cocoon }
- { name: run, mountPath: /run/cocoon }
resources:
requests: { cpu: 500m, memory: 512Mi }
volumes:
- name: kvm
hostPath: { path: /dev/kvm, type: CharDevice }
- name: cocoon
hostPath: { path: /var/lib/cocoon, type: DirectoryOrCreate }
- name: run
hostPath: { path: /run/cocoon, type: DirectoryOrCreate }Le nœud virtuel apparaît ensuite avec une souillure qui empêche l'ordonnanceur d'y placer n'importe quoi.
kubectl get nodes -l type=virtual-kubelet
# NAME STATUS ROLES VERSION
# cocoon-edge-01 Ready agent v1.31.4-vk-cocoon
kubectl describe node cocoon-edge-01 | grep -A2 Taints
# Taints: cocoon.io/microvm=true:NoScheduleAffecter une charge
Seuls les pods qui tolèrent explicitement la souillure atterrissent en MicroVM.
apiVersion: apps/v1
kind: Deployment
metadata:
name: traitement-partenaire
namespace: charges-externes
spec:
replicas: 4
selector:
matchLabels: { app: traitement-partenaire }
template:
metadata:
labels: { app: traitement-partenaire }
annotations:
cocoon.io/vcpus: "2"
cocoon.io/memory: "2048"
cocoon.io/kernel-cmdline: "quiet reboot=k panic=1 pci=off"
cocoon.io/balloon: "true" # restitution de mémoire inutilisée
spec:
nodeSelector: { type: virtual-kubelet }
tolerations:
- key: cocoon.io/microvm
operator: Equal
value: "true"
effect: NoSchedule
containers:
- name: traitement
image: registry-partenaire.io/traitement:4.1.0
resources:
requests: { cpu: "2", memory: 2Gi }
limits: { cpu: "2", memory: 2Gi } # égalité : la MicroVM est fixe
ports: [{ containerPort: 8080 }]Notez l'égalité entre demandes et limites. Une MicroVM reçoit une allocation fixe au démarrage ; la notion de dépassement contrôlé n'existe pas comme pour un cgroup. Le ballon de mémoire permet de restituer ce qui n'est pas utilisé, mais le plafond reste ferme.
Ce que l'isolation coûte réellement
| Critère | Conteneur | MicroVM Cocoon |
|---|---|---|
| Démarrage | 50 à 200 ms | 120 à 400 ms |
| Surcoût mémoire | ~2 Mio | 25 à 40 Mio (noyau invité) |
| Surcoût processeur | négligeable | 2 à 5 % |
| Entrées-sorties disque | natif | −10 à −20 % (virtio) |
| Réseau | natif | −5 à −15 % |
| Surface d'attaque | noyau partagé | hyperviseur seul |
Ces chiffres tranchent avec l'image d'une virtualisation lourde. Cloud Hypervisor démarre une MicroVM en cent vingt millisecondes parce qu'il n'émule presque rien : pas de BIOS, pas de PCI, juste virtio et un noyau taillé pour.
Le surcoût mémoire reste le point le plus tangible. Quarante mégaoctets par pod comptent quand vous en exécutez cinq cents. Sur une charge externe qui en consomme deux gigaoctets, c'est du bruit.
Les limites à connaître
Pas de partage de noyau, donc pas de fonctionnalités noyau partagées. Un pod qui a besoin de hostPID, hostNetwork ou de capacités privilégiées ne fonctionnera pas. C'est le but, mais cela exclut les agents de supervision qui inspectent l'hôte.
Les volumes persistants passent par virtio-fs ou virtio-blk. Les performances sont correctes, mais un emptyDir en mémoire se comporte différemment. Testez vos charges à forte intensité d'écriture.
Le maillage de services demande attention. Un conteneur adjacent Istio tourne dans la même MicroVM que l'application — ce qui reste cohérent. Mais l'injection automatique doit être vérifiée, certains fournisseurs ne la gèrent pas.
Le projet reste jeune. La version 0.9 n'est pas une 1.0. Testez sérieusement avant d'y mettre une charge critique.
Quand cela vaut le détour
Trois situations justifient l'investissement.
L'exécution de code tiers. Une plateforme qui fait tourner des fonctions fournies par des clients ne peut pas se contenter d'un noyau partagé.
Les environnements physiquement exposés. Un serveur en périphérie, dans une usine ou un local technique, auquel quelqu'un peut brancher un câble.
Les exigences réglementaires d'isolation. Certains référentiels imposent une séparation par hyperviseur entre traitements de niveaux de sensibilité différents. Cocoon la fournit sans quitter Kubernetes.
Pour tout le reste, un conteneur bien configuré — utilisateur non privilégié, système de fichiers en lecture seule, capacités retirées, profil seccomp — couvre l'essentiel du risque à un coût nul.