Blog
Ar trebui fiecare chiriaș să aibă propria sa VM?
Alege între containere per chiriaș, VM-uri și configurații hibride cu un cadru de decizie bazat pe risc și pașii de consolidare care fac fiecare opțiune defensabilă.
Rezumat
Găzduirea multi-tenant te obligă să alegi cât de mult pot ajunge chiriașii unul la altul. Containerele folosesc namespace-uri Linux și cgroups pentru a izola procesele și resursele, dar partajează kernel-ul gazdei. Mașinile virtuale adaugă o graniță la nivel de hardware, cu prețul vitezei și al sarcinii operaționale. O abordare hibridă—containere în interiorul VM-urilor—îți poate oferi ambele, dar dublează suprafața pe care trebuie să o patch-ezi. Acest articol te ghidează printr-o decizie bazată pe risc, o comparație una lângă alta și pașii de consolidare Docker care contează chiar și într-un VM. Până la final, vei ști care model de izolare se potrivește chiriașilor tăi și ce să configurezi înainte de lansare.
Aplicația ta multi-tenant este aproape gata. Ai un fișier Docker Compose care pornește un stack per client și este rapid. Apoi un prieten care conduce o companie de găzduire întreabă: „Îi dai fiecărui chiriaș propriul său VM?” Îngheți. Nu te-ai gândit la această întrebare. Acest articol îți oferă o modalitate de a răspunde astăzi, fără o echipă de securitate. Faci asta singur, așa că decizia trebuie să fie suficient de simplă pentru a fi apărată la 2 dimineața.
Nu mai încerca să găsești modelul „cel mai bun”. Începe prin a nota ce se întâmplă dacă codul unui chiriaș preia controlul asupra gazdei tale. Definește raza de explozie înainte de a alege orice instrument. Acest exercițiu îți va spune mai mult decât orice benchmark vreodată.
Kernel-ul este colegul de cameră pe care nu-l poți evacua
Containerele sunt eficiente pentru că partajează kernel-ul gazdei. Această partajare este tot trucul și tot riscul. Namespace-urile Linux oferă fiecărui container propria vedere asupra proceselor, rețelei și sistemului de fișiere. Grupurile de control (cgroups) îți permit să limitezi CPU, memoria și I/O-ul discului, astfel încât un chiriaș să nu-i lase pe ceilalți fără resurse. Dar niciuna nu creează un zid hardware.
Gândește-te la un container ca la un proces cu un act de identitate fals foarte bun. Crede că se află pe propria mașină. Kernel-ul, însă, este o singură copie a Linux care rulează pe gazda ta. Dacă un chiriaș exploatează o vulnerabilitate a kernel-ului, namespace-urile devin metadate și nimic mai mult. Un atacator care poate apela funcții ale kernel-ului poate ajunge la alte namespace-uri de pe același kernel. Aceasta este evadarea din container despre care tot auzi.
Să zicem că găzduiești un instrument B2B mic cu un container per client. Un client instalează un plugin dubios cu o eroare de execuție de cod de la distanță. Cu setările Docker implicite, acel proces rulează ca root în interiorul containerului. Root într-un container este tot UID 0, iar kernel-ul nu distinge acel UID de root-ul gazdei decât dacă mapi explicit utilizatorii. Atacatorul poate încerca să iasă, iar kernel-ul partajat este ținta lui.
Eșecul nu trebuie să fie dramatic. Un singur chiriaș care scurge memorie poate împinge gazda în swap, încetinind fiecare alt chiriaș. Fără limite cgroups, o buclă necontrolată este un atac de disponibilitate. Cu ele, este un proces blocat și o alertă.
Înseamnă asta că containerele sunt nesigure? Nu. Înseamnă că trebuie să tratezi kernel-ul ca pe o zonă de încredere comună. Înainte de a alege, scrie o declarație de risc de un paragraf: „Dacă containerul unui chiriaș este compromis, atacatorul poate accesa: [list]. Costul pentru afacere ar fi: [amount or impact].” Dacă acel paragraf te sperie, nu ești paranoid. Ești onest.
Pentru o privire mai profundă asupra spectrului de izolare, de la containere partajate la stive complet separate, vezi ghidul nostru despre proiectarea unei arhitecturi Docker multi-tenant.
Trei moduri de a tăia (Alege unul înainte de a implementa)
Există cu adevărat trei arhitecturi pentru izolarea multi-tenant. Fiecare „cea mai bună practică” este o combinație a acestora.
| Abordare | Barieră de izolare | Cel mai bun când | Cel mai dificil avertisment |
|---|---|---|---|
| Containere per chiriaș | Namespace-uri kernel + cgroups | Mulți chiriași mici, risc scăzut per chiriaș, nevoie de densitate | O singură exploatare a kernel-ului poate sparge toți chiriașii de pe acea gazdă |
| Un VM per chiriaș | Virtualizare hypervisor/hardware | Date reglementate, chiriași ostili, valoare mare per chiriaș | Mai greu, mai lent de furnizat, patch-ezi un OS per chiriaș |
| Containere în interiorul VM-urilor | Graniță VM în jurul sarcinilor containerizate | Densitate plus o carcasă dură între grupuri | Costurile și supraîncărcarea operațională aproape se dublează |
Containere per chiriaș. Acesta este implicit pentru majoritatea fondatorilor SaaS. Fiecare chiriaș primește propriul container sau un mic stack Compose. Furnizarea este instantanee, imaginile sunt mici, CI/CD este simplu. Limitele de resurse împiedică vecinii zgomotoși să mănânce serverul. Compromisul este kernel-ul partajat. Dacă poți menține sarcinile neprivilegiate și patch-ezi gazda în mod regulat, aceasta este adesea prima mișcare corectă.
Nu pune doi chiriași în același container. Acesta este un kernel partajat plus un runtime partajat plus un sistem de fișiere partajat. Dacă un chiriaș încarcă un fișier care creează un proces, celălalt chiriaș este deja în aceeași tabelă de procese. Un container este unitatea ta de izolare; fă-l un chiriaș per container.
Ce zici de baza de date? Dacă fiecare chiriaș se conectează la o singură instanță MongoDB sau PostgreSQL cu aceleași acreditări, ai adăugat deja o componentă partajată uriașă. Dă fiecărui chiriaș acreditări separate și, ideal, o bază de date sau un schemă separate. Containerele izolează aplicația; baza de date este adesea prima scurgere pe care un atacator o va testa.
Un VM per chiriaș. Dă fiecărui chiriaș o mașină virtuală completă. Hypervisor-ul adaugă o graniță la nivel de hardware, exact ceea ce o exploatare a kernel-ului trebuie să traverseze pentru a ajunge la gazdă. Acest lucru contează pentru medii reglementate sau când chiriașii nu sunt de încredere. Costul este densitatea și timpul. Acum gestionezi o flotă de sisteme de operare, nu doar containere. Fiecare VM are nevoie de actualizări, agenți de securitate și monitorizare. Pentru un fondator solo, asta este muncă reală.
Modele care funcționează la acest nivel: folosește infrastructura-ca-cod pentru a crea un VM din aceeași imagine de bază, coace actualizările în imagini noi în loc să patch-ezi sistemele live și termină sarcinile pe care nu le recunoști. Ține portul de management al VM-ului închis pentru internet.
Containere în interiorul VM-urilor. Acest hibrid este rareori discutat în tutorialele pentru începători. Pune un VM mic în jurul fiecărui chiriaș (sau a unui mic grup de chiriași), apoi rulează containere în interiorul acelui VM. VM-ul este un container de rază de explozie; containerele sunt doar unități implementabile. Acest lucru îți oferă muchia dură a virtualizării și reproductibilitatea imaginilor. Costă mai mult, pentru că plătești pentru supraîncărcarea virtualizării și flexibilitatea containerelor, dar poate fi cel mai sănătos model pe termen lung când nu poți avea încredere deplină în chiriași.
Un micro-exemplu comun: un chiriaș rulează o API Node și un worker de fundal. În loc de un singur container uriaș cu ambele procese, folosește un VM, apoi două containere cu limite de resurse diferite, o rețea partajată și fără expunere directă la internet pentru worker. VM-ul oferă muchia dură; containerele oferă structură.
Pe care ar trebui să o alegi? Tabelul este lista ta scurtă. Secțiunile următoare fac decizia concretă.
Dacă Alegi Containere, Fă Aceste Șase Lucruri sau Nu Te Obosi
Containerele per chiriaș sunt bune dacă tratezi fiecare container ca pe un potențial atacator. Asta începe cu configurația, nu cu gândirea pozitivă.
0. Limitează resursele înainte de a avea încredere în cineva. Cgroups-urile sunt un mecanism de echitate și o apărare a disponibilității. Setează --memory și --cpus per container. Un chiriaș care scurge memorie ar trebui să-și atingă propria limită, nu pe a serverului tău. Aceasta nu este o graniță de securitate, dar un vecin zgomotos este un atac fără o singură linie de cod. Un start practic: --memory 512m --cpus 0.5. Pentru un proces de tip worker, începe mai jos și scalează în sus.
1. Rulează ca utilizator non-root. Nu lăsa niciodată procesul din container să folosească UID 0 decât dacă ai absolut nevoie. Setează un utilizator în Dockerfile și treci --user ca o protecție suplimentară. O exploatare care rulează ca utilizator neprivilegiat are mult mai puține căi către kernel. În Dockerfile-ul tău, creează un utilizator: RUN useradd -u 10001 app și USER app. Nu sări peste asta pentru a economisi timp.
2. Renunță la fiecare capabilitate de care nu ai nevoie. Capabilitățile Linux împart puterea root-ului în bucăți mici. Majoritatea aplicațiilor web au nevoie de aproape niciuna. Începe cu --cap-drop=ALL și adaugă înapoi doar ceea ce știi că ai nevoie. Un container fără CAP_SYS_ADMIN este mult mai greu de folosit pentru trucuri cu namespace-uri. Dacă aplicația ta încearcă să se lege de un port privilegiat, rulează-o pe un port înalt și pune un proxy în față în loc să acorzi NET_BIND_SERVICE.
3. Fă sistemul de fișiere read-only. Aplicația ta nu ar trebui să scrie în propriul strat de container. Montează un tmpfs pentru stare. Un atacator care nu poate scrie pe disc are mult mai greu să planteze persistență. O aplicație PHP compromisă care încearcă să scrie un webshell va eșua când sistemul de fișiere rădăcină este read-only. Poți monta un volum numit pentru un director scriibil de care aplicația ta are nevoie cu adevărat.
4. Aplică seccomp și AppArmor sau SELinux. Acestea trimit apelurile de sistem riscante la coșul de gunoi. Docker livrează un profil seccomp implicit; folosește-l. Adaugă un profil AppArmor pentru un alt strat. Nu trebuie să stăpânești fiecare apel de sistem. Trebuie să negi ceea ce un worker web normal nu cere niciodată. Nu rula niciodată cu --privileged. Acest flag dezactivează aproape fiecare apărare pe care tocmai ai configurat-o.
5. Segmentează rețeaua. Nu oferi fiecărui container o rută către fiecare alt container. Refuză implicit, apoi deschide doar porturile de care ai nevoie. Un container de baze de date compromis nu ar trebui să poată scana panoul tău de administrare. Dacă chiriașii sunt în rețele separate, o breșă într-o rețea nu se poate răspândi lateral.
Un start practic:
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
Pune aceleași flag-uri într-un fișier Compose și aplică-le fiecărui chiriaș. Aceasta nu este completă, dar este un implicit mult mai puternic decât ceea ce îți oferă docker run din cutie.
Pentru un ghid mai aprofundat, folosește ghidul nostru pas cu pas de consolidare pentru containere Docker în găzduirea multi-tenant.
Izolarea Îmbunătățită a Containerelor Docker este Excepția pe care Ar Trebui să o Cunoști
Dacă rulezi într-un mediu Docker gestionat, caută Izolarea Îmbunătățită a Containerelor Docker (ECI). Folosește izolarea namespace-urilor de utilizator și un runtime de container securizat sub capotă. Root-ul dintr-un container se mapează la un utilizator neprivilegiat pe gazdă, astfel încât chiar și un container care rulează ca root nu primește privilegiile de root ale gazdei. De asemenea, blochează capabilitățile și apelurile de sistem periculoase în mod implicit. Acest lucru nu este ceva ce poți recrea cu câteva flag-uri pe Docker vanilat. Dacă platforma ta îl suportă, activează-l. Nu elimină nevoia de utilizatori non-root și limite de resurse, dar schimbă matematica riscului.
Poți aproxima o parte din asta cu remaparea namespace-ului de utilizator (userns-remap) în daemon-ul Docker. Nu este la fel de complet ca un runtime securizat, dar este mai bine decât nimic. Dacă îl folosești, verifică că maparea UID funcționează înainte de a avea încredere în el.
Falacia VM: Mutarea la Mașini Virtuale nu este Consolidare
Iată partea contrarie, și este partea pe care majoritatea oamenilor o sar. Dacă te muți la un VM per chiriaș și apoi implementezi containerele tale normale în interiorul lui, nu ai eliminat problema de securitate a containerelor. Ai adăugat o cușcă largă. Evadarea din container încă funcționează; atacatorul aterizează doar în VM în loc de pe gazdă. Aceasta este o îmbunătățire reală, dar tot ai nevoie de cei șase pași.
Cealaltă capcană este să presupui că VM-ul în sine este sigur. O imagine implicită cu o parolă SSH slabă, pachete de bază nepatch-uite sau un port de management deschis este un cadou. Granița hypervisor-ului contează doar dacă invitatul este consolidat și actualizat. Altfel, „VM-ul tău securizat” este o cale mai rapidă către compromitere pentru că te simți în siguranță și încetezi să verifici.
Ce îți oferă un VM este o rază de explozie reductibilă. Dezastrul unui chiriaș rămâne într-un VM. Ce te costă este timpul tău. Devii administratorul de sistem pentru tot atâtea sisteme de operare câți chiriași ai. Dacă ești un fondator solo care livrează un produs, întreabă-te dacă ai orele să patch-ezi și să monitorizezi o flotă. Dacă da, VM per chiriaș poate fi alegerea corectă. Dacă nu, containerele cu consolidare puternică ar putea fi mai oneste.
De asemenea, amintește-ți că gazda hypervisor este o țintă critică. Un hypervisor compromis poate vedea toți invitații. Patch-ează gazda, nu doar invitații. VM-ul nu te scuză de la patch-uirea gazdei; crește miza pentru a o rata.
O avertizare asupra hibridului: nu presupune că containerele într-un VM îți oferă „două straturi de securitate” gratuit. VM-ul adaugă o graniță; containerul are în continuare nevoie de non-root, capabilități și seccomp. Altfel, primul strat este doar la fel de puternic ca cel mai slab container.
Patru Întrebări care Rezolvă Dezbaterea în Zece Minute
Nu optimiza în abstract. Pune-ți aceste patru întrebări în ordine. Notează răspunsurile.
1. La ce are acces chiriașul meu? Dacă un chiriaș poate ajunge doar la propria aplicație web și baza de date, containerele per chiriaș cu reguli stricte de rețea sunt defensabile. Dacă datele unui chiriaș sunt reglementate sau sensibile financiar, îndreaptă-te spre VM-uri.
2. Cât m-ar costa compromiterea unui singur chiriaș? Adună clienți pierduți, expunere legală și încredere. Dacă numărul este mai mare decât costul rulării VM-urilor, cheltuie banii. Dacă nu, containerele sunt o alegere rațională.
3. Câți chiriași am și cât plătesc ei? Mulți abonați mici: densitatea containerelor contează. O mână de conturi mari: dă fiecăruia un VM și facturează în consecință. Chiriașii care îți plătesc mai puțin decât o cafea nu ar trebui să necesite fiecare un OS de gestionat.
4. Pot patch-ui lucrurile după un program? Containerele partajează un singur kernel al gazdei, așa că patch-uirea gazdei protejează pe toată lumea. VM-urile îți multiplică țintele de patch. Dacă știi că vei sări peste actualizări, alege arhitectura cu mai puține părți mobile și implicit mai dure.
Răspunsurile tale se vor grupa. Două sau mai multe răspunsuri focusate pe VM înseamnă că nu ar trebui să folosești implicit containere per chiriaș. Trei sau mai multe răspunsuri focusate pe containere înseamnă că VM-urile sunt premature. Un rezultat contraintuitiv: un chiriaș cu venituri mici cu acces la date sensibile are totuși nevoie de VM, pentru că costul de reglementare nu are nimic de-a face cu cât plătește.
Livrează Cât Mai Puțin Poți Avea Încredere, Apoi Câștigă Mai Multă Izolare
Prima ta arhitectură nu trebuie să fie cea finală. Începe cu cea mai strânsă configurare pe care o poți menține efectiv, apoi adaugă izolare pe măsură ce baza ta de chiriași o justifică. Pentru majoritatea operatorilor solo, asta înseamnă containere per chiriaș cu non-root, capabilități limitate, sisteme de fișiere read-only, seccomp și segmentare a rețelei. Pentru chiriașii reglementați sau cu valoare mare, sari direct la un VM per chiriaș, cu containere doar ca strat de ambalare în interior.
Orice ai alege, notează decizia și reia-o trimestrial. Când primești prima întrebare „ar trebui să mutăm acest chiriaș la un VM?”, vei avea un răspuns și vei avea lista de verificare care să-l susțină. Asta înseamnă de fapt izolarea: un compromis pe care îl gestionezi, nu o tehnologie pe care o cumperi.
Înainte de lansare, parcurge lista noastră practică de verificare a securității izolării Docker—transformă aceste decizii într-o listă pe care o poți verifica înainte de a arăta o pagină unui client.

