Віртуалізація і контейнери
Навіщо це
Section titled “Навіщо це”Контейнер запускається за мілісекунди, віртуальна машина за секунди.
Контейнер із гігабайтного образу займає мегабайти. docker run alpine
на машині з Ubuntu дає робочу Alpine, хоча ядро лишається тим самим.
Магії тут немає, як немає й окремої технології. Контейнери є комбінацією кількох механізмів ядра, кожен з яких існував задовго до Docker. Розібравши їх, ви зможете зібрати контейнер руками, і саме після цього перестанете ставитися до нього як до чорної скриньки.
Передумови. Ядро й системні виклики (модуль 3), процеси (модуль 6), overlayfs (модуль 15), capabilities і seccomp (модуль 16).
Дві різні ідеї
Section titled “Дві різні ідеї”Віртуалізація створює ілюзію цілого комп’ютера. Гостьова ОС має власне ядро й вважає, що керує апаратурою.
Контейнеризація створює ілюзію окремої системи в межах одного ядра. Процеси контейнера є звичайними процесами господаря, просто з обмеженим поглядом на світ.
Наслідки виходять протилежні за всіма параметрами. Віртуальна машина ізольована міцніше, але важча. Контейнер легкий, зате ділить ядро, а отже уразливість у ядрі стосується всіх контейнерів одразу.
Гіпервізори
Section titled “Гіпервізори”Гіпервізор першого типу, bare metal, працює прямо на апаратурі й сам є мінімальною операційною системою. Такими є VMware ESXi, Xen і Hyper-V, і на них побудовані хмари.
Другий тип, hosted, працює програмою всередині звичайної ОС; це VirtualBox чи VMware Workstation. Поставити простіше, накладних витрат більше.
Межа між типами розмита. KVM, наприклад, перетворює ядро Linux на гіпервізор першого типу, лишаючись при цьому звичайним модулем звичайної системи.
Апаратна віртуалізація (Intel VT-x, AMD-V) додала режим, у якому гостьове ядро виконує привілейовані інструкції безпосередньо, а гіпервізор перехоплює тільки те, що справді потребує втручання. До неї гостьовий код доводилося перекладати на льоту, і коштувало це дорого.
EPT робить те саме для пам’яті: апаратура транслює гостьову фізичну адресу у справжню фізичну й знімає з гіпервізора найдорожчу частину роботи (модуль 11).
Паравіртуалізація йде іншим шляхом. Гостьова ОС знає, що вона віртуальна,
і замість прикидатися звертається до гіпервізора напряму. Драйвери virtio
влаштовані саме так, тому й швидші за емуляцію справжніх пристроїв.
З чого зроблено контейнер
Section titled “З чого зроблено контейнер”Сутності «контейнер» у ядрі не існує. Є процес, до якого застосували чотири незалежні механізми.
-
Простори імен (namespaces) обмежують те, що процес бачить. Окремі простори для PID, точок монтування, мережі, користувачів, імені вузла, міжпроцесного зв’язку й cgroups. У власному просторі PID процес бачить себе під номером 1 і не бачить процесів господаря.
-
Контрольні групи (cgroups) обмежують те, що процес споживає: процесорний час, пам’ять, пропускну здатність вводу-виводу. Саме cgroups робить OOM killer передбачуваним для конкретної групи (модуль 12).
-
overlayfs дає кореневу файлову систему зі спільних шарів (модуль 15). Тому контейнери з одного образу не займають місця повторно.
-
Capabilities і seccomp прибирають зайві привілеї й системні виклики (модуль 16).
Docker, Podman і containerd не додають до цього нічого принципового. Вони пакують образи, керують мережею й життєвим циклом, а ізоляцію роблять ті самі чотири механізми, доступні з командного рядка кожному.
Образи й OCI
Section titled “Образи й OCI”Образ складається з шарів файлової системи й маніфесту з метаданими, де кожен шар є різницею відносно попереднього. Адресуються шари за хешем вмісту, тому однакові зберігаються один раз на всіх.
OCI стандартизує формат образу й середовища виконання, завдяки чому образ, зібраний Docker, запускається під Podman чи containerd.
З архітектури шарів випливає цілком практичний наслідок: порядок команд у Dockerfile визначає швидкість збірки. Якщо копіювати код перед встановленням залежностей, кеш шару із залежностями ставатиме недійсним при кожній зміні коду.
microVM і WSL2
Section titled “microVM і WSL2”Контейнери швидкі, зате ділять ядро. Віртуальні машини ізольовані, зате важкі. microVM намагається взяти обидві переваги: Firecracker запускає віртуальну машину з мінімальним набором емульованих пристроїв за десятки мілісекунд. Так працюють хмарні функції, де на одній машині виконується код різних клієнтів і спільне ядро неприйнятне.
WSL2 запускає повноцінне ядро Linux у полегшеній віртуальній машині поверх Hyper-V, з інтеграцією у файлову систему й мережу Windows. Перша версія WSL намагалася транслювати системні виклики Linux у виклики Windows і зіткнулася з тим, наскільки повно доводиться відтворювати чуже ABI.
Як це насправді в Linux
Section titled “Як це насправді в Linux”lsns 2>/dev/null | headПростори імен у системі: тип, кількість процесів і хто в ньому. На машині з контейнерами їх буде багато.
ls -l /proc/self/ns/Простори імен поточного процесу. Числа в дужках — ідентифікатори; два процеси в одному просторі мають однакове число.
unshare --pid --fork --mount-proc sh -c 'echo "мій PID: $$"; ps -e'Власний простір PID: оболонка бачить себе під номером 1 і не бачить жодного процесу господаря. Це той самий механізм, що дає контейнеру його PID 1.
unshare --net sh -c 'ip link show'Власний простір мережі: лише інтерфейс lo, жодної мережі назовні.
Docker з’єднує такий простір із господарем через пару віртуальних інтерфейсів.
cat /sys/fs/cgroup/cgroup.controllerssystemd-cgls --no-pager 2>/dev/null | head -15Доступні контролери cgroups v2 і дерево груп. systemd кладе кожну
службу у власну групу — це та сама технологія, що й у контейнерах.
systemd-run --user --scope -p MemoryMax=64M sh -c 'echo обмежено 64 МіБ; cat /sys/fs/cgroup/$(cat /proc/self/cgroup | cut -d: -f3)/memory.max 2>/dev/null'Запуск процесу з обмеженням пам’яті без жодного контейнера.
lsmod | grep -E '^kvm'; ls -l /dev/kvm 2>/dev/nullgrep -o -E 'vmx|svm' /proc/cpuinfo | sort -uПідтримка апаратної віртуалізації: vmx для Intel, svm для AMD.
systemd-detect-virtЧи система сама працює у віртуальному середовищі — і в якому саме.
Типові помилки розуміння
Section titled “Типові помилки розуміння”«Контейнер — це легка віртуальна машина». Це процес господаря з обмеженим поглядом на світ. Власного ядра він не має, тому й ізоляція в нього слабша, ніж у віртуальної машини.
«Docker — це технологія ізоляції». Ізоляцію дають namespaces, cgroups, overlayfs, seccomp і capabilities. Docker пакує їх у зручний інтерфейс.
«У контейнері інша операційна система». Інший там простір користувача. Ядро одне, і це ядро того дистрибутива, який стоїть на господарі.
«Root у контейнері безпечний». Без простору імен користувачів це той
самий root господаря, тільки з обрізаними capabilities. Прогалина в ядрі
перетворює його на повний root машини.
«Контейнери ізолюють від уразливостей ядра». Радше навпаки: ядро спільне, тому уразливість у ньому стосується всіх контейнерів одразу. Саме через це й існують microVM.
«Порядок команд у Dockerfile — справа смаку». Шари кешуються за порядком, тож копіювання коду перед встановленням залежностей робить кеш недійсним при кожній зміні коду.
Перевір себе
Лабораторна
Section titled “Лабораторна”B4 — контейнер руками. unshare для просторів імен, pivot_root
для кореня, overlayfs для шарів, cgroups для обмежень, seccomp
для фільтра викликів — без Docker. Мета — довести, що ізоляція
складається з окремих механізмів, кожен з яких можна ввімкнути й вимкнути.
B3 — cgroups і OOM. Обмежити пам’ять і простежити спрацювання OOM killer у межах групи.
Джерела
Section titled “Джерела”- Silberschatz, Operating System Concepts, розділ 18
man 7 namespaces,man 7 cgroups,man 1 unshare,man 2 pivot_root- Documentation/admin-guide/cgroup-v2
- OCI Image Specification
- Firecracker: Lightweight Virtualization — NSDI 2020