Перейти до вмісту

B4. Контейнер руками

середнійспирається на модуль 16, модуль 17

Модуль 17 стверджує, що контейнер не є сутністю ядра, а лише комбінацією незалежних механізмів. Найкраще в це повірити, вмикаючи їх по черзі й дивлячись після кожного, що саме змінилося.

Наприкінці ви матимете скрипт на сотню рядків, який робить те саме, що docker run, і розумітимете, чого він насправді не робить.

Terminal window
sudo ./mycontainer.sh ./rootfs /bin/sh

Скрипт із покроковим виводом: який механізм вмикається і що це змінює.

Після кожного етапу виконуйте всередині одні й ті самі команди — ps -e, hostname, ip link, ls /, id — і записуйте, що змінилося. Таблиця «механізм → що приховалося» і є головним результатом роботи.

  1. Кореневий образ.

    debootstrap --variant=minbase або розпакований alpine-minirootfs. Просто каталог із файлами — жодної магії.

  2. Простір монтувань і pivot_root.

    unshare --mount, потім pivot_root на ваш каталог, змонтувати /proc, /sys, /dev.

    Спробуйте спершу chroot замість pivot_root і знайдіть, чому з нього можна вийти. Це не теоретична різниця.

  3. Простір PID.

    unshare --pid --fork --mount-proc. Тепер ps -e показує один процес, і це ваш sh під номером 1.

    Перевірте зворотне: з господаря той самий процес видно під звичайним PID. Ізоляція — питання погляду, а не існування.

  4. Простори UTS, IPC і мережі.

    unshare --uts дозволяє змінити hostname, не чіпаючи господаря. unshare --net дає порожній стек із самим lo.

    З’єднайте контейнер із господарем парою veth і добийтеся ping назовні — саме це робить Docker під капотом.

  5. overlayfs.

    Змонтуйте корінь як lower тільки для читання плюс власний upper для запису. Створіть файл усередині й знайдіть його в каталозі upper на господарі (модуль 15).

    Запустіть два контейнери з одного lower і переконайтеся, що вони не бачать змін одне одного.

  6. Обмеження ресурсів.

    cgroup із B3 на процес контейнера.

  7. Обрізання привілеїв.

    capsh --drop=cap_sys_admin,cap_net_admin,... і профіль seccomp, що забороняє хоча б mount, reboot і kexec_load (модуль 16).

    Перевірте, що заборонений виклик тепер справді не проходить.

  8. Простір користувачів.

    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 зовні

/proc не перемонтовано. ps показує процеси господаря, хоча простір PID у вас уже власний. Лікується прапорцем --mount-proc або монтуванням вручну.

pivot_root не спрацював. Новий корінь мусить бути точкою монтування, і часто досить зробити mount --bind каталогу на себе.

Мережа не працює після --net. Так і має бути, простір же порожній. Потрібна пара veth, адреси з обох боків і маршрут.

Усе робиться від root господаря. Тоді ви зібрали привілейований контейнер. Восьмий етап тут не додаток, а суть роботи.

Запустити всередині справжній образ із Docker Hub, розпакувавши шари руками. Або порівняти свій скрипт із runc — еталонною реалізацією OCI.