Blog

Welke klanten hebben echt hun eigen VM nodig? Een gelaagd Docker-isolatieplan

Een VM voor elke klant is overdreven. Zo bepaal je hoeveel isolatie elke tenant nodig heeft—en automatiseer je de beslissing.

Samenvatting

Bureaus raken vaak in paniek wanneer een klant vraagt hoe geïsoleerd hun data werkelijk is van andere tenants. Docker's namespaces en cgroups bieden echte isolatie, maar ze zijn niet hetzelfde als een hardwaregrens. In plaats van elke klant op een VM te draaien—of erger, elke klant hetzelfde te behandelen—bouw je een kleine set isolatietiers en koppel je elke klant aan één op basis van datagevoeligheid, vertrouwen en compliance. Een afgesloten container (non-root, capabilities verwijderd, seccomp, read-only root) dekt de meeste sites; gereguleerde of vijandige workloads krijgen een VM of een container-in-VM-hybride. Dit bericht biedt een herhaalbare beslisstroom, een vergelijkingstabel en een eerlijke blik op wanneer meer isolatie overkill is.

Ben je op het punt in een verkoopgesprek waar de nieuwe klant zegt “we zijn in de gezondheidszorg, laat me zien dat onze data geïsoleerd is van je andere klanten” en je liever over iets anders praat?

Dit is het bureauprobleem: niet één perfecte implementatie, maar dezelfde betrouwbare implementatie herhaald bij een tiental klanten met verschillende budgetten, risicoprofielen en compliance-eisen. Hier is de eerlijke versie. Docker-isolatie is reëel, maar het is specifiek. Namespaces geven elke container zijn eigen weergave van processen, netwerk en bestandssysteem; cgroups begrenzen CPU, geheugen en schijf-I/O zodat tenants elkaar niet kunnen uithongeren. Wat je daarmee niet krijgt, is een hardwaremuur tussen de container en de hostkernel. Als een aanvaller uit de container ontsnapt, zitten ze in de enige kernel die je hebt. De rest van dit artikel verandert dat ongemakkelijke feit in een herhaalbare beslissing: classificeer elke klant op basis van datagevoeligheid en vertrouwen, pas een basis-hardeningprofiel toe, en grijp alleen naar een VM wanneer de kosten van een datalek hoger zijn dan de kosten van de VM.

Wacht, zijn containers niet al geïsoleerd?

Docker draait op Linux-namespaces en cgroups, en die woorden doen echt werk. Namespaces scheiden proces-ID's, netwerkstacks, mountpoints en gebruikers, zodat een proces in de ene container de procestabel van een andere niet kan zien. Cgroups stellen limieten: geef een container 0,5 CPU, 512 MB geheugen en een vast blok-I/O-gewicht, en dat is precies wat hij krijgt. Een op hol geslagen loop in de ene tenant wordt afgeremd in plaats van de buurman plat te leggen. Als je geen limieten hebt geconfigureerd, heb je het meest basale doel van cgroups overgeslagen.

Neem een simpele PHP-app in container A. Die ziet zijn eigen bestandssysteem, zijn eigen netwerkinterface, zijn eigen PID 1. Container B heeft hetzelfde, maar een ander beeld. Dat zijn namespaces. Loop nu weg en sla de geheugenlimiet over: container A kan het RAM-geheugen van de host vullen en container B laten kruipen. Dat is wat cgroups moeten voorkomen. Maar twee containers kunnen door namespaces van elkaar geïsoleerd zijn en toch de hostkernel delen, en dat is het deel waar elk container-ontsnappingsverhaal over gaat. Een exploit die de kernel bereikt, kan mogelijk elke tenant op die host bereiken.

“Docker is geïsoleerd” is een halve waarheid. De juiste versie is “Docker isoleert met namespaces en cgroups, en een kernelkwetsbaarheid is de blast radius.” Voordat je een tenant vertrouwt met het draaien van onvertrouwde code, sta daar even bij stil. Het antwoord is niet “gebruik nooit containers”—dat is de makkelijke paniek. Het antwoord is een tiersysteem.

Waarom hebben sommige klanten meer nodig dan namespaces?

Het eerlijke antwoord is dat isolatie geen schakelaar is, maar een spectrum. Aan het ene uiteinde heb je een volledig gedeelde container waar iedereen feitelijk in één app zit. Aan het andere uiteinde heb je een aparte VM per tenant met een eigen kernel. Het meeste bureauwerk bevindt zich in het ongemakkelijke midden, en het midden is geen binaire keuze tussen “Docker is prima” en “draai een VM voor iedereen.”

Wat een klant naar rechts duwt, is niet hun omvang. Het zijn vier vragen:

  • Slaan ze gereguleerde data op? Medische dossiers, creditcardgegevens, alles wat een toezichthouder gevoelig zou noemen.
  • Heeft een datalek op hun tenant een realistisch pad naar een andere tenant? Als ze willekeurige code kunnen draaien, ja.
  • Vertrouw je de code en de mensen die het implementeren? Een klant die de goedkoopste freelancer inhuurt, is niet hetzelfde vertrouwensniveau als een klant wiens dev-team je kent.
  • Zegt hun contract “dedicated,” “geïsoleerd,” of “privé”? Zo ja, dan heb je al een tier beloofd; de enige taak is nu de juiste kiezen.

Als je die vragen nog niet kunt beantwoorden, plaats de klant dan in een basistier en schrijf de aannames op. Dat is geen beveiligingsaudit; het is een realiteitscheck die je bij elke onboarding herhaalt.

Hoe beslis ik per klant zonder elke keer een beveiligingsaudit uit te voeren?

Maak een kleine tabel en houd je eraan. Je hebt geen matrix met veertig cellen nodig. Vier tiers dekken bijna elke klant die een bureau ziet.

ClientpositieWat hen daadwerkelijk scheidtGebruik wanneer
Tier 1: Gedeelde app/containerAlleen applicatielogicaInterne tools, laag-risicodata, projecten waar iedereen expliciet in één loginsysteem zit
Tier 2: Zelfde host, aparte containersNamespaces en cgroupsDe meeste marketingsites, contactformulieren, geen gevoelige data
Tier 3: Afgesloten containerTier 2 + non-root, capabilities verwijderd, seccomp, read-only root, netwerksegmentatieE-commerce, PII, aangepaste code die je niet volledig vertrouwt
Tier 4: Per-tenant VMHypervisor en een aparte kernelGezondheidszorg, financiën, compliance-documentatie, onvertrouwde code, lawaaierige buren

Zo werkt dat in de praktijk. Een bakkerijklant met een contactformulier en een Instagram-link gaat naar Tier 2: één container op een gedeelde host, standaard Docker-netwerk, resource-limieten, klaar. Een webwinkel die klantnamen, adressen en betaalomleidingen opslaat, gaat naar Tier 3: zelfde gedeelde host, maar de container draait als een non-root gebruiker, heeft geen extra kernel-capabilities, gebruikt een seccomp-profiel en stelt alleen poort 443 bloot. Een medisch intakeportaal dat beschermde gezondheidsinformatie opslaat, gaat naar Tier 4: een VM per tenant, omdat de kosten van een datalek niet zijn “we ruimen het wel op” maar “we kunnen de klant niet laten zien dat we hen serieus namen.”

De hele truc is dat je de architectuur niet voor elke klant opnieuw bedenkt. Je kiest een rij uit een tabel waar je het al over eens was. Zo kan een bureau van vijf personen honderd sites draaien zonder honderd afzonderlijke beveiligingsobsessies. Het betekent ook dat de volgende klant geen antwoord krijgt dat afhangt van welk teamlid de telefoon opnam. Voor het diepere architectuurdebat achter die keuzes, gaat deze gids over het ontwerpen van multi-tenant isolatieniveaus dieper in op de afwegingen.

Hoe ziet een afgesloten container er eigenlijk uit?

Laten we stoppen met “afgesloten” te zeggen en concreet worden. Dit is wat Tier 3 betekent voor een typische WordPress- of PHP-klant.

Verander eerst de gebruiker. De meeste officiële images draaien standaard nog als root; maak in je Dockerfile een non-root gebruiker aan en laat de app als die gebruiker draaien. Dat verwijdert meteen de meest voorkomende manier waarop een containercompromis een hostcompromis wordt. Ten tweede, verwijder de capabilities die je niet nodig hebt. Draai met --cap-drop ALL en voeg er maar één aan toe, meestal NET_BIND_SERVICE, zodat de app op poort 80 kan luisteren. Dat alleen al is een grotere verandering dan de meeste mensen verwachten. Ten derde, maak het root-bestandssysteem read-only met --read-only, en mount schrijfbare mappen (uploads, de database-datamap) als volumes of tmpfs. Ten vierde, pas een seccomp-profiel toe en, als je host het ondersteunt, AppArmor of SELinux. Tot slot, plaats de container op een dedicated Docker-netwerk en stel alleen de poorten bloot die daadwerkelijk bereikbaar moeten zijn.

Laten we een WordPress-voorbeeld doorlopen. Het basisimage draait waarschijnlijk als root, dus voeg je een useradd-stap en een USER-richtlijn toe. Je draait de container met een geheugenlimiet en een CPU-limiet, zodat een uitbarsting van plugin-verkeer de buurman geen pijn doet. Je mount /var/www/html/wp-content/uploads als een schrijfbaar volume. Je stelt --read-only in. Je koppelt het aan een netwerk dat geen --privileged-vlag in de buurt heeft. Het resultaat is een container die vroeger “een WordPress-site” was en nu “een WordPress-site die toevallig meer afgesloten is dan de meeste virtuele privéservers.”

Als het handmatig opzetten van dat alles fragiel aanvoelt, is er een eenvoudiger middenweg: Docker's Enhanced Container Isolation, die gebruikmaakt van user namespace-isolatie en een secure container-runtime. Het is een legitieme snelkoppeling, maar geen vrijbrief om non-root of het verwijderen van capabilities over te slaan. De tenant heeft nog steeds een verstandig image nodig. Het verschil is dat het kernel-gerichte aanvalsoppervlak kleiner wordt zonder dat je van de ene op de andere dag een seccomp-expert hoeft te worden. Als je de exacte volgorde voor één tenant wilt, verandert de stapsgewijze isolatie-hardeninggids dit gedeelte in copy-paste-commando's.

Wanneer stop ik met lagen en geef ik ze gewoon een VM?

Hier is het tegendraadse deel: meer isolatie is niet automatisch beter. VMs geven je isolatie op hardwareniveau, een aparte kernel en een veel kleiner aanvalsoppervlak als de gastkernel valt. Dat is precies wat gezondheidszorg- en financiële klanten verwachten als ze zeggen “we willen geïsoleerd worden.” Maar elke VM voegt patching-, back-up- en rekencapaciteitskosten toe, en het vermenigvuldigt het werk om een vloot up-to-date te houden. Als je elke klant een VM geeft omdat één klant je ooit vertelde dat Docker hen bang maakte, heb je safety theater gekocht met echt geld.

Een VM is het juiste antwoord wanneer het risico per tenant hoger is dan de operationele kosten van een VM per tenant. Dat betekent gereguleerde data, schriftelijke compliance-eisen, onvertrouwde code van derden, of een klant die een lawaaierige buur verwijderd wil hebben. Het is ook het juiste antwoord wanneer het contract van de klant letterlijk een dedicated omgeving belooft, want “container” is niet wat ze zich voorstellen wanneer ze “dedicated” ondertekenen.

Maar een VM is geen excuus voor een slordige container. Een veelvoorkomende valkuil is om de klant in een VM te plaatsen en vervolgens de hardening over te slaan omdat “de VM hen beschermt.” De VM beschermt de host tegen de tenant, niet de tenant tegen zijn eigen slechte image. Je wilt nog steeds non-root, verwijderde capabilities en seccomp in die VM. De hybride aanpak—containers in een VM—is vaak de sweet spot: de VM biedt de grens voor compliance-gesprekken, en de container geeft je de deployment-workflow die je al kent. Er is een langere versie van dat debat in Moet elke tenant zijn eigen VM krijgen?, maar het korte antwoord is dat de VM voor het contract is, niet voor de angst.

Hoe maak ik dit herhaalbaar voor elke klant?

Je maakt het herhaalbaar door het tiersysteem een sjabloon te maken, geen herinnering. Houd een map met Compose-bestanden bij, één per tier: tier2-baseline, tier3-locked, tier4-vm-hybrid. Wanneer een nieuwe klant zich aandient, kopieer je het sjabloon, wijzig je de omgevingsvariabelen, en je weet al de isolatievorm voordat je een regel nieuwe infrastructuur hebt geschreven.

Schrijf vervolgens de beslissing op. Geen 400 pagina's tellend beveiligingsrapport, maar een korte paragraaf in de repository van de klant: welke data ze opslaan, in welke tier ze zitten, waarom, en wat hen naar een hogere tier zou verplaatsen. Die paragraaf is meer waard dan honderd firewallregels, want het is het ding dat je de volgende auditor of de volgende bezorgde klant kunt laten zien. Het voorkomt ook dat je moet onthouden waarom de bakkerij Tier 2 kreeg en de webwinkel Tier 3 nadat het oorspronkelijke verkoopgesprek is vervaagd.

Automatiseer de saaie controles. Laat je CI elk klant-image scannen en een build laten mislukken als het als root draait, als het alle capabilities heeft, of als het een andere poort probeert te publiceren dan die de tier toestaat. Niets daarvan is exotisch; het zorgt er gewoon voor dat het sjabloon niet per ongeluk wordt gebroken door een goedbedoelende ontwikkelaar. Als je toch de omringende hosting-workflow bouwt, behandelt het artikel over productieklare Docker-hostingstrategieën het deel dat na de containers komt.

Niets hiervan is glamoureus. Geen blogpost gaat “tenant isolatie” zo spannend laten klinken als een greenfield-architectuurdiagram. Maar dit is het verschil tussen een bureau dat “hoe geïsoleerd zijn we?” beantwoordt met een gekruiste vingers “volledig” en een bureau dat een tier, een config en een reden kan tonen. Containers zijn geen magische muur. VMs zijn geen wondermiddel. Een tiersysteem is gewoon een beslissing die je opschrijft en hergebruikt—en voor een bureau is herhaalbaarheid het hele spel.

Sources (5)