Kairos et vk-cocoon : une flotte de clusters en périphérie, isolée jusqu'au noyau
Kairos tient l'hôte physique en système immuable, vk-cocoon isole chaque charge en MicroVM. Deux couches complémentaires pour exécuter du code non maîtrisé sur des sites que personne ne garde.
Les deux articles précédents traitaient chacun une moitié du problème. Kairos résout la gestion d'un parc physique distribué. vk-cocoon résout l'isolation des charges. Les combiner produit une architecture qui répond à un cas précis et de plus en plus fréquent : exécuter du code dont on ne maîtrise pas l'origine, sur des machines que personne ne surveille physiquement.
C'est la situation d'un opérateur qui héberge les traitements de ses clients en périphérie, d'un industriel qui accueille les modules logiciels de ses fournisseurs sur ses sites, ou d'une administration qui déporte des traitements sur des emprises accessibles.
La répartition des rôles
┌────────────────────────────────────────────────────────────┐
│ 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é │ │
│ │ • lance K3s et rejoint le maillage du site │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ K3s — cluster autonome du site │ │ │
│ │ │ │ │ │
│ │ │ Charges internes ──▶ conteneurs standard │ │ │
│ │ │ │ │ │
│ │ │ Charges tierces ──▶ vk-cocoon │ │ │
│ │ │ └─▶ MicroVM │ │ │
│ │ │ noyau dédié │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
│ GitOps sortant uniquement
▼
Plan de contrôle central (Fleet / ArgoCD)Chaque couche répond à une menace distincte.
| Menace | Couche qui répond | Mécanisme |
|---|---|---|
| Altération du système | Kairos | Racine en lecture seule, image signée, démarrage vérifié |
| Vol physique de la machine | Kairos | Chiffrement du disque scellé au TPM |
| Échappement d'un conteneur | vk-cocoon | Noyau distinct par charge |
| Latéralisation entre charges | vk-cocoon + politiques réseau | Isolation hyperviseur et segmentation |
| Compromission du plan de contrôle | Architecture | Le site ne fait que sortir, jamais entrer |
Préparer l'hôte dans l'image
L'hyperviseur, le noyau invité et le système de fichiers de base se placent dans l'image Kairos. Rien ne s'installe au démarrage.
FROM quay.io/kairos/ubuntu:24.04-standard-amd64-generic-v3.6.0-k3sv1.31.4-k3s1
# Hyperviseur et outillage MicroVM
RUN apt-get update && apt-get install -y --no-install-recommends cloud-hypervisor qemu-utils iproute2 && rm -rf /var/lib/apt/lists/*
# Noyau invité minimal, compilé pour le démarrage rapide
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
# Le groupe kvm existe avant le premier démarrage
RUN groupadd -f -g 993 kvm
# Modules chargés à l'amorçage
RUN printf 'kvm
kvm_intel
vhost_net
tun
' > /etc/modules-load.d/cocoon.conf
# Durcissement : pas de ptrace inter-processus, pas de BPF non privilégié
COPY durcissement/99-cocoon.conf /etc/sysctl.d/L'image pèse environ 1,6 Gio. C'est le prix de l'autonomie : une machine qui démarre est immédiatement capable d'exécuter des MicroVM, sans téléchargement ni configuration.
La configuration du site
#cloud-config
install:
device: "auto"
auto: true
reboot: true
image: "oci:registry.interne/edge/socle-cocoon:v1.4.0"
encrypted_partitions: ["COS_PERSISTENT"] # scellé au TPM 2.0
kairos:
network_token: "{{ jeton_maillage_site }}"
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 chiffrés au repos dans 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 indisponible — vérifier la virtualisation dans le BIOS"
exit 1
fi
after-install:
- name: "Souiller le nœud virtuel"
commands:
- kubectl taint node cocoon-$(hostname) cocoon.io/microvm=true:NoSchedule --overwriteLe chiffrement scellé au TPM mérite qu'on s'y arrête. La clé de déchiffrement ne se libère que si la chaîne de démarrage correspond à l'état mesuré attendu. Un disque retiré de la machine reste illisible ; un système modifié ne démarre pas.
Isoler le réseau des charges tierces
L'isolation par hyperviseur ne protège pas du réseau. Une MicroVM compromise qui atteint l'API Kubernetes ou le réseau industriel annule le bénéfice.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: cloisonnement-charges-tierces
namespace: charges-externes
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
ingress:
- from:
- namespaceSelector:
matchLabels: { role: passerelle-entree }
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 }]
# Collecteur de résultats, rien d'autre
- to:
- podSelector: { matchLabels: { app: collecteur-resultats } }
ports: [{ protocol: TCP, port: 9443 }]
# Tout le reste est refusé : ni API Kubernetes, ni réseau industriel, ni internetLa politique se complète d'une règle Kyverno qui refuse tout pod de cet espace de noms ne tolérant pas la souillure Cocoon.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: charges-tierces-en-microvm
spec:
validationFailureAction: Enforce
background: false
rules:
- name: exiger-tolerance-cocoon
match:
any:
- resources:
kinds: [Pod]
namespaces: ["charges-externes", "charges-partenaires"]
validate:
message: "Toute charge 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"Sans cette règle, un manifeste sans tolérance tomberait sur un nœud ordinaire et s'exécuterait en conteneur classique — exactement ce qu'on cherchait à empêcher. La politique déclarative ferme la porte que l'ordonnanceur laisse ouverte.
Le pilotage de la flotte
Chaque site porte un cluster K3s autonome. Rancher Fleet les réconcilie depuis le centre, en tirant la configuration plutôt qu'en la poussant.
apiVersion: fleet.cattle.io/v1alpha1
kind: GitRepo
metadata:
name: socle-peripherie
namespace: fleet-default
spec:
repo: https://gitlab.interne/edge/socle-peripherie.git
branch: main
paths: ["socle/", "politiques/"]
targets:
- name: canari
clusterSelector:
matchLabels: { canal-maj: canari }
- name: stable
clusterSelector:
matchLabels: { canal-maj: stable }
correctDrift:
enabled: true # toute modification locale est annuléeLe sens du flux est structurant. Les sites tirent leur configuration ; le centre n'ouvre aucune connexion entrante. Un site compromis ne donne aucun accès au plan de contrôle ni aux autres sites.
Ce que l'empilement coûte
| Poste | Surcoût |
|---|---|
| Mémoire par charge tierce | 25 à 40 Mio (noyau invité) |
| Démarrage d'une charge | +100 à 250 ms |
| Poids de l'image système | 1,6 Gio au lieu de 1,2 Gio |
| Processeur | 2 à 5 % |
| Complexité d'exploitation | deux couches à maîtriser |
Sur un site de quarante charges tierces, le surcoût mémoire atteint environ 1,6 Gio. Sur un serveur de 128 Gio, c'est marginal. Sur un boîtier de 8 Gio en périphérie contrainte, cela compte.
Quand ne pas le faire
Cette architecture est justifiée lorsque vous exécutez du code tiers sur du matériel physiquement exposé. Dans tous les autres cas, elle ajoute de la complexité sans contrepartie.
Si vos charges sont toutes internes, Kairos seul apporte déjà l'essentiel : immuabilité, montée de version atomique, fin de la dérive de configuration. La couche MicroVM ne se justifie que face à une menace d'échappement réelle.
Si vos machines se trouvent dans un centre de données gardé, la question du vol physique ne se pose pas, et le chiffrement scellé au TPM perd son intérêt principal.
Le critère de décision. Posez-vous une seule question : si l'une de ces charges s'échappe de son conteneur, qu'atteint-elle ? Si la réponse est « le noyau de l'hôte, puis les autres charges du site », l'empilement se justifie. Si la réponse est « un environnement de test sans données », il ne se justifie pas.