ZFS, философское
Я тут в процессе установки stretch в позу root on ZFS произвел некоторое переосмысление привычек управления файловой системой, и хочу поделиться. Если кто-нибудь напишет что-нибудь умное в ответ, как концептуальное, так и практическое, я как минимум с благодарностью прочитаю, а если будет что обсудить, то и обсужу. Извините, многабукф.
В качестве материала были скрипт Hajo Noerenberg https://github.com/hn/debian-stretch-zfs-root и вики-статья Richard Laager https://github.com/zfsonlinux/zfs/wiki/Debian-Jessie-Root-on-ZFS
Я так понимаю, статья первична, она с тех пор правилась неоднократно. Работа Нёренберга ценна в первую очередь тем, что некоторые технические, но важные моменты из вики-статьи там доведены до работающего кода. Или не работающего, если автору не свезло его протестировать :), я ему push request отправил. Некоторые из них действительно надо скриптовать, потому что шансов ошибиться там немало. Однако, в плане выделения файловых систем он, на мой взгляд, пошел по пути простому, но неправильному.
Лаагер, создавая файловые системы в пуле, явно говорит: "я стараюсь отделить систему от пользовательских данных, чтобы, если потребовалось после неудачного апгрейда откатывать /, не откатился заодно /var/log, где могут быть логи неудач". Да, /var/log он рассматривает как пользовательские данные.
И вот тут я словил осознание. В ZFS комбинация дешевых снапшотов и отсутствия неизбежно зарезервированного места для FS (недоступного другим FS) дала больше, чем каждое из них по отдельности. По отдельности второе тривиально (вообще не делить FS), а первое есть, например, в LVM. Но вместе они дают возможность плодить много файловых подсистем каждая со своей политикой резервирования и восстановления. И сразу начинаешь иначе оценивать, что с чем стоит объединять, а что нет.
Добавляя к философии практику, он
- При создании пула dataset'у пула прописывает canmount=off
mountpoint=/. Штатное употребление canmount=off, очень удобно для
наследования точек монтирования у подсистем. Нёренберг в скрипте не
использует этого, а зря. Отмечу как важный момент.
- Делает по образцу и для возможной будущей совместимости с утилитами
исходного соляриса контейнер rpool/ROOT (mountpoint=none, кажется) для
корневых FS и внутри него уже создает rpool/ROOT/debian с mountpoint=/ и
canmount=noauto — типа, монтирует его initrd. Мне canmount=noauto в этом
месте не понравилось — если придется пул импортировать со стороны, придется
цеплять его в три команды, а не в одну, и это если не забыть сразу
импортировать с -N. С другой стороны, это пока у нас там один рут, а
остальные — снапшоты. Как я подозреваю, в исходном солярисе их реально может
быть несколько. У нас потенциально тоже ничто не мешает, и даже иметь их от
разных дистрибутивов, а при некоторой аккуратности и разных ОС. Тогда noauto
станет нужным.
- Делает rpool/var немонтируемым (canmount=off) dataset'ом с
exec=off. Т.е. поддиректории /var сами по себе расположены в
rpool/ROOT/debian, кроме явно созданных датасетов. Тут с точки зрения
администрирования интересно exec=off, которое потом отдельно оверрайдится
для rpool/var/tmp, а остальные датасеты наследуют. Хотелось бы понять, есть
ли в этом смысл с учетом того, что изрядная часть /var расположена в
корневом датасете, где exec вполне себе yes. И для /var/lib/dpkg/info,
например, он нужен. А если идти дальше, то, например, LXC формирует образы
систем тоже под /var/lib. Понятно, что для них отдельные датасеты, но в них,
опять, exec надо. Тут вот нужен бы некий практический опыт на тему того, что
еще у нас живет под /var, и нужно ли ему exec.
- Выделяет из rpool/var rpool/var/tmp, rpool/var/log, rpool/var/spool,
rpool/var/cache. Последнему отключает автоматические снапшоты, что
логично. Не вполне понятно, забыл он отключить их спулу, или намеренно не
отключил. К спулу в гораздо большей степени, чем к кэшу, относится "это из
бэкапа/снапшота не восстанавливают". Если восстановление кэша хотя бы
безвредно, то спула — вредно. С занудной кочки зрения, снапшоты могут
оказаться полезны для разбирательства, фиг ли письмо доставлено через две
недели, но этого проще превентивно добиться тупо анализом возраста файлов в
спуле. Я у себя спулу автоснапшоты тоже выключил. Для rpool/var/tmp включает
exec=yes, оно без этого не живет. /tmp, к сожалению, тоже. Лаагер /tmp не
выделяет, Нёренберг выделяет, и я у себя тоже выделил. setuid у Лаагера,
кажется, не отключается. Нёренберг отключает для rpool/var/tmp, но почему-то
не для tmp, если я правильно помню. Я для обеих tmp отключил — там exec
включен и world-writable.
- Говорит о выделении таких мест, как /var/lib/postgresql, если он у кого есть
(а где-то мне попадалось выделение еще и отдельного датасета под его xlog,
там типа другие настройки), /var/games, если у кого стоят игры,
/var/lib/nfs, у кого используется NFS, для локов, и т.п. Короче, о
художественном выпиливании лобзиком по /var и /var/lib.
- Выделяет rpool/home с setuid=off. В нем создает rpool/home/root с
mountpoint=root. Есть ли смысл домик рута держать в том же датасете, у меня
большие сомнения. В нем и setuid запрещать смысла нет, и под общую квоту,
если она будет выставлена на весь /home, тоже загонять его не стоит.
- Что интересно: после установки системы делает zfs set devices=off rpool. Все
остальные наследуют. Интересно тем, что реальный /dev и прочие места, где
штатно создаются устройства, у нас на tmpfs, и даже на / создавать
устройства нужно только во время debootstrap для заполнения статического
/dev, а потом оно даже там не нужно.
На выходе я сделал для себя вывод, что если админ хочет иметь возможность
отката к прежнему состоянию системы после неудачного апгрейда, ему нужно
хорошо представлять, какие задачи и где решает система. Особенно художественна
эта задача для /var и /var/lib. Если кто еще сможет что умное сказать про то, как под разные задачи следует там выпиливать, будет ценно.
Нёренберг в этом месте пошел по пути простому, но неправильному. Выделил
rpool/var и rpool/var/tmp, и не справившись с управлением (дело было еще на
jessie), решил, что их надо монтировать через fstab, типа иначе слишком
поздно. Лаагер без этого обходится, но rpool/var у него не выделена. Я,
однако, напарывался, что в каких-то ситуация (race condition?) под /tmp,
/var/log и даже, кажется, /var/cache кто-то успевает создать поддиректории до
монтирования датасетов, и zfs mount ругается. Но не всегда. Вот при переносе
винта из машины, где шел начальный сетап, на машину, где ему потом стоять,
налетел. А после ручной чистки и перезагрузки взлетело.
Рискну предположить, что у варианта Нёренберга не столько монтирование происходит раньше, сколько mount по умолчанию легко монтирует новую FS поверх непустой директории, а ZFS по умолчанию нет. Что аккуратнее, но менее надежно. Вот и думаю: то ли разрешить некоторым датасетам монтироваться поверх непустого дерева (есть у ZFS и такая настройка), то ли разбираться, какая зараза успевает их создать, когда что-то идет не так...
Как я уже писал, в stretch скриптов для ZFS для sysvinit нет вообще, а юниты
для systemd, _казалось бы_, написаны правильно, но вот опыт с неудачным
взлетом (как минимум два раза в процессе установки и настройки конструкции)
намекает нам, что авотфиг. Какие-то шибко умные подсистемы таки поднимаются в
параллель, и иногда успевают раньше.
Отдельный вопрос к опытным гражданам: я правильно понимаю, что никакого
способа аккуратно экспортировать пул, на котором корень, не существует? Вроде
как ключ force у zpool export есть, но с этой ситуацией он не
справляется. Хотя, казалось бы, выпили его из файла кэша (оный файл на оном же
корне по умолчанию расположен), установи датасет (да хоть все датасеты пула) в
readonly, и экспортируй на здоровье. Так нет...
Reply to: