Blog
Moet elke tenant een eigen VM krijgen?
Kies tussen per-tenant containers, VM's en hybride opstellingen met een risicogebaseerd beslissingskader en de hardening-stappen die elke optie verdedigbaar maken.
Samenvatting
Multi-tenant hosting dwingt je om te kiezen hoe ver tenants in elkaar kunnen reiken. Containers gebruiken Linux-namespaces en cgroups om processen en resources te isoleren, maar ze delen de hostkernel. Virtuele machines voegen een grens op hardwareniveau toe, ten koste van snelheid en operationele belasting. Een hybride aanpak—containers in VM's—kan je beide geven, maar verdubbelt het oppervlak dat je moet patchen. Dit artikel leidt je door een risicogebaseerde beslissing, een zij-aan-zij-vergelijking en de Docker-hardeningstappen die er zelfs binnen een VM toe doen. Aan het einde weet je welk isolatiemodel bij jouw tenants past en wat je vóór de lancering moet configureren.
Je multi-tenant app is bijna klaar. Je hebt een Docker Compose-bestand dat per klant een stack opzet, en het is snel. Dan vraagt een vriend die een hostingbedrijf runt: 'Geef je elke tenant zijn eigen VM?' Je bevriest. Op die vraag had je niet gerekend. Dit artikel geeft je een manier om hem vandaag te beantwoorden, zonder een securityteam. Je doet dit alleen, dus de beslissing moet eenvoudig genoeg zijn om om 2 uur 's nachts te verdedigen.
Stop met zoeken naar het 'beste' model. Begin met opschrijven wat er gebeurt als de code van een tenant je host overneemt. Definieer de blast radius voordat je een tool kiest. Die oefening vertelt je meer dan welke benchmark dan ook.
De kernel is de huisgenoot die je niet kunt uitzetten
Containers zijn efficiënt omdat ze de hostkernel delen. Dat delen is de hele truc, en het hele risico. Linux-namespaces geven elke container een eigen beeld van processen, netwerk en bestandssysteem. Control groups (cgroups) laten je CPU, geheugen en schijf-I/O beperken, zodat de ene tenant de andere niet kan uithongeren. Maar geen van beide creëert een hardwaremuur.
Zie een container als een proces met een heel goed nep-ID. Het gelooft dat het op een eigen machine draait. De kernel is echter één kopie van Linux die op je host draait. Als een tenant een kernelkwetsbaarheid exploiteert, worden namespaces metadata en niets meer. Een aanvaller die kernelfuncties kan aanroepen, kan andere namespaces op dezelfde kernel bereiken. Dat is de container-escape waar je steeds over hoort.
Stel dat je een kleine B2B-tool host met één container per klant. Een klant installeert een verdachte plugin met een bug voor externe code-uitvoering. Met de standaard Docker-instellingen draait dat proces als root in de container. Root in een container is nog steeds UID 0, en de kernel maakt geen onderscheid tussen die UID en host-root, tenzij je gebruikers expliciet mapped. De aanvaller kan proberen uit te breken, en de gedeelde kernel is hun doelwit.
De fout hoeft niet dramatisch te zijn. Een enkele tenant die geheugen lekt, kan de host naar swap duwen, waardoor elke andere tenant trager wordt. Zonder cgroup-limieten is één onhandelbare loop een beschikbaarheidsaanval. Met limieten is het een geblokkeerd proces en een melding.
Betekent dit dat containers onveilig zijn? Nee. Het betekent dat je de kernel moet behandelen als een gedeelde vertrouwenszone. Voordat je kiest, schrijf je een risicoverklaring van één alinea: 'Als de container van een tenant wordt gecompromitteerd, heeft de aanvaller toegang tot: [lijst]. De zakelijke kosten zouden zijn: [bedrag of impact].' Als die alinea je bang maakt, ben je niet paranoïde. Je bent eerlijk.
Voor een diepere blik op het isolatiespectrum, van gedeelde containers tot volledig gescheiden stacks, zie onze gids over het ontwerpen van een multi-tenant Docker-architectuur.
Drie manieren om het op te delen (kies er één vóór je uitrolt)
Er zijn eigenlijk drie architecturen voor multi-tenant isolatie. Elke 'best practice' is een combinatie hiervan.
| Aanpak | Isolatiebarrière | Het beste wanneer | Grootste valkuil |
|---|---|---|---|
| Per-tenant containers | Kernel-namespaces + cgroups | Veel kleine tenants, laag risico per tenant, behoefte aan dichtheid | Eén kernel-exploit kan elke tenant op die host breken |
| Eén VM per tenant | Hypervisor/hardwarevirtualisatie | Gereguleerde gegevens, vijandige tenants, hoge waarde per tenant | Zwaarder, langzamer te provisionen, je patcht een OS per tenant |
| Containers in VM's | VM-grens rond gecontaineriseerde workloads | Dichtheid plus een harde schil tussen groepen | Kosten en operationele overhead bijna verdubbeld |
Per-tenant containers. Dit is de standaard voor de meeste SaaS-oprichters. Elke tenant krijgt zijn eigen container of kleine Compose-stack. Provisioning is direct, images zijn klein, CI/CD is eenvoudig. Resourcelimieten voorkomen dat lawaaierige buren de server opeten. De afweging is de gedeelde kernel. Als je workloads niet-geprivilegieerd kunt houden en de host regelmatig kunt patchen, is dit vaak de juiste eerste stap.
Zet niet twee tenants in dezelfde container. Dat is een gedeelde kernel plus een gedeelde runtime plus een gedeeld bestandssysteem. Als één tenant een bestand uploadt dat een proces creëert, bevindt de andere tenant zich al in dezelfde procestabel. Een container is je isolatie-eenheid; maak er één tenant per container van.
En de database? Als elke tenant verbinding maakt met één MongoDB- of PostgreSQL-instantie met dezelfde referenties, heb je al een enorme gedeelde component toegevoegd. Geef elke tenant aparte referenties, en idealiter een aparte database of schema. Containers isoleren de app; de database is vaak het eerste lek dat een aanvaller zal testen.
Eén VM per tenant. Geef elke tenant een volledige virtuele machine. De hypervisor voegt een grens op hardwareniveau toe, precies wat een kernel-exploit moet oversteken om de host te bereiken. Dit is belangrijk voor gereguleerde omgevingen of wanneer tenants niet worden vertrouwd. De kosten zijn dichtheid en tijd. Je beheert nu een vloot besturingssystemen, niet alleen containers. Elke VM heeft updates, beveiligingsagenten en monitoring nodig. Voor een solo-oprichter is dat echt werk.
Patronen die op dit niveau werken: gebruik infrastructure-as-code om een VM te maken vanuit dezelfde basisimage, bak updates in nieuwe images in in plaats van live systemen te patchen, en beëindig workloads die je niet herkent. Houd de beheerpoort van de VM gesloten voor internet.
Containers in VM's. Deze hybride wordt zelden besproken in beginnerstutorials. Je plaatst een kleine VM rond elke tenant (of kleine groep tenants) en draait daar containers in. De VM is een blast-radius-container; de containers zijn slechts implementeerbare eenheden. Dit geeft je de harde rand van virtualisatie en de reproduceerbaarheid van images. Het kost meer, omdat je betaalt voor virtualisatie-overhead en containerflexibiliteit, maar het kan het meest verstandige langetermijnmodel zijn wanneer je tenants niet volledig kunt vertrouwen.
Een veelvoorkomend micro-voorbeeld: een tenant draait een Node-API en een achtergrondwerker. Gebruik in plaats van één grote container met beide processen één VM en daarna twee containers met verschillende resourcelimieten, een gedeeld netwerk en geen directe internetblootstelling voor de werker. De VM biedt de harde rand; de containers bieden structuur.
Welke moet je kiezen? De tabel is je shortlist. De volgende secties maken de beslissing concreet.
Als je voor containers kiest, doe deze zes dingen of doe geen moeite
Per-tenant containers zijn prima als je elke container behandelt als een potentiële aanvaller. Dat begint met configuratie, niet met wensdenken.
0. Beperk resources voordat je iemand vertrouwt. Cgroups zijn een eerlijkheidsmechanisme en een beschikbaarheidsverdediging. Stel --memory en --cpus per container in. Een tenant die geheugen lekt, moet zijn eigen limiet raken, niet die van je server. Dit is geen beveiligingsgrens, maar een lawaaierige buur is een aanval zonder een regel code. Een praktische start: --memory 512m --cpus 0.5. Begin voor een worker-proces lager en schaal op.
1. Draai als niet-root-gebruiker. Laat het containerproces nooit UID 0 gebruiken tenzij je het absoluut nodig hebt. Stel een gebruiker in in de Dockerfile en geef --user door als extra beveiliging. Een exploit die draait als niet-geprivilegieerde gebruiker heeft veel minder paden naar de kernel. Maak in je Dockerfile een gebruiker: RUN useradd -u 10001 app en USER app. Sla dit niet over om tijd te besparen.
2. Laat elke capability vallen die je niet nodig hebt. Linux-capabilities splitsen de macht van root in kleine stukjes. De meeste webapps hebben bijna niets nodig. Begin met --cap-drop=ALL en voeg alleen toe wat je weet dat je nodig hebt. Een container zonder CAP_SYS_ADMIN is veel moeilijker te gebruiken voor namespace-trucs. Als je app een geprivilegieerde poort probeert te binden, draai hem dan op een hoge poort en plaats een proxy ervoor in plaats van NET_BIND_SERVICE toe te staan.
3. Maak het bestandssysteem alleen-lezen. Je app moet niet naar zijn eigen containerlaag schrijven. Mount een tmpfs voor status. Een aanvaller die niet naar schijf kan schrijven, heeft veel meer moeite om persistentie te planten. Een gecompromitteerde PHP-app die een webshell probeert te schrijven, zal falen wanneer het rootbestandssysteem alleen-lezen is. Je kunt een named volume mounten voor een beschrijfbare map die je app echt nodig heeft.
4. Pas seccomp en AppArmor of SELinux toe. Deze sturen risicovolle syscalls naar de prullenbak. Docker levert een standaard seccomp-profiel; gebruik het. Voeg een AppArmor-profiel toe voor een extra laag. Je hoeft niet elke syscall te beheersen. Je moet ontkennen wat een normale webworker nooit nodig heeft. Draai nooit met --privileged. Die vlag schakelt bijna elke verdediging uit die je net hebt ingesteld.
5. Segmenteer het netwerk. Geef niet elke container een route naar elke andere container. Default deny, open dan alleen de poorten die je nodig hebt. Een gecompromitteerde databasecontainer mag je adminpaneel niet kunnen scannen. Als tenants in afzonderlijke netwerken zitten, kan een inbreuk in het ene netwerk zich niet lateraal verspreiden.
Een praktische start:
docker run --user 10001 --cap-drop=ALL --security-opt no-new-privileges --read-only --tmpfs /tmp:rw,size=64M --security-opt seccomp=default.json --memory 512m --cpus 0.5 myimage
Zet dezelfde vlaggen in een Compose-bestand en pas ze toe op elke tenant. Dit is niet compleet, maar het is een veel sterker standaard dan wat docker run je out-of-the-box geeft.
Voor een diepere walkthrough gebruik je onze stapsgewijze hardeninggids voor Docker-containers in multi-tenant hosting.
Docker's Enhanced Container Isolation is de uitzondering die je moet kennen
Als je draait in een beheerde Docker-omgeving, zoek dan naar Docker's Enhanced Container Isolation (ECI). Het gebruikt user namespace-isolatie en een beveiligde container-runtime onder de motorkap. Root in een container wordt gemapt naar een niet-geprivilegieerde gebruiker op de host, dus zelfs een container die als root draait, krijgt geen host-rootprivileges. Het blokkeert ook standaard gevaarlijke capabilities en syscalls. Dit kun je niet recreëren met een paar vlaggen op vanilla Docker. Als je platform het ondersteunt, zet het dan aan. Het verwijdert de noodzaak voor niet-rootgebruikers en resourcelimieten niet, maar het verandert de risicoberekening.
Je kunt een deel hiervan benaderen met user namespace-remapping (userns-remap) in de Docker-daemon. Dat is niet zo compleet als een beveiligde runtime, maar het is beter dan niets. Als je het gebruikt, verifieer dan dat de UID-mapping werkt voordat je het vertrouwt.
De VM-drogreden: overstappen op virtuele machines is geen hardening
Hier is het tegendraadse deel, en het is het deel dat de meeste mensen overslaan. Als je overstapt naar één VM per tenant en vervolgens je normale containers daarin implementeert, heb je je containerbeveiligingsprobleem niet opgelost. Je hebt een wijde kooi toegevoegd. De container-escape werkt nog steeds; de aanvaller landt gewoon in de VM in plaats van op de host. Dat is een echte verbetering, maar je hebt nog steeds de zes stappen nodig.
De andere valkuil is aannemen dat de VM zelf veilig is. Een standaardimage met een zwak SSH-wachtwoord, ongepatchte basispakketten of een open beheerpoort is een geschenk. De hypervisorgrens doet er alleen toe als de gast is gehard en bijgewerkt. Anders is je 'beveiligde VM' een snellere weg naar compromittering omdat je je veilig voelt en stopt met controleren.
Wat een VM je oplevert, is reduceerbare blast radius. De ramp van één tenant blijft in één VM. Wat het je kost, is je tijd. Je wordt de systeembeheerder voor net zoveel besturingssystemen als je tenants hebt. Als je een solo-oprichter bent die een product uitbrengt, vraag je dan af of je de uren hebt om een vloot te patchen en te monitoren. Zo ja, dan is een VM per tenant de juiste keuze. Zo nee, dan zijn containers met sterke hardening misschien eerlijker.
Onthoud ook dat je hypervisor-host een kritiek doelwit is. Een gecompromitteerde hypervisor kan alle gasten zien. Patch de host, niet alleen de gasten. De VM ontslaat je niet van host-patching; het verhoogt de inzet als je het mist.
Een kanttekening bij de hybride: ga er niet vanuit dat containers in een VM je 'twee beveiligingslagen' gratis geven. De VM voegt een grens toe; de container heeft nog steeds niet-root, capabilities en seccomp nodig. Anders is de eerste laag slechts zo sterk als de zwakste container.
Vier vragen die het debat in tien minuten beslechten
Optimaliseer niet in het abstracte. Stel jezelf deze vier vragen in volgorde. Schrijf de antwoorden op.
1. Waar heeft mijn tenant toegang toe? Als een tenant alleen zijn eigen webapp en database kan bereiken, zijn per-tenant containers met strikte netwerkregels verdedigbaar. Als de gegevens van een tenant gereguleerd of financieel gevoelig zijn, ga dan richting VM's.
2. Hoeveel zou de compromittering van één tenant mij kosten? Tel verloren klanten, juridische blootstelling en vertrouwen op. Als het getal groter is dan de kosten van het draaien van VM's, geef het geld dan uit. Zo niet, dan zijn containers een rationele keuze.
3. Hoeveel tenants heb ik en hoeveel betalen ze? Veel kleine abonnees: containerdichtheid is belangrijk. Een handvol grote accounts: geef elke een VM en factureer dienovereenkomstig. Tenants die je minder dan een kop koffie betalen, zouden niet elk een besturingssysteem moeten vereisen om te beheren.
4. Kan ik dingen op schema patchen? Containers delen één hostkernel, dus het patchen van de host beschermt iedereen. VM's vermenigvuldigen je patchdoelen. Als je weet dat je updates zult overslaan, kies dan de architectuur met minder bewegende delen en hardere standaardinstellingen.
Je antwoorden zullen clusteren. Twee of meer VM-gerichte antwoorden betekenen dat je niet standaard per-tenant containers moet gebruiken. Drie of meer container-gerichte antwoorden betekenen dat VM's voorbarig zijn. Eén contra-intuïtief resultaat: een low-revenue tenant met toegang tot gevoelige gegevens heeft nog steeds de VM nodig, omdat de regelgevingskosten niets te maken hebben met hoeveel ze betalen.
Lever het minste dat je kunt vertrouwen en verdien dan meer isolatie
Je eerste architectuur hoeft niet je definitieve te zijn. Begin met de strakste opstelling die je daadwerkelijk kunt onderhouden en voeg dan isolatie toe zodra je tenantbasis dit rechtvaardigt. Voor de meeste solo-operators betekent dat per-tenant containers met niet-root, afgedekte capabilities, alleen-lezen bestandssystemen, seccomp en netwerksegmentatie. Voor gereguleerde of hoogwaardige tenants ga je direct naar één VM per tenant, met containers alleen als verpakkingslaag erin.
Wat je ook kiest, schrijf de beslissing op en bekijk hem elk kwartaal opnieuw. Wanneer je je eerste vraag 'moeten we deze tenant naar een VM verhuizen?' krijgt, heb je een antwoord en heb je de checklist om het te ondersteunen. Dat is wat isolatie werkelijk betekent: een afweging die je beheert, geen technologie die je koopt.
Vóór de lancering doorloop je onze praktische Docker-isolatiebeveiligingschecklist—het zet deze beslissingen om in een lijst die je kunt controleren voordat je een pagina aan een klant laat zien.

