Blog
Czy każdy dzierżawca powinien mieć własną maszynę wirtualną?
Wybierz między kontenerami na dzierżawcę, maszynami wirtualnymi i rozwiązaniami hybrydowymi, stosując ramy decyzyjne oparte na ryzyku oraz kroki utwardzania, które czynią każdą opcję obronną.
Summary
Wielodostępny hosting zmusza Cię do wyboru, jak daleko dzierżawcy mogą sięgać do siebie nawzajem. Kontenery używają przestrzeni nazw jądra Linux i cgroups do izolacji procesów i zasobów, ale współdzielą jądro hosta. Maszyny wirtualne dodają granicę na poziomie sprzętu, kosztem szybkości i obciążenia operacyjnego. Podejście hybrydowe – kontenery wewnątrz maszyn wirtualnych – może dać Ci jedno i drugie, ale podwaja powierzchnię, którą musisz łatać. Ten artykuł przeprowadzi Cię przez decyzję opartą na ryzyku, porównanie obok siebie oraz kroki utwardzania Dockera, które mają znaczenie nawet wewnątrz maszyny wirtualnej. Na koniec będziesz wiedzieć, który model izolacji pasuje do Twoich dzierżawców i co skonfigurować przed uruchomieniem.
Twoja aplikacja wielodostępna jest prawie gotowa. Masz plik Docker Compose, który uruchamia stack dla każdego klienta, i jest szybki. Wtedy znajomy prowadzący firmę hostingową pyta: „Czy dajesz każdemu dzierżawcy własną maszynę wirtualną?” Zamarzasz. Nie planowałeś tego pytania. Ten artykuł daje Ci sposób, aby odpowiedzieć na nie dzisiaj, bez zespołu ds. bezpieczeństwa. Robisz to sam, więc decyzja musi być wystarczająco prosta, aby obronić ją o 2 w nocy.
Przestań szukać „najlepszego” modelu. Zacznij od zapisania, co się stanie, jeśli kod dzierżawcy przejmie Twój host. Zdefiniuj promień rażenia, zanim wybierzesz jakiekolwiek narzędzie. To ćwiczenie powie Ci więcej niż jakikolwiek benchmark.
Jądro to współlokator, którego nie możesz eksmitować
Kontenery są wydajne, ponieważ współdzielą jądro hosta. To współdzielenie jest całym trikiem i całym ryzykiem. Przestrzenie nazw jądra Linux dają każdemu kontenerowi własny widok procesów, sieci i systemu plików. Grupy kontrolne (cgroups) pozwalają ograniczyć CPU, pamięć i operacje wejścia/wyjścia na dysku, aby jeden dzierżawca nie zagłodził pozostałych. Ale żadne z nich nie tworzy sprzętowej ściany.
Pomyśl o kontenerze jako o procesie z naprawdę dobrym fałszywym dowodem tożsamości. Wierzy, że jest na własnej maszynie. Jądro natomiast to jedna kopia Linuksa działająca na Twoim hoście. Jeśli dzierżawca wykorzysta lukę w jądrze, przestrzenie nazw stają się tylko metadanymi. Atakujący, który może wywoływać funkcje jądra, może dotrzeć do innych przestrzeni nazw na tym samym jądrze. To jest ta ucieczka z kontenera, o której ciągle słyszysz.
Powiedzmy, że hostujesz małe narzędzie B2B z jednym kontenerem na klienta. Klient instaluje podejrzaną wtyczkę z błędem zdalnego wykonywania kodu. Przy domyślnych ustawieniach Dockera proces działa jako root wewnątrz kontenera. Root w kontenerze to nadal UID 0, a jądro nie odróżnia tego UID od roota hosta, chyba że jawnie zmapujesz użytkowników. Atakujący może próbować się wydostać, a współdzielone jądro jest jego celem.
Awaria nie musi być dramatyczna. Pojedynczy dzierżawca wyciekający pamięć może wepchnąć host w swap, spowalniając wszystkich innych. Bez limitów cgroups, jedna źle działająca pętla to atak na dostępność. Z nimi to zablokowany proces i alert.
Czy to oznacza, że kontenery są niebezpieczne? Nie. Oznacza to, że musisz traktować jądro jako wspólną strefę zaufania. Zanim wybierzesz, napisz jednoakapitowe oświadczenie o ryzyku: „Jeśli kontener dzierżawcy zostanie naruszony, atakujący może uzyskać dostęp do: [list]. Koszt biznesowy wyniósłby: [amount or impact].” Jeśli ten akapit Cię przeraża, nie jesteś paranoikiem. Jesteś uczciwy.
Aby głębiej przyjrzeć się spektrum izolacji, od współdzielonych kontenerów po w pełni oddzielne stacki, zobacz nasz przewodnik na temat projektowania wielodostępnej architektury Docker.
Trzy sposoby na podział (wybierz jeden przed wdrożeniem)
Tak naprawdę istnieją trzy architektury izolacji wielodostępnej. Każda „najlepsza praktyka” to kombinacja tych trzech.
| Podejście | Bariera izolacji | Najlepsze, gdy | Najtrudniejsza pułapka |
|---|---|---|---|
| Kontenery per-dzierżawca | Przestrzenie nazw jądra + cgroups | Wielu małych dzierżawców, niskie ryzyko na dzierżawcę, potrzebna gęstość | Jeden exploit jądra może złamać wszystkich dzierżawców na tym hoście |
| Jedna maszyna wirtualna na dzierżawcę | Wirtualizacja hipervisora/sprzętu | Regulowane dane, wrodzy dzierżawcy, wysoka wartość na dzierżawcę | Cięższa, wolniejsza do provisioningu, łatasz system operacyjny na dzierżawcę |
| Kontenery wewnątrz maszyn wirtualnych | Granica VM wokół zadań kontenerowych | Gęstość plus twarda skorupa między grupami | Koszty i obciążenie operacyjne prawie się podwajają |
Kontenery per-dzierżawca. To domyślny wybór dla większości założycieli SaaS. Każdy dzierżawca dostaje własny kontener lub mały stack Compose. Provisioning jest natychmiastowy, obrazy są małe, CI/CD jest proste. Limity zasobów powstrzymują hałaśliwych sąsiadów przed zjadaniem serwera. Kompromisem jest współdzielone jądro. Jeśli możesz utrzymać obciążenia bez uprawnień i regularnie łatać hosta, to często jest właściwy pierwszy ruch.
Nie umieszczaj dwóch dzierżawców w jednym kontenerze. To współdzielone jądro plus współdzielone środowisko uruchomieniowe plus współdzielony system plików. Jeśli jeden dzierżawca wgra plik, który tworzy proces, drugi dzierżawca jest już w tej samej tabeli procesów. Kontener jest Twoją jednostką izolacji; zrób jeden kontener na dzierżawcę.
A co z bazą danych? Jeśli każdy dzierżawca łączy się z jedną instancją MongoDB lub PostgreSQL z tymi samymi poświadczeniami, już dodałeś ogromny współdzielony komponent. Daj każdemu dzierżawcy osobne poświadczenia, a najlepiej osobną bazę danych lub schemat. Kontenery izolują aplikację; baza danych jest często pierwszym wyciekiem, który atakujący przetestuje.
Jedna maszyna wirtualna na dzierżawcę. Daj każdemu dzierżawcy pełną maszynę wirtualną. Hipervisor dodaje granicę na poziomie sprzętu, czyli dokładnie to, co exploit jądra musi przekroczyć, aby dotrzeć do hosta. To ma znaczenie w środowiskach regulowanych lub gdy dzierżawcy są niegodni zaufania. Koszt to gęstość i czas. Teraz zarządzasz flotą systemów operacyjnych, nie tylko kontenerami. Każda VM wymaga aktualizacji, agentów zabezpieczeń i monitorowania. Dla samotnego założyciela to prawdziwa praca.
Wzorce, które działają na tym poziomie: używaj infrastruktury jako kodu do tworzenia VM z tego samego obrazu bazowego, wbudowuj aktualizacje w nowe obrazy zamiast łatać działające systemy i kończ obciążenia, których nie rozpoznajesz. Trzymaj port zarządzania VM zamknięty dla internetu.
Kontenery wewnątrz maszyn wirtualnych. Ten hybrydowy model rzadko jest omawiany w tutorialach dla początkujących. Umieszczasz małą VM wokół każdego dzierżawcy (lub małej grupy dzierżawców), a następnie uruchamiasz kontenery wewnątrz tej VM. VM jest kontenerem promienia rażenia; kontenery to tylko jednostki wdrażania. Daje Ci to twardą krawędź wirtualizacji i odtwarzalność obrazów. Kosztuje więcej, bo płacisz za narzut wirtualizacji i elastyczność kontenerów, ale może być najbardziej rozsądnym długoterminowym modelem, gdy nie możesz w pełni ufać dzierżawcom.
Częsty mikro-przykład: dzierżawca uruchamia API Node i proces roboczy w tle. Zamiast jednego ogromnego kontenera z obydwoma procesami, użyj jednej VM, a następnie dwóch kontenerów z różnymi limitami zasobów, współdzieloną siecią i bez bezpośredniej ekspozycji na internet dla procesu roboczego. VM zapewnia twardą krawędź; kontenery zapewniają strukturę.
Który wybrać? Tabela to Twoja skrócona lista. Kolejne sekcje czynią decyzję konkretną.
Jeśli wybierasz kontenery, zrób te sześć rzeczy albo nie rób ich wcale
Kontenery per-dzierżawca są w porządku, jeśli traktujesz każdy kontener jako potencjalnego atakującego. To zaczyna się od konfiguracji, a nie od pobożnych życzeń.
0. Ogranicz zasoby, zanim komukolwiek zaufasz. Cgroups to mechanizm sprawiedliwości i obrona dostępności. Ustaw --memory i --cpus dla każdego kontenera. Dzierżawca, który wycieka pamięć, powinien trafić we własny limit, a nie w Twój serwer. To nie jest granica bezpieczeństwa, ale hałaśliwy sąsiad to atak bez ani jednej linii kodu. Praktyczny początek: --memory 512m --cpus 0.5. Dla procesu roboczego zacznij od mniejszych wartości i skaluj.
1. Uruchamiaj jako użytkownik niebędący rootem. Nigdy nie pozwól, aby proces w kontenerze używał UID 0, chyba że absolutnie go potrzebujesz. Ustaw użytkownika w Dockerfile i przekaż --user jako dodatkową ochronę. Exploit działający jako nieuprzywilejowany użytkownik ma znacznie mniej ścieżek do jądra. W swoim Dockerfile utwórz użytkownika: RUN useradd -u 10001 app i USER app. Nie pomijaj tego, aby zaoszczędzić czas.
2. Odrzuć wszystkie możliwości, których nie potrzebujesz. Możliwości Linuksa dzielą moc roota na małe kawałki. Większość aplikacji internetowych nie potrzebuje prawie żadnych. Zacznij od --cap-drop=ALL i dodaj tylko to, o czym wiesz, że potrzebujesz. Kontener bez CAP_SYS_ADMIN jest znacznie trudniejszy do wykorzystania do sztuczek z przestrzeniami nazw. Jeśli Twoja aplikacja próbuje związać uprzywilejowany port, uruchom ją na wysokim porcie i umieść proxy z przodu, zamiast przyznawać NET_BIND_SERVICE.
3. Spraw, aby system plików był tylko do odczytu. Twoja aplikacja nie powinna zapisywać do własnej warstwy kontenera. Zamontuj tmpfs dla stanu. Atakujący, który nie może zapisać na dysku, ma znacznie trudniejsze zadanie z utrwaleniem się. Naruszona aplikacja PHP próbująca zapisać webshella nie powiedzie się, gdy system plików jest tylko do odczytu. Możesz zamontować nazwany wolumen dla katalogu zapisywalnego, którego Twoja aplikacja naprawdę potrzebuje.
4. Zastosuj seccomp i AppArmor lub SELinux. Te narzędzia odrzucają ryzykowne wywołania systemowe. Docker dostarcza domyślny profil seccomp; użyj go. Dodaj profil AppArmor jako kolejną warstwę. Nie musisz opanować każdego wywołania systemowego. Musisz odmówić tego, czego zwykły proces roboczy sieci nigdy nie wymaga. Nigdy nie uruchamiaj z --privileged. Ta flaga wyłącza prawie wszystkie zabezpieczenia, które właśnie skonfigurowałeś.
5. Segmentuj sieć. Nie dawaj każdemu kontenerowi trasy do każdego innego kontenera. Domyślnie odmawiaj, a następnie otwórz tylko te porty, których potrzebujesz. Naruszony kontener bazy danych nie powinien być w stanie skanować Twojego panelu administracyjnego. Jeśli dzierżawcy są w oddzielnych sieciach, naruszenie w jednej sieci nie może rozprzestrzeniać się lateralnie.
Praktyczny początek:
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
Umieść te same flagi w pliku Compose i zastosuj je do każdego dzierżawcy. To nie jest komplet, ale jest znacznie silniejszym domyślnym ustawieniem niż to, co daje docker run od razu po wyjęciu z pudełka.
Aby uzyskać bardziej szczegółowe omówienie, skorzystaj z naszego przewodnika krok po kroku dotyczącego utwardzania kontenerów Docker w środowisku wielodostępnym.
Ulepszona izolacja kontenerów Docker to wyjątek, o którym powinieneś wiedzieć
Jeśli działasz w zarządzanym środowisku Docker, poszukaj Enhanced Container Isolation (ECI) od Docker. Wykorzystuje on izolację przestrzeni nazw użytkownika i bezpieczne środowisko uruchomieniowe kontenerów pod maską. Root w kontenerze mapuje się na nieuprzywilejowanego użytkownika na hoście, więc nawet kontener działający jako root nie uzyskuje uprawnień roota na hoście. Domyślnie blokuje również niebezpieczne możliwości i wywołania systemowe. To nie jest coś, co można odtworzyć kilkoma flagami na standardowym Dockerze. Jeśli Twoja platforma to obsługuje, włącz to. Nie eliminuje to potrzeby stosowania użytkowników niebędących rootem i limitów zasobów, ale zmienia matematykę ryzyka.
Możesz częściowo to przybliżyć, używając ponownego mapowania przestrzeni nazw użytkownika (userns-remap) w demonie Docker. To nie jest tak kompletne jak bezpieczne środowisko uruchomieniowe, ale lepsze niż nic. Jeśli to używasz, zweryfikuj działanie mapowania UID, zanim mu zaufasz.
Błąd VM: Przejście na maszyny wirtualne to nie utwardzanie
To jest kontrowersyjna część i część, którą większość ludzi pomija. Jeśli przejdziesz na jedną VM na dzierżawcę, a następnie wdrożysz w niej swoje normalne kontenery, nie usunąłeś problemu bezpieczeństwa kontenerów. Dodałeś szeroką klatkę. Ucieczka z kontenera nadal działa; atakujący ląduje w VM zamiast na hoście. To prawdziwa poprawa, ale nadal potrzebujesz tych sześciu kroków.
Drugą pułapką jest zakładanie, że sama VM jest bezpieczna. Domyślny obraz ze słabym hasłem SSH, niezałatanymi pakietami bazowymi lub otwartym portem zarządzania to prezent. Granica hipervisora ma znaczenie tylko wtedy, gdy gość jest utwardzony i zaktualizowany. W przeciwnym razie Twoja „bezpieczna VM” to szybsza droga do naruszenia, bo czujesz się bezpiecznie i przestajesz sprawdzać.
To, co daje Ci VM, to redukowalny promień rażenia. Katastrofa jednego dzierżawcy zostaje w jednej VM. To, co Cię kosztuje, to Twój czas. Stajesz się administratorem systemów dla tylu systemów operacyjnych, ilu masz dzierżawców. Jeśli jesteś samotnym założycielem wdrażającym produkt, zapytaj, czy masz godziny na łatanie i monitorowanie floty. Jeśli tak, VM na dzierżawcę może być właściwym wyborem. Jeśli nie, kontenery z silnym utwardzeniem mogą być bardziej uczciwe.
Pamiętaj również, że host hipervisora jest krytycznym celem. Naruszony hipervisor może widzieć wszystkich gości. Łataj hosta, nie tylko gości. VM nie zwalnia Cię z łatania hosta; podnosi stawkę za jego pominięcie.
Uwaga dotycząca hybrydy: nie zakładaj, że kontenery wewnątrz VM dają Ci „dwie warstwy bezpieczeństwa” za darmo. VM dodaje granicę; kontener nadal potrzebuje non-root, możliwości i seccomp. W przeciwnym razie pierwsza warstwa jest tylko tak silna, jak najsłabszy kontener.
Cztery pytania, które rozstrzygają debatę w dziesięć minut
Nie optymalizuj w abstrakcji. Zadaj sobie te cztery pytania w kolejności. Zapisz odpowiedzi.
1. Do czego ma dostęp mój dzierżawca? Jeśli dzierżawca może dotrzeć tylko do własnej aplikacji internetowej i bazy danych, kontenery per-dzierżawca z rygorystycznymi regułami sieciowymi są do obrony. Jeśli dane dzierżawcy są regulowane lub wrażliwe finansowo, skieruj się w stronę maszyn wirtualnych.
2. Ile kosztowałoby mnie naruszenie jednego dzierżawcy? Zsumuj utraconych klientów, ekspozycję prawną i zaufanie. Jeśli ta liczba jest większa niż koszt uruchomienia maszyn wirtualnych, wydaj te pieniądze. Jeśli nie, kontenery są racjonalnym wyborem.
3. Ilu mam dzierżawców i ile płacą? Wielu małych subskrybentów: gęstość kontenerów ma znaczenie. Kilka dużych kont: daj każdemu VM i rozliczaj odpowiednio. Dzierżawcy, którzy płacą mniej niż kawa, nie powinni wymagać systemu operacyjnego do zarządzania.
4. Czy mogę łatać rzeczy zgodnie z harmonogramem? Kontenery współdzielą jedno jądro hosta, więc łatanie hosta chroni wszystkich. Maszyny wirtualne mnożą cele łatek. Jeśli wiesz, że pominiesz aktualizacje, wybierz architekturę z mniejszą liczbą ruchomych części i twardszymi domyślnymi ustawieniami.
Twoje odpowiedzi będą się grupować. Dwie lub więcej odpowiedzi skupionych na VM oznacza, że nie powinieneś domyślnie używać kontenerów per-dzierżawca. Trzy lub więcej odpowiedzi skupionych na kontenerach oznacza, że maszyny wirtualne są przedwczesne. Jeden kontrintuicyjny wynik: dzierżawca o niskich przychodach z dostępem do wrażliwych danych nadal potrzebuje VM, ponieważ koszt regulacyjny nie ma nic wspólnego z tym, ile płaci.
Wdrażaj najmniej, czemu możesz zaufać, a potem zapracuj na większą izolację
Twoja pierwsza architektura nie musi być tą ostateczną. Zacznij od najciaśniejszej konfiguracji, którą możesz faktycznie utrzymać, a następnie dodawaj izolację, gdy uzasadni to baza dzierżawców. Dla większości samotnych operatorów oznacza to kontenery per-dzierżawca z non-root, ograniczonymi możliwościami, systemami plików tylko do odczytu, seccomp i segmentacją sieci. Dla regulowanych lub wysokowartościowych dzierżawców przejdź od razu do jednej maszyny wirtualnej na dzierżawcę, z kontenerami wyłącznie jako warstwą pakowania wewnątrz.
Cokolwiek wybierzesz, zapisz decyzję i wracaj do niej co kwartał. Kiedy otrzymasz pierwsze pytanie „czy przenieść tego dzierżawcę na VM?”, będziesz miał odpowiedź i listę kontrolną, która ją poprze. To właśnie oznacza izolacja: kompromis, którym zarządzasz, a nie technologia, którą kupujesz.
Przed uruchomieniem przejrzyj naszą praktyczną listę kontrolną bezpieczeństwa izolacji Docker — zamienia te decyzje na listę, którą możesz zweryfikować, zanim pokażesz stronę klientowi.

