Привет, Хабр! Сегодня рассмотрим изоляции процессов и управления ресурсами в Linux, изучив возможности cgroups и namespaces. Разберёмся, как работают контейнеры изнутри и научимся создавать собственное изолированное окружение без Docker.
Немного теории
Namespaces
Namespaces в Linux — это механизм, который изолирует и виртуализирует системные ресурсы для групп процессов. По сути, это как если бы каждый процесс жил в своём собственном мире, не подозревая о существовании других. Рассмотрим основные виды namespaces:
PID Namespace: изолирует идентификаторы процессов. Процессы внутри этого пространства видят свои собственные PID, начиная с 1, и не могут видеть процессы вне этого пространства..
Mount Namespace: изолирует точки монтирования файловой системы. Процессы внутри видят собственную файловую систему или её часть, не затрагивая хост-систему.
UTS Namespace: позволяет изолировать имена узлов и доменов, т.е.
hostname
иdomainname
.Network Namespace: изолирует сетевые интерфейсы, маршруты, таблицы ARP и т.д. Процессы внутри имеют свой собственный сетевой стек.
IPC Namespace: изолирует межпроцессное взаимодействие с помощью System V IPC.
User Namespace: изолирует идентификаторы пользователей и групп.
Cgroup Namespace: иззолирует видимость cgroups. Процессы видят только свои собственные cgroups.
Time Namespace: позволяет изолировать системное время, чтобы процессы могли видеть другое время, нежели хост-система.
Cgroups
Cgroups — это механизм ядра Linux, который позволяет ограничивать, учитывать и изолировать использование системных ресурсов для групп процессов. Допустим, вы на афтепати и хотите, чтобы каждый гость съел не больше определённого количества пиццы. Cgroups делают то же самое, только вместо пиццы — CPU, память, диск и другие ресурсы.
Основные контроллеры cgroups:
CPU: конролирует использование процессорного времени. Можно установить лимиты на процент использования CPU или распределять время между группами процессов.
Memory: ограничивает использование оперативной памяти и swap.
IO: контролирует ввод-вывод на блочные устройства.
Devices: управляет доступом к устройствам. Позволяет разрешить или запретить определённым процессам доступ к конкретным устройствам.
PIDs: ограничивает количество процессов в группе.
Стоит отметить, что существует две версии cgroups: v1 и v2. Вторая версия объединяет все контроллеры в единую иерархию и упрощает управление. В современных дистрибутивах Linux по дефолту используется cgroups v2.
Cgroups используют псевдофайловую систему, которая монтируется в /sys/fs/cgroup/
.
Практическая часть
Сначала убедимся, какая версия cgroups используется в системе:
mount | grep cgroup
Если в выводе есть cgroup2
, значит используется cgroups v2.
Создание cgroup и ограничение ресурсов
Создадим директорию для нашей группы:
sudo mkdir /sys/fs/cgroup/my_group
Установим лимит использования CPU до 20%:
echo "20000 100000" | sudo tee /sys/fs/cgroup/my_group/cpu.max
Здесь 20000
— квота в микросекундах, 100000
— период, т.е процесс может использовать CPU не более 20% времени.
Установим лимит памяти в 50 МБ:
echo $((50*1024*1024)) | sudo tee /sys/fs/cgroup/my_group/memory.max
Добавим текущий процесс в cgroup:
echo $$ | sudo tee /sys/fs/cgroup/my_group/cgroup.procs
Создание изолированного окружения
Используем утилиту unshare
для запуска процессов в новых пространствах имён.
sudo unshare --fork --pid --mount --uts --ipc --net --user --map-root-user --mount-proc /proc /bin/bash
Разберём ключи команды:
--fork
: форкает процесс после создания пространств имён.--pid
: создаёт новый PID namespace.--mount
: создаёт новый Mount namespace.--uts
: создаёт новый UTS namespace.--ipc
: создаёт новый IPC namespace.--net
: создаёт новый Network namespace.--user
: создаёт новый User namespace.--map-root-user
: маппинг root пользователя внутри пространства имён.--mount-proc
: монтирует новую файловую систему/proc
внутри нового Mount namespace.
Теперь мы находимся в изолированной среде. Проверим это.
Выполним:
ps aux
Можно увидеть минимальный список процессов. PID bash будет равен 1.
На хосте выполняем:
ps aux | grep bash
Вы увидите, что процесс имеет другой PID на хосте.
Изменим hostname
внутри изолированного окружения:
hostname new_container
Проверим:
hostname
# Вывод: new_container
На хосте hostname
останется прежним.
Посмотрим сетевые интерфейсы:
ip link
Скорее всего, вы увидите только интерфейс lo
, т.к в новом Network namespace нет других интерфейсов.
Подготовка файловой системы
Создадим директорию для нашей файловой системы:
mkdir -p ~/my_container/rootfs
BusyBox — это единый бинарник, включающий множество стандартных утилит Unix.
Установим его:
sudo apt-get update
sudo apt-get install -y busybox-static
Копируем BusyBox в файловую систему:
mkdir -p ~/my_container/rootfs/bin
cp /bin/busybox ~/my_container/rootfs/bin/
Создание символических ссылок на утилиты:
cd ~/my_container/rootfs/bin
for cmd in sh ls ps mkdir mount uname hostname cat echo sleep; do
ln -s busybox $cmd
done
Создадим необходимые директории:
mkdir -p ~/my_container/rootfs/{proc,sys,dev,etc,tmp}
Настройка устройств
Создадим устройства в dev
:
sudo mknod -m 666 ~/my_container/rootfs/dev/null c 1 3
sudo mknod -m 666 ~/my_container/rootfs/dev/zero c 1 5
sudo mknod -m 666 ~/my_container/rootfs/dev/tty c 5 0
sudo mknod -m 666 ~/my_container/rootfs/dev/random c 1 8
sudo mknod -m 666 ~/my_container/rootfs/dev/urandom c 1 9
Создадим файл passwd
:
echo "root:x:0:0:root:/root:/bin/sh" > ~/my_container/rootfs/etc/passwd
Монтирование файловых систем внутри контейнера
Внутри изолированного окружения (после запуска unshare
) монтируем псевдофайловые системы:
mount -t proc proc ~/my_container/rootfs/proc
mount -t sysfs sysfs ~/my_container/rootfs/sys
mount -t devtmpfs devtmpfs ~/my_container/rootfs/dev
Войдём в изолированную систему с chroot
:
chroot ~/my_container/rootfs /bin/sh
Теперь находимся внутри изолированной файловой системы.
Проверим, что всё работает:
ls /
# Вывод: bin dev etc proc sys tmp
Проверим hostname
:
hostname
# Вывод: new_container
Попробуем создать файл:
echo "Hello from container" > /tmp/hello.txt
cat /tmp/hello.txt
# Вывод: Hello from container
Ограничение ресурсов с cgroups
Если cgroups не смонтирован, монтируем его:
mount -t cgroup2 none /sys/fs/cgroup
Создадим cgroup для контейнера:
mkdir /sys/fs/cgroup/my_container
Установим лимит CPU до 20%:
echo "20000 100000" > /sys/fs/cgroup/my_container/cpu.max
Установим лимит памяти в 50 МБ:
echo $((50*1024*1024)) > /sys/fs/cgroup/my_container/memory.max
Получим PID текущего процесса:
echo $$ > /sys/fs/cgroup/my_container/cgroup.procs
Тестирование ограничений
Создадим скрипт cpu_stress.sh
:
#!/bin/sh
while true; do :; done
Сделаем его исполняемым:
chmod +x cpu_stress.sh
Запустим скрипт внутри контейнера:
./cpu_stress.sh &
Проверим использование CPU:
top
Можно будет увидеть, что использование CPU ограничено примерно 20%.
Теперь создадим скрипт для нагрузки на память.
Создадим файл mem_stress.sh
:
#!/bin/sh
mem=()
while true; do
mem+=($(head -c 1048576 /dev/zero))
echo "Allocated ${#mem[@]} MB"
sleep 1
done
Этот скрипт будет выделять 1 МБ памяти каждую секунду.
Сделаем его исполняемым:
chmod +x mem_stress.sh
Запустим скрипт:
./mem_stress.sh
Когда суммарное потребление памяти превысит 50 МБ, процесс будет убит cgroups, и вы увидите сообщение об ошибке.
Автоматизация процесса с помощью скрипта
Создадим скрипт start_container.sh
для автоматизации всех шагов:
#!/bin/bash
# Шаг 1: Создание изолированного окружения
sudo unshare --fork --pid --mount --uts --ipc --net --user --map-root-user --mount-proc=/proc bash <<EOF
# Шаг 2: Монтирование файловых систем
mount -t proc proc ~/my_container/rootfs/proc
mount -t sysfs sysfs ~/my_container/rootfs/sys
mount -t devtmpfs devtmpfs ~/my_container/rootfs/dev
# Шаг 3: Вход в chroot
exec chroot ~/my_container/rootfs /bin/sh
EOF
Сделаем скрипт исполняемым:
chmod +x start_container.sh
Теперь можно запускать контейнер командой:
/start_container.sh
Заключение
Важно помнить, что при работе с низкоуровневыми возможностями ядра можно легко навредить системе. Всегда проверяйте права доступа и не запускайте незнакомый код с правами суперпользователя.
Если у вас есть вопросы или вы хотите поделиться своим опытом, смело пишите в комментариях.
Полезные ссылки:
Статья подготовлена в преддверии старта курса Computer Science. На странице курса вы сможете бесплатно посмотреть записи последних вебинаров.
Комментарии (2)
TIEugene
19.11.2024 01:29контейнеры в линукс не так уж давно устаканились
Ну как сказать... Chroot в Linux с самого начала, наверное. Но таки да, не так и давно. Годиков 30+ примерно
seyko2
Ну что ж.. Полезная статья. Именно по данной теме. Ибо контейнеры в линукс не так уж давно устаканились (относительно всего остального). Ещё бы про KVM и его связь с qemu. Это хоть и пораньше было разработано. Но разработчики - особо не прилагают усилий для просвещения кого либо кроме Линуса.