LaurVas

Запускаем Debian в контейнере systemd-nspawn

debian, linux

Linux-контейнер — это линукс внутри линукса. Примерно как виртуализация (VirtualBox, Qemu), только без эмуляции железа. Для каких задач могут пригодиться контейнеры?

  • Когда хочется установить временно какой-то софт, не захлямляя основную ОС. Сделал контейнер, поработал, сохранил результат, удалил контейнер. Мусора в системе не осталось.

  • Когда нужна более свежая или наоборот, устаревшая версия той или иной программы/библиотеки.

  • Когда нужного софта нет в репозиториях вашего дистрибутива, зато есть в репозиториях другого. Например мне пару раз нужен был pdftk, который в Archlinux есть в AUR, но не хочет собираться. Я нашёл готовый пакет в Debian и быстро установил его в контейнере.

  • Когда надо потестить свежий выпуск Debian/Ubuntu/whatever.

  • Чтобы проверить работоспособность своего скрипта/приложения в другой среде, погонять тесты/бенчмарки.

  • Для сборки пакетов под другой дистрибутив.

  • Когда вам по работе нужен какой-нибудь oldstable Debian, недостойный быть основной системой на компьютере из-за плохой поддержки оборудования.

  • Для изоляции приложений. Заводим отдельный контейнер под Skype и не боимся его шпионских действий.

Эта инструкция описывает создание и использование контейнера на примере Debian Jessie. Создание контейнера с другим Linux-дистрибутивом ничем принципиально не отличается.

Нам понадобятся..

Любой дистрибутив под управлением systemd. Всё. В отличие от OpenVZ (другая система контейнерной виртуализации), для работы контейнеров не требуется кастомное ядро. В отличие от LXC… Не знаю, не смотрел LXC.

Запускать 32-битную систему можно как из 32-битной, так и из 64-битной. А вот 64-битную из 32-битной нельзя.

Для создания контейнера с Debian или Ubuntu понадобится утилита debootstrap из одноимённого пакета. Он точно есть в Debian, Ubuntu, Archlinux и Gentoo. Наверняка есть и в других дистрибутивах. В конце концов это всего лишь набор shell-скриптов.

Где размещать контейнеры?

Там, где вам удобно. У меня контейнеры лежат в /home/chroot, потому что /home вынесен на отдельный большой раздел и этот путь легко набирать на клавиатуре. А вообще стандартным местом считается /var/lib/machines/, поэтому я буду использовать его в этой инструкции.

Шаг 1. Debootstrap

Debootstrap разворачивает в указанном каталоге минимальный набор пакетов, необходимый для самостоятельной работы системы. Это самый важный шаг. Если сейчас допустить ошибку, то всё придётся переделывать сначала. Инструкцию я вынес в отдельный пост debootstrap.

Рекомендую сделать бэкап сразу после этого шага. Я устанавливаю Debian Jessie, поэтому мои бэкапы содержат jessie в названии. Команды подсмотрены в Ubuntu community help wiki.

# cd /var/lib/machines
# tar cpzf jessie-deboostrapped.tar.gz --one-file-system -C jessie .
или
# tar cpjf jessie-deboostrapped.tar.bz2 --one-file-system -C jessie .
или
# tar cpJf jessie-deboostrapped.tar.xz --one-file-system -C jessie .

Здесь jessie — это каталог с корневой ФС контейнера. У меня наименьший размер получился у .tar.xz архива.

Восстановление из бэкапа:

# cd /var/lib/machines
# rm -rf jessie && mkdir jessie
# tar xpf /path/to/backup.tar.gz -C jessie --numeric-owner
или
# tar xpf /path/to/backup.tar.bz2 -C jessie --numeric-owner
или
# tar xpf /path/to/backup.tar.xz -C jessie --numeric-owner

Шаг 2. Запуск контейнера

Команды для 32-битной и 64-битной системы соответственно:

# cd /var/lib/machines
# linux32 systemd-nspawn -D jessie
# systemd-nspawn -D jessie

Я ставлю 32-битную, т.к. она занимает меньше места и не имеет ощутимых недостатков перед 64-битной.

По умолчанию в качестве имени контейнера будет использоваться имя каталога с системой. Если нужно другое, то его необходимо указать после ключа -M. Имя будет использоваться для идентификации и в качестве хостнэйма.

Если systemd-nspawn ругается на machine ID и не стартует, просто создаём пустой файл:

# touch jessie/etc/machine-id
# linux32 systemd-nspawn -D jessie

Шаг 3. Первичная настройка системы

Все команды, которые здесь отмечены приглашением (chroot)#, надо вводить в контейнере.

Задаём пароль рута:

(chroot)# passwd

Устанавливать часовой пояс не надо, он возьмётся с хостовой системы. Имя хоста тоже задавать не надо.

Чтобы df корректно работал, нужно сделать mtab:

(chroot)# cat >/etc/mtab <<EOF
rootfs / rootfs rw 0 0
EOF

Сеть должна работать без дополнительных манипуляций. Проверяем:

(chroot)# apt update

Устанавливаем локаль:

(chroot)# apt install locales

(chroot)# echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
(chroot)# locale-gen
(chroot)# update-locale LANG=en_US.UTF-8
  • Вы можете не использовать echo, а раскомментировать необходимые локали в /etc/locale.gen вручную.
  • Вместо en_US.UTF-8 можно прописать ru_RU.UTF-8, тогда всё будет по-русски.
  • Последние 3 команды можно заменить интерактивной dpkg-reconfigure locales.

Проверяем список доступных локалей и текущую локаль:

(chroot)# locale -a
(chroot)# locale

Локаль у рута почему-то остаётся пустой, даже если перезайти в контейнер. Значит придётся вручную объявить переменную окружения

(chroot)# echo "export LANG=en_US.UTF-8" >> ~/.profile
(chroot)# source ~/.profile

Конфигурируем менеджер пакетов

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

/etc/apt/sources.list для Debian Squeeze
deb http://archive.debian.org/debian squeeze main contrib non-free
deb http://archive.debian.org/debian-security squeeze/updates main contrib non-free
deb http://archive.debian.org/debian squeeze-lts main contrib non-free
# deb http://archive.debian.org/backports.org squeeze-backports main contrib non-free
/etc/apt/sources.list для Debian Wheezy
deb http://mirror.yandex.ru/debian wheezy main contrib non-free
deb http://mirror.yandex.ru/debian-security wheezy/updates main contrib non-free
deb http://mirror.yandex.ru/debian wheezy-updates main contrib non-free
# deb http://mirror.yandex.ru/debian wheezy-backports main
/etc/apt/sources.list для Debian Jessie
deb http://mirror.yandex.ru/debian jessie main contrib non-free
deb http://mirror.yandex.ru/debian-security jessie/updates main contrib non-free
deb http://mirror.yandex.ru/debian jessie-updates main contrib non-free
# deb http://mirror.yandex.ru/debian jessie-backports main
/etc/apt/sources.list для Ubuntu Precise Pangolin
deb http://mirror.yandex.ru/ubuntu precise main multiverse restricted universe
deb http://mirror.yandex.ru/ubuntu precise-security main multiverse restricted universe
deb http://mirror.yandex.ru/ubuntu precise-updates main multiverse restricted universe
/etc/apt/sources.list для Ubuntu Trusty Tahr
deb http://mirror.yandex.ru/ubuntu trusty main multiverse restricted universe
deb http://mirror.yandex.ru/ubuntu trusty-security main multiverse restricted universe
deb http://mirror.yandex.ru/ubuntu trusty-updates main multiverse restricted universe
/etc/apt/sources.list для Ubuntu Xenial Xerus
deb http://mirror.yandex.ru/ubuntu xenial main multiverse restricted universe
deb http://mirror.yandex.ru/ubuntu xenial-security main multiverse restricted universe
deb http://mirror.yandex.ru/ubuntu xenial-updates main multiverse restricted universe
/etc/apt/sources.list для Kali Sana
deb http://old.kali.org/kali sana main contrib non-free
deb http://old.kali.org/kali-security sana/updates main contrib non-free
/etc/apt/sources.list для Kali Rolling
deb http://http.kali.org/kali kali-rolling main non-free contrib

Спортсмены отключают установку рекомендованных пакетов:

(chroot)# echo 'APT::Get::Install-Recommends "false";' >>/etc/apt/apt.conf
(chroot)# echo 'APT::Get::Install-Suggests "false";' >>/etc/apt/apt.conf

Мне нравится устанавливать и обновлять пакеты без подтверждения Do you want to continue? [Y/n]. Ведь если я приказал установить пакет, значит я в этом уверен и не надо переспрашивать. К тому же так быстрее получается. Отключаем подтверждение:

(chroot)# echo 'APT::Get::Assume-Yes "true";' >>/etc/apt/apt.conf

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

Чтобы сервисы не стартовали сразу после установки пакетов:

(chroot)# cat > /usr/sbin/policy-rc.d <<EOF
#!/bin/sh
exit 101
EOF
(chroot)# chmod a+x /usr/sbin/policy-rc.d

Самое время сделать второй бэкап:

# tar cpJf jessie-configured.tar.xz --exclude='./var/cache/apt/archives/*.deb' \
--exclude='./var/lib/apt/lists/*' --exclude='./var/cache/apt/*.bin' \
--one-file-system -C jessie .

Чтобы архив занимал меньше места, мы исключаем: /var/cache/apt/archives/*.deb — кэш deb-пакетов apt’а
/var/lib/apt/lists/* — сохранённые индексы репозиториев (что лежит в таком-то репозитории)
/var/cache/apt/*.bin — какая-то база данных apt’а

Шаг 4. Доведение до ума

Дополнительные пакеты, которые могут понадобиться:
build-essential для сборки deb-пакетов
cron — планировщик задач
file — утилита для определения типа файла
less — утилита просмотра текста с возможностью скроллить вверх и вниз
logrotate
nano — простенький консольный текстовый редактор
ncurses-term для поддержки терминала rxvt-unicode-256color и других
openssh-server
rsync — мощная утилита синхронизации файлов и каталогов, в т.ч. по сети
sudo
vim или vim-tiny
zsh

Если контейнер должен уметь сам себе сконфигурировать сетевой интерфейс, то надо установить пакеты для настройки сети:
dhcpcd позволит получать настройки сети автоматически по протоколу DHCP
iproute — набор утилит для управления сетевыми подключениями

Напоминаю, что посмотреть описание пакета можно командой

(chroot)# apt show ПАКЕТ
или
#linux32 systemd-nspawn -D jessie apt show ПАКЕТ

По умолчанию при старте контейнера запускается bash --login от рута, независимо от установленного шелла. Можно указать команду для запуска в качестве аргумента:

# linux32 systemd-nspawn -D jessie /usr/bin/zsh

Писать такую команду каждый раз вам быстро надоест, поэтому применим хак:

(chroot)# echo "exec zsh --login" >> ~/.profile

Создаём пользователя vas, входящего в группу sudo. Вы можете создать другого :)

(chroot)# useradd -m -g users -G sudo -s /usr/bin/zsh vas
(chroot)# passwd vas

Переключение с рута на пользователя делается командой:

(chroot)# su - vas

Иногда бывает удобно сделать sudo без ввода пароля. На хостовой системе я себе таких шалостей не позволяю, а в контейнере можно. Не редактируйте /etc/sudoers напрямую! Используйте команду visudo.

Разрешаем sudo без пароля для всех пользователей, входящих в группу sudo:

/etc/sudoers
%sudo    ALL=(ALL:ALL) NOPASSWD:ALL

В качестве вишенки на торте возьмём zshrc, vimrc и т.п. с хостовой системы.

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

(chroot)# rm -rf /usr/share/doc/*
(chroot)# find /usr/share/locale -maxdepth 1 -mindepth 1 ! -name en_US -exec rm -rf {} \;
(chroot)# find /usr/share/i18n/locales -maxdepth 1 -mindepth 1 ! -name en_US -exec rm -rf {} \;
(chroot)# rm -rf /usr/share/man/*
(chroot)# rm -rf /usr/share/groff/*
(chroot)# rm -rf /usr/share/info/*
(chroot)# rm -rf /usr/share/lintian/*
(chroot)# rm -rf /usr/include/*

И последний бэкап на сегодня:

# tar cpJf jessie-vas.tar.xz --exclude='./var/cache/apt/archives/*.deb' \
--exclude='./var/lib/apt/lists/*' --exclude='./var/cache/apt/*.bin' \
--one-file-system -C /var/lib/machines/jessie .

Всё, контейнер jessie готов к бою! А благодаря бэкапам, в любой момент можно откатиться к чистому состоянию или сделать ещё несколько однотипных контейнеров.

Хорошо бы добавить в статью:

  • отдельная сеть для контейнера,
  • запуск графических приложений,
  • как получить второй терминал,
  • инициализация контейнера и запуск сервисов.