Блог
Яким клієнтам насправді потрібна власна VM? План ізоляції Docker за рівнями
VM для кожного клієнта — це надмірність. Ось як визначити, скільки ізоляції потрібно кожному клієнту — і автоматизувати це рішення.
Резюме
Агенції часто панікують, коли клієнт питає, наскільки насправді ізольовані їхні дані від інших клієнтів. Простори імен (namespaces) і cgroups у Docker дають реальну ізоляцію, але це не те саме, що апаратна межа. Замість того щоб запускати кожного клієнта на віртуальній машині (VM) або, що гірше, ставитися до всіх клієнтів однаково, створіть невеликий набір рівнів ізоляції та підберіть кожному клієнту рівень за чутливістю даних, рівнем довіри та вимогами відповідності. Захищений контейнер (не-root, зменшені можливості (capabilities), seccomp, read-only коренева файлова система) покриває більшість сайтів; регульовані або ворожі навантаження отримують VM або гібрид контейнер-в-VM. Ця стаття пропонує повторюваний процес прийняття рішень, порівняльну таблицю та чесний погляд на те, коли додаткова ізоляція є надмірною.
Ви на тому етапі продажу, коли новий клієнт каже: «ми медична сфера, покажіть, що наші дані ізольовані від ваших інших клієнтів», і ви б краще говорили про щось інше?
Це проблема агенцій: не одне ідеальне розгортання, а те саме надійне розгортання, повторене для десятка клієнтів з різними бюджетами, профілями ризику та вимогами відповідності. Ось чесна версія. Ізоляція Docker реальна, але вона специфічна. Простори імен дають кожному контейнеру власний погляд на процеси, мережу та файлову систему; cgroups обмежують CPU, пам'ять та дисковий ввід/вивід, щоб клієнти не могли «виснажити» один одного. Але це не дає вам апаратної стіни між контейнером і ядром хоста. Якщо зловмисник вирветься з контейнера, він опиниться всередині єдиного ядра, яке у вас є. Решта цієї статті перетворює цей неприємний факт на повторюване рішення: класифікуйте кожного клієнта за чутливістю даних і довірою, застосовуйте базовий профіль захисту та використовуйте VM лише тоді, коли вартість порушення безпеки вища за вартість VM.
Зачекайте, хіба контейнери вже не ізольовані?
Docker працює на просторах імен Linux і cgroups, і ці слова роблять реальну роботу. Простори імен розділяють ідентифікатори процесів, мережеві стеки, точки монтування та користувачів, тому процес в одному контейнері не може бачити таблицю процесів іншого. Cgroups встановлюють ліміти: надайте контейнеру 0,5 CPU, 512 МБ пам'яті та фіксовану вагу блокового вводу/виводу — і він отримає саме це. Неконтрольований цикл в одному клієнті буде обмежений, а не виведе з ладу сусіда. Якщо ви не налаштували ліміти, ви пропустили найосновніше, для чого існують cgroups.
Візьмемо простий PHP-застосунок у контейнері A. Він бачить власну файлову систему, власний мережевий інтерфейс, власний PID 1. Контейнер B має те саме, але інший погляд. Це простори імен. Тепер відійдіть і пропустіть обмеження пам'яті: контейнер A може заповнити RAM хоста і змусити контейнер B повзати. Саме для цього існують cgroups. Але два контейнери можуть бути ізольовані один від одного просторами імен і все одно спільно використовувати ядро хоста, що є суттю кожної історії про втечу з контейнера. Експлойт, який досягає ядра, потенційно може дістатися до кожного клієнта на цьому хості.
«Docker ізольований» — це напівправда. Точна версія: «Docker ізолює за допомогою просторів імен і cgroups, а вразливість ядра є радіусом ураження». Перш ніж довірити клієнту запуск ненадійного коду, подумайте про це хвилину. Відповідь — не «ніколи не використовуйте контейнери» — це легка паніка. Відповідь — система рівнів.
То чому деяким клієнтам потрібно більше, ніж простори імен?
Чесна відповідь: ізоляція — це не перемикач, а спектр. На одному кінці — повністю спільний контейнер, де всі фактично в одному застосунку. На іншому — окрема VM для кожного клієнта з власним ядром. Більшість роботи агенцій знаходиться в незручній середині, і середина — це не бінарний вибір між «Docker підходить» і «запустіть VM для всіх».
Що штовхає клієнта вправо, — це не його розмір. Це чотири питання:
- Чи зберігають вони регульовані дані? Медичні записи, дані платіжних карток, будь-що, що регулятор назвав би чутливим.
- Чи має порушення на їхньому клієнті реалістичний шлях до іншого клієнта? Якщо вони можуть запускати довільний код, так.
- Чи довіряєте ви коду та людям, які його розгортають? Клієнт, який наймає найдешевшого фрілансера, — не той самий рівень довіри, що клієнт, чию команду розробників ви знаєте.
- Чи є в їхньому контракті слова «виділений», «ізольований» або «приватний»? Якщо так, ви вже пообіцяли рівень; тепер залишилося лише вибрати правильний.
Якщо ви ще не можете відповісти на ці питання, помістіть клієнта в базовий рівень і запишіть припущення. Це не аудит безпеки; це перевірка здорового глузду, яку ви повторюєте при кожному онбордингу.
Як мені вирішувати для кожного клієнта без проведення аудиту безпеки щоразу?
Створіть невелику таблицю і дотримуйтесь її. Вам не потрібна матриця з сорока клітинками. Чотири рівні покриють майже кожного клієнта, якого бачить агенція.
| Позиція клієнта | Що насправді їх розділяє | Використовувати, коли |
|---|---|---|
| Рівень 1: Спільний застосунок/контейнер | Тільки логіка застосунку | Внутрішні утиліти, дані з низьким ризиком, проєкти, де всі явно в одній системі входу |
| Рівень 2: Той самий хост, окремі контейнери | Простори імен і cgroups | Більшість маркетингових сайтів, контактні форми, без чутливих даних |
| Рівень 3: Захищений контейнер | Рівень 2 + не-root, зменшені можливості, seccomp, read-only корінь, сегментація мережі | Електронна комерція, PII, власний код, якому ви не повністю довіряєте |
| Рівень 4: VM на кожного клієнта | Гіпервізор та окреме ядро | Медицина, фінанси, документація відповідності, ненадійний код, гучні сусіди |
Ось як це працює на практиці. Клієнт-пекарня з контактною формою та посиланням на Instagram потрапляє на Рівень 2: один контейнер на спільному хості, стандартна мережа Docker, обмеження ресурсів, готово. Інтернет-магазин, який зберігає імена, адреси та перенаправлення платежів, потрапляє на Рівень 3: той самий спільний хост, але контейнер працює від не-root користувача, без зайвих можливостей ядра, з профілем seccomp, і відкриває лише порт 443. Медичний портал прийому, який зберігає захищену медичну інформацію, потрапляє на Рівень 4: VM на кожного клієнта, оскільки вартість порушення — це не «ми це приберемо», а «ми не можемо показати клієнту, що сприйняли їх серйозно».
Весь фокус у тому, що ви не переосмислюєте архітектуру для кожного клієнта. Ви обираєте рядок із таблиці, про яку вже домовилися. Це дає змогу агенції з п'ятьох осіб запускати сотню сайтів без сотні окремих зацикленостей на безпеці. Це також означає, що наступний клієнт не отримає відповідь, яка залежить від того, який член команди відповів на дзвінок. Для глибшої дискусії про архітектуру, що стоїть за цими рішеннями, цей посібник із проєктування рівнів ізоляції для мультитенантності детальніше розглядає компроміси.
Як насправді виглядає захищений контейнер?
Давайте перестанемо говорити «захищений» і перейдемо до конкретики. Ось що означає Рівень 3 для типового WordPress або PHP клієнта.
По-перше, змініть користувача. Більшість офіційних образів за замовчуванням запускаються від root; у вашому Dockerfile створіть не-root користувача та запускайте застосунок від цього користувача. Це одразу усуває найпоширеніший спосіб, коли компрометація контейнера стає компрометацією хоста. По-друге, скиньте можливості, які вам не потрібні. Запускайте з --cap-drop ALL і додайте лише одну, зазвичай NET_BIND_SERVICE, щоб застосунок міг слухати порт 80. Лише це — більша зміна, ніж більшість очікує. По-третє, зробіть кореневу файлову систему read-only за допомогою --read-only і монтуйте доступні для запису каталоги (завантаження, каталог даних бази даних) як томи або tmpfs. По-четверте, застосуйте профіль seccomp і, якщо ваш хост підтримує, AppArmor або SELinux. Нарешті, помістіть контейнер у виділену мережу Docker і відкривайте лише ті порти, які справді мають бути доступними.
Розглянемо приклад WordPress. Базовий образ, ймовірно, запускається від root, тому ви додаєте крок useradd і директиву USER. Ви запускаєте контейнер з обмеженням пам'яті та CPU, щоб сплеск трафіку від плагінів не нашкодив сусіду. Ви монтуєте /var/www/html/wp-content/uploads як доступний для запису том. Ви встановлюєте --read-only. Ви підключаєте його до мережі, де немає жодного прапора --privileged. Результат — контейнер, який раніше був «сайтом WordPress», а тепер «сайтом WordPress, який захищений більше, ніж більшість віртуальних приватних серверів».
Якщо ручне налаштування всього цього здається крихким, існує простіший середній шлях: Enhanced Container Isolation від Docker, який використовує ізоляцію простору імен користувача та захищене середовище виконання контейнера. Це законний ярлик, але це не безкоштовний квиток для пропуску non-root або скидання можливостей. Клієнту все одно потрібен розумний образ. Різниця в тому, що поверхня атаки на ядро стає меншою без необхідності ставати експертом з seccomp за одну ніч. Якщо ви хочете отримати точну послідовність для одного клієнта, покроковий посібник із посилення ізоляції перетворює цей розділ на команди для копіювання.
Коли мені зупинитися і просто дати їм VM?
Ось контраргумент: більше ізоляції не завжди краще. VM дають ізоляцію на рівні апаратного забезпечення, окреме ядро та набагато меншу поверхню атаки, якщо гостьове ядро падає. Це саме те, що очікують медичні та фінансові клієнти, коли кажуть «ми хочемо бути ізольованими». Але кожна VM додає витрати на патчі, резервне копіювання та обчислення, і це множить роботу з оновлення флоту. Якщо ви ставите VM кожному клієнту, тому що один клієнт колись сказав, що Docker його лякає, ви купили театр безпеки за реальні гроші.
VM — правильна відповідь, коли ризик на клієнта вищий за операційну вартість VM на клієнта. Це означає регульовані дані, письмові вимоги відповідності, ненадійний сторонній код або клієнт, якому потрібно усунути гучного сусіда. Це також правильна відповідь, коли контракт клієнта буквально обіцяє виділене середовище, оскільки «контейнер» — це не те, що вони уявляють, підписуючи «виділений».
Але VM не виправдовує недбалого контейнера. Поширена пастка — помістити клієнта у VM, а потім пропустити посилення, бо «VM їх захищає». VM захищає хост від клієнта, а не клієнта від його власного поганого образу. Вам все одно потрібні non-root, скинуті можливості та seccomp у цій VM. Гібридний підхід — контейнери всередині VM — часто є золотою серединою: VM забезпечує межу для розмов про відповідність, а контейнер дає вам робочий процес розгортання, який ви вже знаєте. Довша версія цієї дискусії є в Чи повинен кожен клієнт отримати власну VM?, але коротка відповідь: VM потрібна для контракту, а не для страху.
Як зробити це повторюваним для кожного клієнта?
Ви робите це повторюваним, перетворивши систему рівнів на шаблон, а не на пам'ять. Тримайте каталог Compose-файлів, по одному на кожен рівень: tier2-baseline, tier3-locked, tier4-vm-hybrid. Коли з'являється новий клієнт, скопіюйте шаблон, змініть змінні середовища — і ви вже знаєте форму ізоляції, перш ніж написати рядок нової інфраструктури.
Потім запишіть рішення. Не звіт з безпеки на 400 сторінок, а короткий абзац у репозиторії клієнта: які дані вони зберігають, на якому рівні вони перебувають, чому і що може перемістити їх на вищий рівень. Цей абзац вартий більше, ніж сотня правил брандмауера, тому що це те, що ви можете показати наступному аудитору або наступному стурбованому клієнту. Це також позбавляє вас від необхідності згадувати, чому пекарня отримала Рівень 2, а інтернет-магазин — Рівень 3, коли первісний продаж уже стерся з пам'яті.
Автоматизуйте нудні перевірки. Нехай ваш CI сканує кожен образ клієнта і зупиняє збірку, якщо він запускається від root, якщо має всі можливості або намагається опублікувати порт, відмінний від тих, які дозволяє рівень. Нічого з цього не є екзотичним; це просто гарантія, що шаблон не буде випадково зламаний доброзичливим розробником. Якщо ви все одно будуєте навколишній процес хостингу, матеріал про готові до продакшну стратегії Docker-хостингу охоплює частину, яка йде після визначення контейнерів.
Нічого з цього не є гламурним. Жоден блог-пост не зробить «ізоляцію клієнтів» такою ж захопливою, як діаграма архітектури на зеленому полі. Але це різниця між агенцією, яка відповідає на питання «наскільки ми ізольовані?» з перехрещеними пальцями «повністю», і тією, яка може показати рівень, конфігурацію та причину. Контейнери — не магічна стіна. VM — не магічна куля. Система рівнів — це просто рішення, яке ви записуєте та використовуєте повторно — і для агенції повторюваність є усією грою.

