Blogg
Vilka kunder behöver verkligen en egen VM? En nivåindelad Docker-isoleringsplan
VM för varje kund är överkill. Här är hur du bestämmer hur mycket isolering varje klient behöver – och automatiserar beslutet.
Sammanfattning
Byråer får ofta panik när en kund frågar hur verkligt isolerad deras data är från andra klienter. Dockers namespaces och cgroups ger dig verklig isolering, men de är inte samma sak som en hårdvarugräns. Istället för att köra varje kund på en VM – eller ännu värre, behandla alla kunder likadant – bygg en liten uppsättning isoleringsnivåer och matcha varje kund till en utifrån datakänslighet, förtroende och efterlevnad. En nedlåst container (non-root, borttagna capabilities, seccomp, skrivskyddat root) täcker de flesta webbplatser; reglerade eller fientliga arbetsbelastningar får en VM eller en container-i-VM-hybrid. Det här inlägget ger ett repeterbart beslutsflöde, en jämförelsetabell och en ärlig titt på när mer isolering är överdrivet.
Är du i ett säljsamtal där den nya kunden säger “vi är ett vårdbolag, visa att vår data är isolerad från era andra kunder” och du helst skulle prata om något annat?
Det här är byråns problem: inte en perfekt driftsättning, utan samma pålitliga driftsättning upprepad över ett dussin kunder med olika budgetar, riskprofiler och efterlevnadskrav. Här är den ärliga versionen. Docker-isolering är verklig, men den är specifik. Namespaces ger varje container sin egen vy över processer, nätverk och filsystem; cgroups sätter tak för CPU, minne och disk I/O så att klienter inte kan svälta varandra. Det du inte får är en hårdvaruvägg mellan containern och värdkernel. Om en angripare rymmer containern är de inne i den enda kernel du har. Resten av den här artikeln förvandlar det obekväma faktumet till ett repeterbart beslut: klassificera varje kund efter datakänslighet och förtroende, tillämpa en baslinjeprofil för härdning, och sträck dig bara efter en VM när kostnaden för ett dataintrång är högre än kostnaden för VMen.
Vänta, är inte containrar redan isolerade?
Docker körs på Linux namespaces och cgroups, och de orden gör ett verkligt jobb. Namespaces separerar process-ID:n, nätverksstackar, monteringspunkter och användare så att en process i en container inte kan se en annan containers processtabell. Cgroups sätter gränser: ge en container 0,5 CPU, 512 MB minne och en fast block I/O-vikt, så är det exakt vad den får. En skenande loop i en klient stryps istället för att ta ner grannen. Om du inte har konfigurerat gränser har du hoppat över det mest grundläggande som cgroups är till för.
Ta en enkel PHP-app i container A. Den ser sitt eget filsystem, sitt eget nätverksgränssnitt, sin egen PID 1. Container B har samma sak, men en annan vy. Det är namespaces. Om du nu går därifrån och skippar minnesgränsen: container A kan fylla värdens RAM och få container B att krypa fram. Det är vad cgroups finns för att förhindra. Men två containrar kan vara isolerade från varandra med namespaces och fortfarande dela värdkernel, vilket är den del varje containerflyktsberättelse handlar om. En exploit som når kernel kan potentiellt nå varje klient på den värden.
“Docker är isolerat” är en halvsann mening. Den korrekta versionen är “Docker isolerar med namespaces och cgroups, och en kernel-sårbarhet är sprängradien.” Innan du litar på att en klient kör otillförlitlig kod, sätt dig med den tanken en minut. Svaret är inte “använd aldrig containrar” – det är den enkla paniken. Svaret är ett nivåsystem.
Så varför behöver vissa kunder mer än namespaces?
Det ärliga svaret är att isolering inte är en strömbrytare, det är ett spektrum. I ena änden har du en helt delad container där alla i praktiken är i en app. I den andra änden har du en separat VM per klient med egen kernel. De flesta byråjobb lever i det obekväma mitten, och mitten är inte ett binärt val mellan “Docker är okej” och “kör en VM för alla.”
Det som driver en kund åt höger är inte deras storlek. Det är fyra frågor:
- Lagrar de reglerad data? Journaler, betalkortsuppgifter, allt som en tillsynsmyndighet skulle kalla känsligt.
- Har ett dataintrång hos deras klient en realistisk väg till en annan klient? Om de kan köra godtycklig kod, ja.
- Litar du på koden och människorna som driftsätter den? En kund som anlitar den billigaste frilansaren är inte samma förtroendenivå som en kund vars utvecklingsteam du känner.
- Står det “dedikerad”, “isolerad” eller “privat” i deras avtal? Om det gör det har du redan lovat en nivå; det enda jobbet nu är att välja rätt.
Om du inte kan svara på de frågorna ännu, placera kunden i en baslinjenivå och skriv ner antagandena. Det är inte en säkerhetsgranskning; det är en verklighetskontroll du upprepar vid varje onboarding.
Hur bestämmer jag per kund utan att köra en säkerhetsgranskning varje gång?
Gör en liten tabell och håll dig till den. Du behöver inte en matris med fyrtio celler. Fyra nivåer täcker nästan varje kund en byrå ser.
| Kundposition | Vad som faktiskt skiljer dem åt | Använd när |
|---|---|---|
| Nivå 1: Delad app/container | Endast applikationslogik | Interna verktyg, lågriskmaterial, projekt där alla uttryckligen är i ett inloggningssystem |
| Nivå 2: Samma värd, separata containrar | Namespaces och cgroups | De flesta marknadswebbplatser, kontaktformulär, ingen känslig data |
| Nivå 3: Nedlåst container | Nivå 2 + non-root, borttagna capabilities, seccomp, skrivskyddat root, nätverkssegmentering | E-handel, PII, anpassad kod du inte fullt ut litar på |
| Nivå 4: Per-klient VM | Hypervisor och en separat kernel | Vård, ekonomi, efterlevnadspapper, otillförlitlig kod, störande grannar |
Så här ser det ut i praktiken. En bagerikund med ett kontaktformulär och en Instagram-länk hamnar på Nivå 2: en container på en delad värd, standard Docker-nätverk, resursgränser, klart. En webbutik som lagrar kundnamn, adresser och betalningsomdirigeringar hamnar på Nivå 3: samma delade värd, men containern körs som en non-root-användare, har inga extra kernel-capabilities, använder en seccomp-profil och exponerar bara port 443. En medicinsk intagningportal som lagrar skyddad hälsoinformation hamnar på Nivå 4: en VM per klient, eftersom kostnaden för ett dataintrång inte är “vi städar upp det” utan “vi kan inte visa kunden att vi tog dem på allvar.”
Hela tricket är att du inte tänker om arkitekturen för varje kund. Du väljer en rad från en tabell ni redan kommit överens om. Så kan en fempersonersbyrå driva hundra webbplatser utan hundra separata säkerhetsfixeringar. Det innebär också att nästa kund inte får ett svar som beror på vilken teammedlem som svarade i telefonen. För den djupare arkitekturdebatten bakom dessa val, den här guiden om att designa multi-tenant-isoleringsnivåer täcker avvägningarna mer i detalj.
Hur ser en nedlåst container faktiskt ut?
Låt oss sluta säga “nedlåst” och bli konkreta. Det här är vad Nivå 3 innebär för en typisk WordPress- eller PHP-kund.
För det första, byt användare. De flesta officiella bilder kör fortfarande som root som standard; i din Dockerfile skapar du en non-root-användare och kör appen som den användaren. Det tar omedelbart bort det vanligaste sättet som en komprometterad container blir en komprometterad värd. För det andra, ta bort de capabilities du inte behöver. Kör med --cap-drop ALL och lägg bara tillbaka en, vanligtvis NET_BIND_SERVICE så att appen kan lyssna på port 80. Det i sig är en större förändring än de flesta förväntar sig. För det tredje, gör root-filsystemet skrivskyddat med --read-only, och montera skrivbara kataloger (uploads, databasens datakatalog) som volymer eller tmpfs. För det fjärde, tillämpa en seccomp-profil och, om din värd stödjer det, AppArmor eller SELinux. Slutligen, placera containern på ett dedikerat Docker-nätverk och exponera endast de portar som faktiskt behöver vara nåbara.
Låt oss gå igenom ett WordPress-exempel. Basbilden körs förmodligen som root, så du lägger till ett useradd-steg och en USER-direktiv. Du kör containern med en minnesgräns och en CPU-gräns, så att en burst av plugintrafik inte skadar grannen. Du monterar /var/www/html/wp-content/uploads som en skrivbar volym. Du ställer in --read-only. Du kopplar den till ett nätverk som inte har någon --privileged-flagga i närheten. Resultatet är en container som brukade vara “en WordPress-webbplats” och nu är “en WordPress-webbplats som råkar vara mer nedlåst än de flesta virtuella privata servrar.”
Om det känns skört att göra allt för hand finns det en enklare mellanväg: Dockers Enhanced Container Isolation, som använder user namespace-isolering och en säker containerruntime. Det är en legitim genväg, men det är inte ett frikort för att hoppa över non-root eller borttagning av capabilities. Klienten behöver fortfarande en förnuftig bild. Skillnaden är att den kernelvända attackytan blir mindre utan att du blir en seccomp-expert över en natt. Om du vill ha exakt sekvens för en enskild klient, steg-för-steg-guiden för isoleringshärdning förvandlar det här avsnittet till copy-paste-kommandon.
När slutar jag lägga på lager och ger dem bara en VM?
Här är den konträra delen: mer isolering är inte automatiskt bättre. VM:ar ger dig isolering på hårdvarunivå, en separat kernel och en mycket mindre attackyta om gästkernel fallerar. Det är exakt vad vård- och ekonomikunder förväntar sig när de säger “vi vill vara isolerade.” Men varje VM lägger till patchning, backup och beräkningskostnad, och det multiplicerar arbetet med att hålla en serverflotta uppdaterad. Om du ger varje kund en VM för att en kund en gång sa att Docker skrämde dem, har du köpt säkerhetsteater med riktiga pengar.
En VM är rätt svar när risken per klient är högre än den operationella kostnaden för en VM per klient. Det innebär reglerad data, skriftliga efterlevnadskrav, otillförlitlig tredjepartskod eller en kund som behöver få bort en störande granne. Det är också rätt svar när kundens avtal bokstavligen lovar en dedikerad miljö, eftersom “container” inte är vad de föreställer sig när de skriver under “dedikerad”.
Men en VM ursäktar inte en slarvig container. En vanlig fälla är att placera kunden i en VM och sedan hoppa över härdningen för att “VM:en skyddar dem.” VM:en skyddar värden från klienten, inte klienten från sin egen dåliga bild. Du vill fortfarande ha non-root, borttagna capabilities och seccomp inuti den VMen. Hybridmetoden – containrar i en VM – är ofta den idealiska lösningen: VM:en ger gränsen för efterlevnadssamtal, och containern ger dig det driftsättningsarbetsflöde du redan kan. Det finns en längre version av den debatten i Ska varje klient ha en egen VM?, men det korta svaret är att VM:en är för kontraktet, inte för rädslan.
Hur gör jag detta repeterbart för varje kund?
Du gör det repeterbart genom att göra nivåsystemet till en mall, inte ett minne. Ha en katalog med Compose-filer, en per nivå: tier2-baseline, tier3-locked, tier4-vm-hybrid. När en ny kund dyker upp kopierar du mallen, ändrar miljövariablerna, och du vet redan isoleringsformen innan du har skrivit en rad ny infrastruktur.
Skriv sedan ner beslutet. Inte en 400-sidig säkerhetsrapport, utan ett kort stycke i kundens repository: vilken data de lagrar, vilken nivå de ligger på, varför, och vad som skulle flytta upp dem en nivå. Det stycket är värt mer än hundra brandväggsregler, för det är det du kan visa nästa revisor eller nästa oroliga kund. Det förhindrar också att du måste komma ihåg varför bageriet fick Nivå 2 och webbutiken Nivå 3 efter att det ursprungliga säljsamtalet har bleknat.
Automatisera de tråkiga kontrollerna. Låt din CI skanna varje kundimage och få en build att misslyckas om den körs som root, om den har alla capabilities, eller om den försöker publicera en annan port än de som nivån tillåter. Inget av det är exotiskt; det handlar bara om att se till att mallen inte råkar förstöras av en välmenande utvecklare. Om du ändå bygger det omgivande hostingarbetsflödet, artikeln om produktionsredo Docker-hostingstrategier täcker den del som kommer efter att containrarna är definierade.
Inget av detta är glamoröst. Inget blogginlägg kommer att få “klientisolering” att låta lika spännande som ett greenfield-arkitekturdiagram. Men det här är skillnaden mellan en byrå som svarar “hur isolerade är vi?” med fingrarna i kors och “helt” och en som kan visa en nivå, en konfiguration och en anledning. Containrar är inte en magisk vägg. VM:ar är inte en magisk kula. Ett nivåsystem är bara ett beslut du skriver ner och återanvänder – och för en byrå är repeterbarhet hela spelet.

