Пользовался я контейнерами и бед не знал. Но вот понадобилось как-то собрать пакет openjdk-6. А он при сборке ругается на ядро: uname -a
ему не нравится. Говорит не буду такое поддерживать. Значит надо собирать на железе или виртуалке. Устанавливать систему с нуля не хотелось — слишком долго. И тогда я подумал, что можно развернуть бэкап контейнера на диск, добавить загрузчик и ядро и получить Debian, способный работать самостоятельно. А чтобы не выполнять одну и ту же работу дважды, я решил работать не на диске, а в образе, и сохранить его на будущее.
Эта инструкция подойдёт для всех релизов Debian и Ubuntu. Я буду переносить Debian Jessie.
Первым делом создадим образ диска. Простой, надёжный, и 100% рабочий вариант — использовать для выделения места утилиту dd. Она создаст файл заданного размера, заполнив пространство на диске нулями. Более быстрый способ — попросить файловую систему зарезервировать пустое место под файл командой fallocate
. Она работает только на тех файловых системах, которые поддерживают разреженные (sparse) файлы: btrfs, ext4, ocfs2, xfs. Универсальная утилита — truncate
. Судя по её поведению, она создаёт разреженный файл там, где это возможно, и заполняет нулями там, где нет.
Много места не нужно, лишь бы контейнер уместился с запасом. Я делаю 4 ГиБ.
# truncate -s 4096M jessie.img
Для лучшего понимания происходящего я подготовил схему. Это наш пустой образ:
Натягиваем jessie.img
на первое свободное петлевое устройство:
# losetup -f jessie.img --show
Дальше работаем с петлевым устройством как с обычным диском. Создаём таблицу разделов и первый раздел:
# parted -s /dev/loopX mklabel msdos
# parted -s /dev/loopX -- mkpart primary ext2 1MiB -1s
Вместо loopX
должно быть loop0
, loop1
, loop2
и т.д, в зависимости от того, куда натянулся jessie.img
.
Первая команда создаёт таблицу разделов MBR (не GPT). Вторая команда создаёт раздел, смещённый относительно начала диска ровно на 1 МиБ (т.н. “выравнивание” раздела) и заканчивающийся последним сектором (-1s
). Нумерация секторов начинается с нуля. Ключ --
означает, что дальше аргументов не будет и нужен для правильного восприятия -1s
.
После разметки имеем следующую картину:
Информация о разделе записана в таблицу разделов, но файловой системы на нём ещё нет. ext2
во второй команде — это только тип раздела — однобайтный идентификатор, записываемый в таблицу разделов MBR. У файловых систем ext2/3/4 идентификатор одинаковый и равен 0x83.
Хорошая подсказка по идентификаторам есть в утилите fdisk
:
# echo l | fdisk /dev/sdX
Проверяем что всё разметилось правильно:
# fdisk -lu /dev/loopX
Диск /dev/loopX: 4 GiB, 4294967296 байт, 8388608 секторов
Единицы: секторов по 1 * 512 = 512 байт
Размер сектора (логический/физический): 512 байт / 512 байт
Размер I/O (минимальный/оптимальный): 512 байт / 512 байт
Тип метки диска: dos
Идентификатор диска: 0x55242bd0
Устр-во Загрузочный начало Конец Секторы Размер Идентификатор Тип
/dev/loopXp1 2048 8388607 8386560 4G 83 Linux
Что тут надо увидеть:
- Общий размер диска в секторах — 8388608.
- Начальный сектор раздела — 2048. Нумерация секторов начинается с нуля (как в сишных массивах), значит перед разделом есть 2048 / 2 / 1024 = 1МиБ.
- Конечный сектор раздела — 8388607. Это на единицу меньше, чем общий размер диска, значит это действительно последний сектор.
После разметки должен появиться /dev/loopXp1
, проверяем командой lsblk
. Если не появился, то перенатягиваем образ:
# losetup -d /dev/loopX
# losetup -f -P --show jessie.img
Форматируем наш раздел. В качестве файловой системы будем использовать Ext4. Это хорошая, стабильная файловая система. Я вижу два существенных преимущества Ext4 по сравнению с Ext2 и Ext3:
- возможность эффективного хранения разреженных (sparse) файлов,
- поддержка ATA-команды TRIM.
# mkfs.ext4 -m 1 /dev/loopXp1
или
# mkfs.ext4 -m 1 -O ^has_journal /dev/loopXp1
-m 1
— зарезервировать 1% пространства под нужды суперпользователя (по умолчанию 5%).
-O ^has_journal
отключает журналирование на ФС. Отсутствие журнала сокращает нагрузку на диск, но может привести к нарушению целостности ФС при внезапном отключении питания. Короче, штука несколько сомнительная, используйте на свой страх и риск.
Наш образ после форматирования:
Монтируем файловую систему, чтобы получить к ней доступ. Каталог /mnt/t
выбран только лишь потому, что его быстро набирать на клавиатуре.
# mkdir /mnt/t && mount /dev/loopXp1 /mnt/t
Распаковываем имеющийся бэкап контейнера:
# tar xpf jessie-vas.tar.xz -C /mnt/t --numeric-owner
Монтируем всё необходимое и чрутаемся внутрь:
# cd /mnt/t
# mount proc -t proc ./proc
# mount sys -t sysfs ./sys
# mount --bind /dev ./dev
# mount --bind /dev/pts ./dev/pts
# chroot ./ /usr/bin/env -i HOME=/root TERM="$TERM" /bin/bash --login
Дальше по инструкции устанавливаем часовой пояс, задаём имя хоста, создаём fstab, устанавливаем ядро и загрузчик. Отличие только в том, что вместо физического диска /dev/sdX
у нас /dev/loopX
, а первый раздел обозначается не /dev/sdX1
, а /dev/loopXp1
.
Устанавливаем пакеты для настройки сети и ssh-сервер для удобства:
(chroot)# apt-get install dhcpcd iproute openssh-server
Удаляем лишнее:
(chroot)# rm /etc/mtab /etc/resolv.conf /usr/sbin/policy-rc.d
Размонтируем всё:
(chroot)# exit
# cd
# umount /mnt/t/{proc,sys,dev/pts,dev,}
Наш образ после переноса контейнера:
Важное замечание для 32-битных систем. Если у файловой системы включена опция 64bit, то 32-битное ядро не подружится с ней. Проверяем:
# tune2fs -l /dev/loopXp1 | grep features
Отключаем при необходимости:
# e2fsck -f /dev/loopXp1
# resize2fs -s /dev/loopXp1
Теперь маленький трюк для экономии места. Уменьшаем (shrink) файловую систему:
# resize2fs -M /dev/loopXp1
resize2fs 1.43.3 (04-Sep-2016)
Resizing the filesystem on /dev/loopXp1 to 96809 (4k) blocks.
The filesystem on /dev/loopXp1 is now 96809 (4k) blocks long.
Новый размер ФС (96809 блоков по 4 КиБ) надо запомнить. Нам предстоит уменьшить раздел до этого размера. Для этого надо правильно вычислить номер последнего сектора: 2048 + 96809*8 - 1 = 776519.
# parted /dev/loopX resizepart 1 776519s
После уменьшения раздела из 8388608 секторов диска осталось занято 776520 (примерно 380 МиБ). Место в конце никак не используется.
# fdisk -lu /dev/loopX
Диск /dev/loopX: 4 GiB, 4294967296 байт, 8388608 секторов
Единицы: секторов по 1 * 512 = 512 байт
Размер сектора (логический/физический): 512 байт / 512 байт
Размер I/O (минимальный/оптимальный): 512 байт / 512 байт
Тип метки диска: dos
Идентификатор диска: 0x55242bd0
Устр-во Загрузочный начало Конец Секторы Размер Идентификатор Тип
/dev/loopXp1 2048 776519 774472 380.3M 83 Linux
Отключаем образ от петлевого устройства и уменьшаем до нужного размера:
# losetup -d /dev/loopX
# truncate -s $((774473 * 512)) jessie.img
И напоследок сжимаем архиватором:
# xz jessie.img
На этом основная работа закончена. Мы получили сжатый образ jessie.img.xz
, который в любой момент можно развернуть на флэшку или виртуальный диск и загрузить настроенный Debian. Благодаря уменьшению ФС образ занимает минимум места и влезет даже на флэшку в 512 MB.
Записываем образ на флэшку или диск /dev/sdX
, предварительно убедившись, что там нет ничего ценного!
# xzcat jessie.img.xz | dd of=/dev/sdX bs=1M
Растягиваем ранее сжатый раздел на весь размер диска:
# parted -s /dev/sdX -- resizepart 1 -1s
# e2fsck -f /dev/sdX1
# resize2fs /dev/sdX1
Всё, можно загружаться с /dev/sdX
.
Если нужен виртуальный диск для виртуальной машины, то сначала увеличиваем образ до нужного размера, затем преобразуем его в один из форматов: vdi, vmdk или vhd:
# xz --decompress --keep jessie.img.xz
# truncate -s 1G jessie.img
# losetup -f -P --show jessie.img
# parted -s /dev/loopX -- resizepart 1 -1s
# e2fsck -f /dev/loopXp1
# resize2fs /dev/loopXp1
# losetup -d /dev/loopX
# vboxmanage convertfromraw --format VDI jessie.img jessie.vdi
Да, openjdk6 я в итоге успешно собрал.