Il y a un patron en ingénierie logicielle dont on ne parle pas assez : construire une infrastructure qui ne fait rien aujourd'hui mais qui évite un refactoring douloureux plus tard. Nous venons de le faire dans sh0, et la méthodologie derrière mérite d'être documentée.
Le problème que nous n'avons pas encore
sh0 exécute actuellement tous les conteneurs avec le runtime par défaut de Docker : runc. Namespaces, cgroups, l'isolation standard des conteneurs Linux. Cela fonctionne parfaitement pour les déploiements auto-hébergés où vous faites confiance au code que vous déployez.
Mais nous construisons vers sh0 Cloud -- multi-tenant, où des inconnus déploient du code sur des serveurs partagés. Soudain, runc ne suffit plus :
- sh0 Debug permet aux utilisateurs d'exécuter du code arbitraire. Un exploit kernel = accès à l'hôte.
- sh0 Cloud signifie qu'une évasion de conteneur = données d'autres clients.
Les solutions existent : gVisor (noyau en espace utilisateur, ~10-30 % de surcoût I/O) et Kata Containers (micro-VM, ~150 ms de pénalité de démarrage). Docker supporte les deux via un simple champ Runtime dans son API.
La décision : construire l'abstraction maintenant
Une spécification est arrivée suggérant d'implémenter le support complet des runtimes de conteneurs -- vérifications préalables, flags CLI, interface dashboard, détection de disponibilité des runtimes. Le genre de spécification qu'une IA écrit quand elle ne connaît pas la base de code.
Nous l'avons réduite au minimum viable d'abstraction :
rust#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ContainerRuntime {
#[default]
Runc,
Gvisor,
KataQemu,
KataFirecracker,
}Un enum, une colonne de base de données, un champ sur la requête de l'API Docker. C'est tout. Pas de vérifications préalables (rien à vérifier -- seul runc est installé). Pas de flag CLI (l'API suffit). Pas d'interface dashboard (l'administrateur peut configurer via l'API le moment venu).
Ce que "dormant" signifie précisément
Chaque application est par défaut à Runc. Le champ HostConfigRequest.runtime est Option<String> avec skip_serializing_if = "Option::is_none". Pour runc, c'est None -- le champ n'apparaît pas du tout dans la requête de l'API Docker. Docker utilise son défaut. Le comportement est octet par octet identique à avant.
La seule façon d'activer cela est de faire PATCH /api/v1/apps/:id avec {"container_runtime": "gvisor"}. À ce moment, Docker essaiera d'utiliser runsc et échouera (parce qu'il n'est pas installé). Ce qui est le mode d'échec correct -- Docker vous dit que le runtime n'existe pas.
L'audit a détecté quelque chose
Nous suivons une méthodologie construire-auditer-auditer-approuver. Le premier auditeur (session Claude fraîche, zéro contexte de la session de build) a trouvé que 6 des 8 chemins de déploiement codaient en dur container_runtime: None au lieu de lire le paramètre de l'application :
- Déploiement d'image Docker
- Déploiement de contenu Dockerfile
- Déploiement par upload
- Déploiement rollback
- Montée en charge des réplicas
- Déploiement d'environnement de prévisualisation
Seuls les 2 chemins de déploiement Git (primaire + réplica) utilisaient correctement le runtime de l'application.
Impact actuel : zéro -- toutes les applications sont en runc, donc None et lire-depuis-l'application produisent le même résultat. Mais si nous avions activé gVisor 6 mois plus tard, 6 chemins de déploiement auraient silencieusement ignoré le paramètre. Les utilisateurs auraient configuré gvisor, déployé par upload, et obtenu runc. Le genre de bug invisible jusqu'à la production.
L'auditeur a corrigé les 6 sites. Le second auditeur a confirmé les corrections. Propre.
Le coût
- 20 fichiers touchés -- principalement mécanique (ajout de
container_runtime: Default::default()aux initialiseurs de structs App,container_runtime: Noneaux sites d'appel Sh0ContainerParams) - 1 migration --
ALTER TABLE apps ADD COLUMN container_runtime TEXT NOT NULL DEFAULT 'runc' - ~60 lignes de logique réelle -- l'enum, Display/FromStr, 2 patterns de match dans le pipeline de déploiement
- Temps de construction : environ 2 heures incluant les deux tours d'audit
- Coût d'exécution : zéro (le champ ne se sérialise pas pour runc)
À quoi ressemble l'activation
Quand nous installerons gVisor sur un serveur :
bash# Sur le serveur sh0
apt install runsc
# Dans l'API sh0
PATCH /api/v1/apps/:id {"container_runtime": "gvisor"}
# Le prochain déploiement utilise runsc automatiquementAucun changement de code. Aucune migration. Aucune nouvelle release. L'infrastructure est déjà en place.
Le patron
Le cadre de décision est simple :
- L'abstraction est-elle peu coûteuse ? (Oui -- un enum et une colonne de base de données)
- Le point d'intégration est-il clair ? (Oui -- le champ
Runtimede Docker dans HostConfig) - Cela change-t-il le comportement actuel ? (Non --
None= défaut Docker = runc) - La rétro-intégration serait-elle douloureuse ? (Oui -- toucher chaque chemin de déploiement, migration, type API, handler)
Si les quatre conditions sont remplies : construire maintenant, activer plus tard. La méthodologie d'audit attrape les bugs d'intégration que vous découvririez autrement 6 mois plus tard en production.