Запуск изолированных Linux-процессов без Docker
Контейнеры кажутся чем-то загадочным - пока не соберёшь один сам. В этом пошаговом руководстве мы соберём крошечный, полностью изолированный Linux-контейнер, используя только инструменты, которые уже встроены в ядро. Никакого Docker, Podman или других контейнерных рантаймов.
Я буду работать внутри временной виртуальной машины и советую вам поступить так же: мы будем действовать от root-пользователя, так что сломать хост-систему случайно здесь проще, чем кажется.
Термин control group (cgroup) впервые появился в ядре Linux ещё в 2007 году и изначально назывался process containers.

Подготовка собственного корня файловой системы
1.Создаём структуру каталогов для эксперимента:
mkdir -p /tmp/mybox/{lower,upper,work,merged}
2.Скачиваем какой-нибудь небольшой root-файловый архив - подойдёт Alpine, BusyBox или что-то похожее - и распаковываем его в lower:
3.Наслаиваем всё через overlayFS, чтобы все изменения записывались в upper, а исходные файлы в lower оставались нетронутыми:
mount -t overlay overlay \ -o lowerdir=/tmp/mybox/lower,upperdir=/tmp/mybox/upper,workdir=/tmp/mybox/work \ /tmp/mybox/merged
Теперь /tmp/mybox/merged - это, по сути, корневая директория нашего будущего контейнера.
Так как overlayFS хранит только изменённые файлы, большинство контейнерных образов меньше по размеру, чем музыкальный альбом, который вы стримили сегодня утром.
Настройка ограничений ресурсов через cgroups
Создаём новый срез cgroup и включаем в нём управление памятью и CPU:
mkdir -p /sys/fs/cgroup/mybox.slice/one echo "+memory +cpu" > /sys/fs/cgroup/mybox.slice/cgroup.subtree_control
Задаём лимиты: не больше 15 % одного ядра CPU и максимум 512 МиБ оперативной памяти (swap - отключён):
echo "15000 100000" > /sys/fs/cgroup/mybox.slice/one/cpu.max echo "512M" > /sys/fs/cgroup/mybox.slice/one/memory.max echo "0" > /sys/fs/cgroup/mybox.slice/one/memory.swap.max
Почему именно "15000 100000"? Первое число - это допустимое время выполнения в микросекундах, второе - весь период. То есть процесс может работать 15 000 мкс из каждых 100 000 мкс - ровно 15 %.Вход в новые пространства имён
Переключаемся на root-пользователя, добавляем текущий процесс в нужную cgroup и запускаем shell в новых пространствах имён:
sudo -i echo $$ > /sys/fs/cgroup/mybox.slice/one/cgroup.procs unshare \ --uts --pid --mount --mount-proc \ --net --ipc --cgroup \ --fork /bin/bash
Теперь у вас:
- UTS namespace - можно задать любое имя хоста;
- PID namespace - первый запущенный процесс получает PID 1;
- Mount namespace - точки монтирования изолированы от хоста.
Попробуйте сменить hostname:
hostname mycontainer2025 # видно только внутри контейнера
Безопасная смена корня с помощью pivot_root
Переходим в директорию merged, переключаем корневую файловую систему на неё и отцепляем старый корень:
cd /tmp/mybox/merged mount --make-rprivate / mkdir old_root pivot_root . old_root umount -l /old_root rmdir old_root
pivot_rootзащищает от нескольких классических способов выхода изchroot, так как делает старую корневую файловую систему недоступной.
Монтирование основных виртуальных файловых систем
Без /proc, /sys и /dev многие утилиты просто не работают. Добавим минимальный набор:
mknod -m 666 dev/null c 1 3
mknod -m 666 dev/zero c 1 5
mknod -m 666 dev/tty c 5 0
Создадим нужные каталоги и смонтируем виртуальные ФС
mkdir -p dev/{pts,shm}
mount -t devpts devpts dev/pts
mount -t tmpfs tmpfs dev/shm
mount -t sysfs sysfs sys
mount -t tmpfs tmpfs run
mount -t proc proc proc
Замена shell на ваше приложение
Для демонстрации я запускаю минимальный shell, который уже есть в rootfs:
exec /bin/busybox sh
С этого момента вы находитесь внутри контейнера, который собрали сами.
Проверка ограничений
Тест CPU
Внутри контейнера выполните:
while :; do :; done
А на хосте - посмотрите потребление CPU:
top -Hp $(pgrep -f busybox)
Должно быть видно, что процесс использует около 15 % CPU.
Тест памяти
Всё ещё внутри контейнера запустите:
python - <<'PY' b = bytearray(700 * 1024 * 1024) # allocate 700 MiB PY
Когда процесс превысит лимит в 512 МиБ, контроллер памяти ядра завершит его и выведет сообщение Killed.
Если бы swap был включён, ядро могло бы сначала сбросить страницы на диск. Но с отключённым swap процесс убивается быстрее, что удобно для тестов.
Очистка
Выходим из контейнера и отмонтируем overlay:
exit # leave the inner shell umount /tmp/mybox/merged
Теперь временные директории можно спокойно удалить.
Заключение
Мы только что собрали контейнер, используя исключительно возможности самого ядра Linux:
overlayFS- для слоя с возможностью записи,cgroups- чтобы задать лимиты на ресурсы,namespaces- для изоляции окружения.
Инструменты вроде Docker просто оборачивают эти базовые механизмы - но сами механизмы давно были в ядре. Не стесняйтесь продолжить эксперимент: можно закрепить контейнер за конкретными ядрами CPU через cpuset или ограничить опасные системные вызовы с помощью seccomp.