B4. Контейнер руками
Модуль 17 стверджує, що контейнер не є сутністю ядра, а лише комбінацією незалежних механізмів. Найкраще в це повірити, вмикаючи їх по черзі й дивлячись після кожного, що саме змінилося.
Наприкінці ви матимете скрипт на сотню рядків, який робить те саме, що
docker run, і розумітимете, чого він насправді не робить.
Що має вийти
Section titled “Що має вийти”sudo ./mycontainer.sh ./rootfs /bin/shСкрипт із покроковим виводом: який механізм вмикається і що це змінює.
Після кожного етапу виконуйте всередині одні й ті самі команди —
ps -e, hostname, ip link, ls /, id — і записуйте, що змінилося.
Таблиця «механізм → що приховалося» і є головним результатом роботи.
-
Кореневий образ.
debootstrap --variant=minbaseабо розпакованийalpine-minirootfs. Просто каталог із файлами — жодної магії. -
Простір монтувань і
pivot_root.unshare --mount, потімpivot_rootна ваш каталог, змонтувати/proc,/sys,/dev.Спробуйте спершу
chrootзамістьpivot_rootі знайдіть, чому з нього можна вийти. Це не теоретична різниця. -
Простір PID.
unshare --pid --fork --mount-proc. Теперps -eпоказує один процес, і це вашshпід номером 1.Перевірте зворотне: з господаря той самий процес видно під звичайним PID. Ізоляція — питання погляду, а не існування.
-
Простори UTS, IPC і мережі.
unshare --utsдозволяє змінитиhostname, не чіпаючи господаря.unshare --netдає порожній стек із самимlo.З’єднайте контейнер із господарем парою
vethі добийтесяpingназовні — саме це робить Docker під капотом. -
overlayfs.
Змонтуйте корінь як
lowerтільки для читання плюс власнийupperдля запису. Створіть файл усередині й знайдіть його в каталозіupperна господарі (модуль 15).Запустіть два контейнери з одного
lowerі переконайтеся, що вони не бачать змін одне одного. -
Обмеження ресурсів.
cgroup із B3 на процес контейнера.
-
Обрізання привілеїв.
capsh --drop=cap_sys_admin,cap_net_admin,...і профільseccomp, що забороняє хоча бmount,rebootіkexec_load(модуль 16).Перевірте, що заборонений виклик тепер справді не проходить.
-
Простір користувачів.
unshare --user --map-root-user: усередині виroot, зовні — звичайний користувач. Це те, що відрізняє непривілейований контейнер від привілейованого, і найважливіший рівень захисту з усіх перелічених.
Що треба вміти підтвердити
Section titled “Що треба вміти підтвердити”Для звірки — що виходить у правильно зібраному контейнері:
hostname власний, ps -e бачить 4 процеси замість 144 на господарі,
оболонка має PID 1, findmnt / показує overlay, мережевих інтерфейсів нуль.
| Твердження | Чим доводиться |
|---|---|
| Усередині власний простір PID | ps -e показує 1–2 процеси |
| Той самий процес видно з господаря | ps -ef | grep на господарі |
| Простори різні | порівняти readlink /proc/self/ns/pid там і там |
Корінь підмінено, а не chroot |
findmnt / усередині |
Запис іде в upper |
знайти створений файл на господарі |
| Ресурси обмежені | cat <група>/memory.max |
| Привілеї обрізані | capsh --print усередині |
root усередині ≠ root зовні |
id усередині, ps -o user зовні |
Часті помилки
Section titled “Часті помилки”/proc не перемонтовано. ps показує процеси господаря, хоча простір
PID у вас уже власний. Лікується прапорцем --mount-proc або монтуванням
вручну.
pivot_root не спрацював. Новий корінь мусить бути точкою монтування,
і часто досить зробити mount --bind каталогу на себе.
Мережа не працює після --net. Так і має бути, простір же порожній.
Потрібна пара veth, адреси з обох боків і маршрут.
Усе робиться від root господаря. Тоді ви зібрали привілейований
контейнер. Восьмий етап тут не додаток, а суть роботи.
Далі, якщо цікаво
Section titled “Далі, якщо цікаво”Запустити всередині справжній образ із Docker Hub, розпакувавши шари
руками. Або порівняти свій скрипт із runc — еталонною реалізацією OCI.