Советы пользователям

Documentation
Login

Title: Разные виртуальные среды

Author: Виктор Вагнер

Зачем нужны виртуальные среды

Последнее время распространилась мода создавать внутри одного физического компьютера много разных виртуальных сред, тем или иным способом отделенных друг от друга.

Зачем это нужно?

Ну, например, провайдер хостинга может предоставить каждому пользователю не просто директорию под веб-страничку, а полноценную вычислительную среду, куда можно ставить любые программы.

Ну, например, провайдеры Software as a Service получают удобную штуковину, которую можно легко мигрировать с одного физического компьютера на другой.

Ну, например, разработчики кроссплатформного софта получают возможности собирать и тестировать софт под разные дистрибутивы и операционные системы. На последнем случае мы в основном и остановимся.

Уровни изоляции

Кросс-компиляция

Существуют разные уровни изоляции виртуальной среды от хост-системы. Самый минимальный — это кросс-компиляция. Мы вообще никак не защищаем систему от программы, выполняемой в виртуализованной среде. Поскольку эта программа — это компилятор, поставленный из такого же пакета, как и родной, а то и собранный самостоятельно. Просто путем манипуляций с environment мы делаем так, чтобы он находил заголовочные файлы и библиотеки не от нашей основной системы, а от целевой.

В принципе этот способ позволяет собирать под любую систему, под любой процессор и так далее. Я, например, таким образом собирал под Linux-ом программы для Windows и на компьютере с процессором amd64 программы для процессора mips.

Но есть и свои неудобства. Во-первых, мы не можем что-то собранное таким образом тут же и запустить. А сборочные процессы многих крупных проектов включают стадии запуска чего-то свежесобранного, например для генерации каких-нибудь файлов данных.

Во-вторых, очень легко ошибиться и не уследить за тем, что компилятор подхватит какую-нибудь библиотеку или заголовочный файл от хост-системы. И если эти системы достаточно близки (например под Ubuntu 20.04 мы пытаемся собрать программу для RedHat Enteprise Linux 6 той же архитектуры) всплывёт это только при запуске на настоящей целевой системе.

chroot

Системный вызов chroot(2) был в Unix с незапамятных времён. Он позволяет запустить процесс, сделав так, что корнем файловой системы, видимой этому процессу, будет указанная директория. Естественно, что для выполнения этого вызова нужно быть суперюзером.

В принцие, сhroot уже позволяет много. Можно развернуть в подкаталоге копию файловой системы целевого дистрибутива, и вам будет гарантировано что там запустится какой надо компилятор и найдет какие надо библиотеки.

Правда, в современных linux-ах программы при выполнении (в том числе и в ходе сборки) могут захотеть доступа к специальным файловым системам /proc и /sys. Их придется смонтировать с -o bind. В общем, отсетапить правильный сборочный chroot — это довольно трудоёмкая работа.

В состав дистрибутива Debian входит pbuilder, которые использует этот метод для сборки дебиановских пакетов. А у альтлинукс есть аналогичный инструмент hasher. Эти инструменты берут все тонкости настройки chrooted environment на себя, но их область применения достаточно ограничена.

Например, man-ы читать или интерактивно экспериментировать в них сильно неудобно. А при кроссплатформной разработке на это уходит гораздо больше времени, чем на собственно компиляцию.

Ещё одним недостатком chroot является то, что он не изолирует сетевую подсистему. Ваша виртуальная среда будет видеть те же сетевые интерфейсы что и хост, и все запущенные chrooted среды будут делить общее пространство портов. А оно и так маленькое.

schroot

В состав некоторых дистрибутивов входит пакет schroot (который почему-то расшифровывается как secure chroot). Безопасность в смысле возможность раздать непривилегированным пользователям права на конкретные чруты там действительно есть. Но главное — не это. Это удобный chroot.

Если у вас RedHat или Centos то вам придётся подключить EPEL, чтобы получит возможность использовать этот пакет. Если Debian, Ubuntu или altlinux, вы просто ставите пакет и читаете его документацию.

Здесь есть уйма возможностей. Например, монтировать в chroot автоматически /home. Заходите в chroot, а там все ваши файлы и каталоги. Кроме того есть возможность создавать временные chroot-ы используя overlayfs. Главное преимущество этой конструкции это то что вы можете параллельно запустить на базе одной иерархии файлов несколько процессов, и они не будут мешать друг другу. Например при сборке пакетов необходимо бывает доставлять пакеты сборочных зависимостей. Использование сессий chroot позволит на одной физической машине запустить несколько процессов которые будут ставить и удалять пакеты независимо от друг от друга. Главное предусмотреть место, куда они все сложат результаты своей работы.

qemu-user

Иногда возникает необходимость выполнят программы для другой архитектуры. QEMU user mode позволяет запустить конкретную программу в режиме эмуляции процессора. При этом системные вызовы будут выполняться родным ядром вашей системы, что несколько облегчит вашу тяжелую участь.

Тем не менее, обычно при выполнении программ в режиме эмуляции производительность проседает на порядок.

В linux существует возможность регистрации в ядре новых форматов исполняемых файлов. Если вы в Debian установите пакет qemu-user-binfmt, то программы для всех типов процессоров, поддерживаемых qemu, будут выполняться на вашей системе как родные, если только найдут подходящие библиотеки.

В сочетании с schroot, qemu-user позволяет создать виртуальные среды для работы с любыми дистрибутивами линукс на любых арихтектурах.

Оно, конечно, работает медленно. Но если у вас нет машин со всеми требуемыми архитектурами, и вы пользуетесь машинами, предоставленными партнёрами или арендуете вычислительные мощности в облаках, очень рекомендую завести на всякий случай chrooted environments для всех поддерживаемых архитектур локально. Если у ваших партнёров всё упадёт в тот момент, когда вам позарез нужно срочно выпускать критичный багфикс, это вас спасёт. Хотя, честно сказать, производительность чрута, эмулирующего процессор arm64 на мощной современной рабочей станции с процессором amd64 получается сравнимой с Raspberry Pi.

Контейнеры

Следующий уровень изоляции — это контейнеры. В ядре limux существует понятие пространств имён, которое позволяет выделить какому-то подмножеству процессов собственные виртуальные сетевые интерфейсы, собственное пространство идентификаторов процесса и так далее, таким образом делая виртуализованную среду куда более похожей на полноценную систему чем chrooted environment.

Существует довольно много высокоуровневых и среднеуровневых инструментов, использующих эти возможности: lxc, systemd-nspsawn, runc, docker, cubernetes и т.д.

В основном высокоуровневые среды ориентированы на хостинг приложений и SaaS. Поэтому если ввша задача — кроссплатформная разработка, пять раз подумайте, прежде чем с ними связываться. В Postgres Professional мы используем docker для сборки, в основном потому что там имеются продвинутые средства управления реестром образов контейнеров, что облегчает распространения изменений в этих образах по целой куче сборочных серверов. Если бы не это, вероятно, давно бы перешли на сборку с помощью schroot, которую мы практикуем для архитектур, отличных от amd64.

Мы рассмотрим здесь в lxc. LXC - это достаточно низкоуровневый инструмент, чтобы его было легко заточить под требуемые задачи и достаточно высокоуровневый, чтобы им было удобно пользоваться без дополнительных обёрток.

Настройка ядра

Казалось бы, никакая специальная настройка ядра не должна требоваться. Но оказывается, что Linux развивается достаточно быстро, и если вам требуется запускать в контейнере дистрибутивы примерно десятилетней давности, вам потребуется включить кое-какие режимы совместимости в ядре. Например для работы в современных системах RHEL 7 и ему подобынх потребуется параметр systemd.unified_cgroup_hierarchy=0 ещё кому-то требовался cgroup_enable=memory, а дистрибутивам примерно 12-летней давности - RHEL 6 и SLES 11 требовался параметр vsyscall=emulate. (последний скорее всего требуется и для запуска программ из этих дистрибутивов в chroot, но я не пробовал).

Это связано с тем, что системы, выполняющиеся в контейнерах используют то же ядро, что и хост-система. Кроме того системы контейнеризации используют механизм cgroup для разделения ресурсов между хост-системой и контейнером, и этот же механизмо используется systemd. Если у вас на хост-системе systemd относительно новой версии, то это ещё ничего, а вот если на хосте используется другая init-система, а в контейнерах надо запускать systemd, то могут потребоваться некоторые ручные действия по настройке хоста.