Перейти к основному содержимому

Обновление до 3.2, Center старой линейки: шаги

Шаги идут строго по порядку. В каждом: где выполнять, команда, что должно получиться. Если результат другой — СТОП: дальше не идти, записать, что видно, обратиться в техническую поддержку vStack. Почему шаги такие и что делать при сбоях — в «Справочнике».

Где выполнять:

  • [раб] — рабочая машина инженера (нужны sh, ssh, curl, jq); команды — из каталога, где распакован архив со скриптами (шаг 2);
  • [mgmt] — mgmtVM по ssh;
  • [узел] — узел по ssh. У root на узле оболочка csh, а команды написаны для sh: войдя на узел, сначала наберите sh.

Обозначения:

  • <N> — номер узла в ccrd_cli; zNN — пул; <MGMT> — внешний адрес mgmtVM;
  • <OWN> — узел, на котором сейчас пул с mgmtVM: тот, где zfs list | grep '/.vm_V4/000000000$' что-то выводит;
  • <vxlan> — первые три октета подсети VXLAN кластера (vxlan_vlan_network в /usr/local/etc/vstack-env.conf, например 192.0.2);
  • <узлы> — адреса управления всех узлов через пробел.

Подготовка — за 1–3 дня, без простоя​

1. Кластер подходит [узел], [mgmt]​

freebsd-version -k # [узел] 14.2-RELEASE-p2
pgrep -lf '^/usr/local/bin/(spdk|nvmf)_tgt' # [узел] пусто
grep -E 'vxlan_vlan_(id|network)' /usr/local/etc/vstack-env.conf # [узел] VLAN и подсеть VXLAN
ifconfig vlan40 | grep 'inet ' # [узел] адрес узла в подсети VXLAN
freebsd-version -u # [mgmt] 14.1-…
pkg query '%n-%v' vStack-control-pane postgresql14-server # [mgmt] обе строки

Должно получиться: узлы 3.0, spdk_tgt и nvmf_tgt не запущены, Center старой линейки; у узла есть интерфейс vlan40 с адресом из vxlan_vlan_network, подсеть — /24. Интерфейс называется vlan40 на всех кластерах (его имя задаёт инсталлятор), номер VLAN на коммутаторе — vxlan_vlan_id. Запишите <vxlan>. Узлы 2.x, mgmtVM на FreeBSD 13, уже работающая фабрика, нет vlan40 или подсеть не /24 — СТОП, это другой план.

2. Скрипты и учётка [раб]​

curl -O https://docs.vstack.com/os/3.2.0/files/upgrade-3.0-to-3.2/upgrade-3.0-to-3.2-tools.tar.gz
curl -O https://docs.vstack.com/os/3.2.0/files/upgrade-3.0-to-3.2/SHA256SUMS
grep tools.tar.gz SHA256SUMS | shasum -a 256 -c -
tar xzf upgrade-3.0-to-3.2-tools.tar.gz
printf '%s\n%s\n' '<логин cluster master>' '<пароль>' > center.cred; chmod 600 center.cred
export VAPI_URL=https://<MGMT> VAPI_CRED=$PWD/center.cred
export NODES="<узлы>" KEY=~/.ssh/<ключ для узлов>
sh tools/vapi.sh vm-list '{}' | jq '.result.data | length'
sh tools/dupcheck.sh # DUPLICATES: none

Должно получиться: OK, каталоги tools/ и upgrade-to-3.2-r2/, число ВМ кластера.

Дальше используются: sh tools/vapi.sh vms-stop '{"id":<id>,"hard_timeout":90}', sh tools/vapi.sh vms-start '{"id":<id>,"node_id":<id узла в Center>}', sh tools/vapi.sh wait <id> <1|3> (ждёт, пока ВМ остановится или запустится) и sh tools/dupcheck.sh (ищет ВМ, запущенные дважды; должно быть DUPLICATES: none). Эти переменные export действуют до закрытия терминала — в новом терминале повторить.

Ещё нужны: ssh root по ключу на все узлы и mgmtVM, консоль BMC (iKVM) каждого узла, консоль mgmtVM в обход Center (на <OWN> — cu -l /dev/nmdm0B), на рабочей машине — клиент PostgreSQL 14 или новее (pg_restore, шаг 12).

3. Доступ к раздаче [узел], [mgmt]​

fetch -T 10 -qo /dev/null https://repo.vstack.com/os/3.2.0/update.sh && echo os-OK # [узел], каждый
fetch -T 10 -qo /dev/null https://repo.vstack.com/center/3.2.0/repo/meta.conf && echo center-OK # [mgmt]

Нет OK — заявка в техническую поддержку vStack на доступ с адресов выхода узлов и mgmtVM. Без него работы невозможны.

4. Список ВМ [mgmt], [раб]​

cd /tmp
su -m postgres -c "psql -X -v ON_ERROR_STOP=1 -d vstack" > /root/inventory-pre-center32.csv <<'SQL'
COPY (select m.id, m.name, m.status as wanted, sm.status_id as actual, m.rhost, m.pool, m.os_type, p.id as profile_id, p.name as profile, p.zvol, p.guest_customization
from vm.machine m left join vm.status_map sm on sm.vm_id = m.id left join vm.os_profile p on p.id = m.os_profile order by p.zvol, m.id)
TO STDOUT WITH (FORMAT CSV, DELIMITER ';', HEADER true);
SQL
echo rc=$?

[раб]: scp root@<MGMT>:/root/inventory-pre-center32.csv .

Колонка actual: 3 — работает, 1 — остановлена, 11 — приостановлена. В конце работ состояния должны совпасть.

5. Драйвер NVMe в Linux-гостях​

После обновления Center все ВМ, кроме Windows, получают диск NVMe. Гости из образов ниже без драйвера nvme в initramfs не загрузятся. [раб] — список таких ВМ:

Z='OracleLinux-8.6-x86_64-v2|OracleLinux-8.6-x86_64-v0|vStack-OracleLinux-8.3-x86_64-v0|vStack-OracleLinux-8.5-x86_64-v0|OracleLinux-9.0-x86_64-v0|OracleLinux-9.1-x86_64-v0|vStack-CentOS-7.9-x86_64-v1|vStack-CentOS-8.3-x86_64-v0|vStack-CentOS-7-x86_64-cloud.raw|vStack-CentOS-packer-x86_64.raw|vStack-CentOS-8.1-x86_64-v1.raw|vStack-CentOS-8.2-x86_64-v0.raw|vStack-almalinux-8.7-v0|AlmaLinux-10.1-amd64-v0|vStack-RedOS-7.3-x86_64-v2'
awk -F';' -v z="\\.vm_images/($Z)\$" 'NR>1 && $10 ~ z {print $1";"$2";"$4";"$10}' inventory-pre-center32.csv > nvme-linux.csv; wc -l nvme-linux.csv

В каждой работающей ВМ списка — с согласия владельца, перезагрузка не нужна:

echo 'add_drivers+=" nvme nvme_core "' > /etc/dracut.conf.d/90-vstack-nvme.conf
dracut -f --regenerate-all
lsinitrd /boot/initramfs-$(uname -r).img | grep -c '/nvme.ko' # больше 0

Каждой ВМ списка — отметка «исправлена» или «не исправлена» (нет согласия, ВМ остановлена). Неисправленные в шаге 37 вернутся на virtio-blk.

6. Проверки узлов [раб], каждый узел​

ssh root@<узел> 'sh -s' <<'EOF'
command -v jq >/dev/null || echo "нет jq"
for p in $(zpool list -H -o name); do for d in /pools/$p/.vm_V4/0*; do
id=$(basename $d | sed 's/^0*//'); [ -n "$id" ] || continue
[ -f $d/vm.json ] && [ ! -e /pools/$p/.vm_V4_state/$id.state ] && echo "$p $id: нет ссылки состояния"
done; done
for p in $(zpool list -H -o name); do for d in /pools/$p/.vm_V4/0*; do
v=$(zfs get -Hp -o value volsize ${d#/pools/}/snapshot.bvmss 2>/dev/null); m=$(jq -r .ram $d/vm.json 2>/dev/null)
[ -n "$v" ] && [ -n "$m" ] && [ "$v" -lt "$m" ] && echo "$d: snapshot.bvmss $v < ram $m"
done; done
pkg info -q cdrkit-genisoimage || echo "нет cdrkit-genisoimage"
grep -n -A3 'inputs.smart' /usr/local/etc/telegraf.conf
EOF
ВыводЧто сделать
нет jqpkg install -y jq и повторить
нет ссылки состояниязаписать номер ВМ: после остановки она не запустится. Сейчас ничего не делать; ссылку создать в шаге 19, когда ВМ остановлена: ln -s /pools/<zNN>/.vm_V4/<9 цифр>/vm.json /pools/<zNN>/.vm_V4_state/<id>.state
snapshot.bvmss … < ram …эту ВМ нельзя перенести с приостановкой — только с остановкой
нет cdrkit-genisoimagepkg install -y -r FreeBSD cdrkit-genisoimage
в [[inputs.smart]] есть строка path = …заменить на path_smartctl = "/usr/local/sbin/smartctl": telegraf 3.2 со старой строкой не стартует

7. Ранний микрокод [узел], каждый​

cp -p /boot/loader.conf.local /boot/loader.conf.local.pre-ucode-$(date +%Y%m%d)
sysrc -f /boot/loader.conf.local cpu_microcode_load=NO

Со строкой cpu_microcode_load="YES" узел 3.2 на части прошивок не загружается (X64 Exception). В 3.2 микрокод применяет служба при старте узла, её пакет ставится в шаге 24.

8. Куда переносить пулы [mgmt], [раб]​

su -m postgres -c "psql -At -d vstack -c 'select id, hostname, round(ram/1073741824.0) from node.node order by id'" # [mgmt] id узла в Center, имя, бюджет RAM в ГиБ
sh tools/vapi.sh vm-list '{}' | jq -r '.result.data
| (group_by(.node)[] | "узел \(.[0].node): занято \(([.[]|select(.oper_status==3)|.memory_count]|add // 0)/1073741824) ГиБ"),
(group_by(.pool)[] | "пул \(.[0].pool): нужно \(([.[]|select(.oper_status==3)|.memory_count]|add // 0)/1073741824) ГиБ")'

id из первого запроса — это node_id для vms-start; обычно он совпадает с номером узла в ccrd_cli, но сверить по имени. Для каждого узла выбрать соседа, у которого «бюджет − занято» не меньше, чем «нужно» его пулу (пул в выводе — guid, zpool get -H -o value guid zNN). Сосед должен вместить и физически: минус 8 ГиБ под SPDK (шаг 25). Не помещается — часть ВМ пула будет стоять 1–1,5 ч, согласовать с владельцами.

9. Скрипт обновления узлов [раб], каждый узел​

ssh root@<узел> 'sh -s' <<'EOF'
D=/root/upgrade-320; mkdir -p $D; cd $D || exit 1
fetch -q -o update.sh https://repo.vstack.com/os/3.2.0/update.sh || { echo FETCH-FAIL; exit 1; }
fetch -q -o update.sh.sig https://repo.vstack.com/os/3.2.0/update.sh.sig || { echo FETCH-FAIL; exit 1; }
fetch -q -o poudriere.pub https://repo.vstack.com/os/3.2.0/poudriere.pub || { echo FETCH-FAIL; exit 1; }
echo "key $(sha256 -q poudriere.pub)"
openssl dgst -sha256 -verify poudriere.pub -signature update.sh.sig update.sh
ntpq -pn | grep '^\*' || echo 'NTP: нет синхронизации'
rc=0; PUBKEY_PATH=$D/poudriere.pub sh update.sh --controllers --base --source release --version 3.2.0 --dry-run > dry.out 2>&1 || rc=$?
echo dry-rc=$rc
grep -E 'upgraded|reinstalled|to be installed|REMOVED|ERROR' dry.out | tail -20
EOF

Должно получиться: key ddf6ca993798a1120f8f5febdab293b9b2a279a888172422fbe510ccb0d82bb4, Verified OK, строка NTP со *, dry-rc=0, одинаковый план на всех узлах.

10. Скрипт обновления Center [раб]​

cd upgrade-to-3.2-r2 && scp -r mgmtvm-update root@<MGMT>:/root/ && cd ..
ssh root@<MGMT> 'sh -s' <<'EOF'
cd /root/mgmtvm-update && sha256sum -c SHA256SUMS && cp mgmtvm-update.sh mgmtvm-update-oldgen.sh /root/
sh /root/mgmtvm-update.sh --source release --version 3.2.0 > /root/mu-plan.txt 2>&1; cat /root/mu-plan.txt
sh /root/mgmtvm-update.sh --source release --version 3.2.0 --dry-run > /root/mu-dry.txt 2>&1; cat /root/mu-dry.txt
df -h /; du -sh /var/db/postgres
EOF

Должно получиться: generation old-gen, plan: app-save world(reboot) app finish, на / свободно не меньше 4 ГБ. Объём /var/db/postgres — это и время шага 34: около 35 минут на 19 ГБ.

Пароль admin в influx: если он известен, положить его в /var/db/vstack/secrets/influxdb_admin_password (каталог 0700, файл 0600). Если нет — скрипт задаст новый туда же.

11. Копия конфигурации узлов [раб], каждый узел​

ssh root@<узел> 'sh -s -- --tag 3.0-to-3.2.0 --tar' < tools/vstackos.config-backup.sh
ssh root@<узел> 'sh -s' <<'EOF' > vstackos-config-backup-<узел>.tar.gz
cat $(ls -t /root/vstackos-config-backup/*/*/vstackos-config-backup-$(hostname -s)-*.tar.gz | head -1)
EOF
mkdir -p check-<узел> && tar xzf vstackos-config-backup-<узел>.tar.gz -C check-<узел>
sh tools/vstackos.config-backup.sh --verify check-<узел>/<hostname -s узла>

Должно получиться: mismatched 0. Подробно — «Снятие конфигурации узла». На узлах 3.0 скрипт на тестовом кластере не проверялся: FAIL в выводе — в поддержку.

12. Копия Center и пароль эджей [раб]​

umask 077; TS=$(date -u +%Y%m%dT%H%M%SZ)
ssh root@<MGMT> 'cd /tmp; su -m postgres -c "pg_dump -Fc -d vstack"' > center-db-pre-center32-$TS.dump
pg_restore -l center-db-pre-center32-$TS.dump > /dev/null && echo dump-OK
ssh root@<MGMT> 'sh -c "tar -czf - /etc /usr/local/etc 2>/dev/null"' > mgmtvm-etc-pre-center32-$TS.tgz
tar -tzf mgmtvm-etc-pre-center32-$TS.tgz > /dev/null && echo tar-OK
ssh root@<MGMT> 'sh -s' <<'EOF' > edge-ctrl-pre.txt
for f in /usr/local/etc/vstack-passwords.conf /usr/local/etc/vstack-dispatcher.conf; do
[ -r $f ] || continue
printf '%s ' "$(basename $f)"; grep -iE '^[[:space:]]*edge_ctrl[[:space:]]*=' $f | head -1 | sed -E "s/^[^=]*=[[:space:]]*//; s/^[\"']//; s/[\"'][[:space:]]*\$//" | tr -d '\n' | sha256 -q
done
EOF
cat edge-ctrl-pre.txt

Должно получиться: dump-OK, tar-OK, в edge-ctrl-pre.txt хотя бы одна строка, и не e3b0c442… (это пустой пароль). Отпечаток пароля edge_ctrl сверяется в шаге 36: с другим паролем старые эджи к Center не подключатся.

13. Образ эджа v8 во все пулы [раб], каждый узел​

ssh root@<узел> 'sh -s' <<'EOF'
D=/root/edge-v8; mkdir -p $D; cd $D || exit 1
fetch -q -o edge-13.0-v1.zfs https://repo.vstack.com/images/edge-13.0/edge-13.0-v1.zfs || { echo FETCH-FAIL; exit 1; }
[ "$(sha256 -q edge-13.0-v1.zfs)" = fd27164755d8bfa44a1acf5c252d8de32ae5f07dc670921c62896151b93f93a9 ] || { echo SHA-MISMATCH; exit 1; }
for p in $(zpool list -H -o name); do
zfs list -H $p/.vm_images/vStack-edge-13.0-v8 >/dev/null 2>&1 && { echo "$p: уже есть"; continue; }
zfs receive -u $p/.vm_images/vStack-edge-13.0-v8 < edge-13.0-v1.zfs && echo "$p: OK" || echo "$p: FAIL"
done
cd /; rm -rf $D
EOF

Должно получиться: OK или уже есть у каждого пула узла.

14. Адреса фабрики в конфиге ccrd [раб]​

Фабрика работает в VLAN подложки VXLAN (vlan40), как на кластере, установленном с 3.2: новый VLAN на коммутаторах не нужен. Каждому пулу — свой адрес в этой подсети: пул zNN → <vxlan>.(200+NN). Адрес поднимает ccrd на узле, где пул, и переносит вместе с пулом. Узлы .(100+N) в этой подсети уже есть.

Конфиг ccrd одинаков на всех узлах — проверить, взять копию, добавить адреса:

for h in $NODES; do printf '%s ' $h; ssh -n root@$h 'md5 -q /usr/local/etc/ccrd/config.json'; done # у всех одинаковый
scp root@<любой узел>:/usr/local/etc/ccrd/config.json ccrd-config.orig.json
jq --arg pfx <vxlan> -f tools/nvmf-add.jq ccrd-config.orig.json > ccrd-config.new.json
jq -r '.services[] | select(.name|test("^zpool_z")) | .name as $n | .ext_ips[] | select(.label=="NVMf") | "\($n)\t\(.interface)\t\(.ip)"' ccrd-config.new.json | tee nvmf-addresses.tsv

md5 разный — СТОП: конфиги разошлись, сначала разобраться. В nvmf-addresses.tsv — по строке на пул: zpool_zNN vlan40 <vxlan>.2NN. Если у пулов уже были адреса с меткой NVMf (так ставит инсталлятор), скрипт их не трогает — в списке будут они.

Адреса свободны (ответ есть только у уже прописанных пулам):

cut -f3 nvmf-addresses.tsv | while read a; do ssh -n root@<любой узел> "ping -c 1 -t 1 $a" >/dev/null && echo "$a ЗАНЯТ"; done

Занят адрес, которого не было в исходном конфиге, — СТОП: в подсети VXLAN кто-то ещё; адреса выбирать с поддержкой.

Разложить на все узлы:

jq -e . ccrd-config.new.json >/dev/null && echo json-OK
for h in $NODES; do
ssh -n root@$h 'cp -p /usr/local/etc/ccrd/config.json /usr/local/etc/ccrd/config.json.pre-nvmf'
scp ccrd-config.new.json root@$h:/usr/local/etc/ccrd/config.json
done
for h in $NODES; do printf '%s ' $h; ssh -n root@$h 'md5 -q /usr/local/etc/ccrd/config.json'; done # у всех одинаковый, новый

ccrd не перезапускать — он прочтёт файл сам при перезагрузке узла в шаге 26. nvmf-addresses.tsv понадобится в шагах 29 и 49.

15. Фильтр портов фабрики [раб]​

cp tools/pf-fabric.conf pf-fabric-<кластер>.conf

В копии заменить ЗАМЕНИТЬ_интерфейс_фабрики на vlan40, ЗАМЕНИТЬ_подсеть_фабрики/24 на <vxlan>.0/24. Порты 7000 и 4420 к адресам узла будут пропускаться только из подсети VXLAN; трафик ВМ фильтр не затрагивает. Фильтр включается в шаге 47: порт 7000 (управление SPDK) работает без пароля, снаружи его закрывают.

16. Внешние системы​

Если ВМ на кластере создаёт внешняя система (панель самообслуживания, автоматизация) через API Center, — в её каталоге скрыть образы из шага 5 и эдж v7: новая ВМ из них после обновления не загрузится. Владельцам таких систем — быть на связи в окне B (шаг 38).

17. Уведомление​

Клиентам и в план работ:

  • окно A: ВМ пула останавливаются на время переноса пула, простой до 10 минут; пул <OWN> переносится дважды; Windows-ВМ выключаются жёстко, если владелец не выключит их сам;
  • окно B: портал и API недоступны 30–60 минут, управление ВМ — около часа; каждый эдж перезапускается, около минуты без сети за ним.

Окна A и B лучше в разные дни. Между ними кластер штатно работает на узлах 3.2 со старым Center.

Окно A — узлы, по одному, <OWN> последним​

Шаги 18–29 — для одного узла. Следующий узел — только после шага 29. На узел — 1–1,5 часа.

18. Кластер здоров [узел], [раб]​

ccrd_cli | grep -c ONLINE # [узел] = числу узлов
zpool status -x # [узел] all pools are healthy
zpool status | grep 'scan:' # [узел] нет "in progress"

[раб]: dupcheck.sh — DUPLICATES: none.

19. Освободить узел​

Пулы узла — [узел] ccrd_cli --service --command status | grep "node: *<N>;". Если в vstack-vm vm list на узле есть ВМ с пометкой remote (ВМ чужого пула) — в портале Suspend, затем Resume с выбором узла-владельца её пула.

Каждый пул узла переносится на соседа из шага 8 с остановкой ВМ (Hard Transfer) [раб], <id соседа> — id в Center:

P=<zNN>; G=$(ssh root@<узел> "zpool get -H -o value guid $P"); echo $G
sh tools/vapi.sh vm-list '{}' | jq -r --arg g "$G" '.result.data[] | select(.pool==$g and .oper_status==3) | .vm_id' > running-$P.txt
wc -l running-$P.txt
for id in $(cat running-$P.txt); do sh tools/vapi.sh vms-stop "{\"id\":$id,\"hard_timeout\":90}" >/dev/null; done
for id in $(cat running-$P.txt); do sh tools/vapi.sh wait $id 1 >/dev/null || echo "$id НЕ ОСТАНОВЛЕНА"; done

ВМ останавливать только через Center (не на узле): иначе Center запустит их снова. Windows через ~30 секунд выключается жёстко — лучше заранее попросить владельцев выключить её изнутри; Windows, которая ещё ставится (на консоли экран установки), не останавливать. НЕ ОСТАНОВЛЕНА — разобраться до переноса.

[узел] — перенос:

ccrd_cli --service --service_name zpool_<zNN> --command stop
ccrd_cli --service --command status | grep zpool_<zNN> # повторять, пока не STOPPED
ccrd_cli --service --service_name zpool_<zNN> --node_id <N соседа> --command start
ccrd_cli --service --command status | grep zpool_<zNN> # STARTED at node: <N соседа>

ВМ без ссылки состояния (шаг 6) — создать ссылку на соседе. [раб] — запуск по 4:

xargs -n 4 < running-$P.txt | while read batch; do
for id in $batch; do sh tools/vapi.sh vms-start "{\"id\":$id,\"node_id\":<id соседа>}" >/dev/null; done
for id in $batch; do sh tools/vapi.sh wait $id 3 >/dev/null || echo "$id НЕ ЗАПУЩЕНА"; done
done
sh tools/dupcheck.sh

НЕ ЗАПУЩЕНА с ошибкой -440 — соседу не хватает бюджета RAM: эти ВМ ждут возврата пула (шаг 29).

Можно вместо этого перенести пул с приостановкой ВМ (портал → Storage Resources → Transfer Pool → Soft Transfer): гости не перезагружаются. Между узлами 3.0 и 3.2 этот способ не проверялся — сначала на одном некритичном пуле, при сбое — как выше.

Узел <OWN> — пул с mgmtVM переносится только так. Остальные ВМ пула — остановить, как выше. Затем [узел] vstack-vm vm stop --id=0, ждать, пока pgrep -f '^bhyve: 0$' станет пустым, и снять снимки томов mgmtVM [раб]:

ssh root@<OWN> 'sh -s' <<'EOF'
V=$(zfs list -H -o name -t filesystem,volume | grep '/.vm_V4/000000000$')
for t in $(zfs list -H -o name -r $V | grep -v "^$V\$"); do zfs snapshot $t@pre_move_$(date +%Y%m%d) || echo "FAIL $t"; done
EOF

Перенос пула — командами ccrd_cli выше. mgmtVM стартует на соседе сама примерно через 40 секунд. Когда портал открывается — запустить ВМ пула по running-<пул>.txt.

20. Вывести узел [узел]​

ccrd_cli --node --node_id <N> --command disable
pgrep -lf '^bhyve: [0-9]'; zpool list # пусто / no pools available

Не пусто — СТОП: на узле ещё есть ВМ или пул.

21. Обновить [раб]​

ssh root@<узел> 'sh -s' <<'EOF'
cd /root/upgrade-320 || exit 1
cat > run.sh <<'S'
#!/bin/sh
cd /root/upgrade-320
PUBKEY_PATH=/root/upgrade-320/poudriere.pub sh update.sh --controllers --base --source release --version 3.2.0 --no-restart --log /root/upgrade-320/update.log > update.out 2>&1
echo $? > update.rc
S
chmod 700 run.sh; rm -f update.rc; daemon -f /root/upgrade-320/run.sh; echo started
EOF

Ждать, пока появится update.rc (1–2 минуты):

until ssh -n root@<узел> 'test -f /root/upgrade-320/update.rc'; do sleep 10; done; echo done

22. Проверить до перезагрузки [раб]​

ssh root@<узел> 'sh -s' <<'EOF'
cd /root/upgrade-320
echo "rc=$(cat update.rc) ERROR=$(grep -c ERROR update.log)"
for m in 'one-shot boot armed for kernel.GENERIC_PROD' 'root shell .* present' 'ESP loader updated' 'verifying kernel'; do grep -qE "$m" update.log && echo "OK: $m" || echo "НЕТ: $m"; done
grep cpu_microcode_load /boot/loader.conf.local
cat /boot/nextboot.conf
B=$(ls -d /var/backups/etc-2* | tail -1)
for f in ctl.conf iscsi.conf; do cmp -s /etc/$f $B/$f && echo "$f unchanged" || echo "$f CHECK"; done
md5 -q /usr/local/etc/ccrd/config.json
EOF

Должно получиться: rc=0 ERROR=0, четыре OK:, cpu_microcode_load="NO", в nextboot.conf — kernel.GENERIC_PROD, оба файла unchanged, md5 как в шаге 14. Иначе СТОП, узел не перезагружать.

23. Загрузчик на второй диск [раб]​

ssh root@<узел> 'sh -s apply' < tools/esp-sync.sh
ssh root@<узел> 'sh -s' < tools/esp-sync.sh # ESP OK

update.sh обновляет загрузчик только на диске, с которого узел загрузился.

24. Пакеты, которых нет после update.sh [раб]​

ssh root@<узел> 'sh -s' <<'EOF'
set -e
pkg install -y -r vstack-os cpu-microcode vStack-spdk vStack-nvmf-initiator vStack-storage-san vStack-vm-perfdata vStack-host-perfdata
sysrc -f /etc/rc.conf.local vstack_vm_perfdata_enable=YES vstack_host_perfdata_enable=YES
pkg info -e cpu-microcode-rc vStack-spdk vStack-nvmf-initiator && echo pkgs-OK
EOF

Должно получиться: pkgs-OK.

25. Память под SPDK [раб]​

ssh root@<узел> 'sh -s' <<'EOF'
set -e
cp -p /boot/loader.conf.local /boot/loader.conf.local.pre-contigmem-$(date +%Y%m%d%H%M%S)
sed -i '' -e '/^contigmem_load=/d' -e '/^hw.contigmem./d' /boot/loader.conf.local
cat >> /boot/loader.conf.local <<'L'
contigmem_load="YES"
hw.contigmem.num_buffers="8"
hw.contigmem.buffer_size="1073741824"
L
grep contigmem /boot/loader.conf.local
EOF

8 ГиБ на узел. Без этих строк фабрика на узле не запустится, а ни update.sh, ни Center их не пишут.

26. Перезагрузить — с консолью BMC перед глазами [раб]​

ssh root@<узел> 'sync; shutdown -r now'

Если на консоли после загрузчика !!!! X64 Exception — в меню загрузчика «3», затем disable-module cpu_microcode, set kernel=kernel.GENERIC_PROD, boot; после загрузки повторить шаг 7.

27. Проверить узел [узел]​

sysctl -n kern.bootfile # /boot/kernel.GENERIC_PROD/kernel
freebsd-version -kru # три раза 14.2-RELEASE-vstack-3.2.0-…
ccrd_cli | grep 'Node ID: *<N>;' # ONLINE
for s in vStackNodeside vStackVMctrl vStackStorageCtrl vstack_net_daemon; do service $s status >/dev/null && echo "$s up" || echo "$s DOWN"; done
pkg info -e cpu-microcode-rc && echo microcode-OK
sysctl hw.contigmem.num_buffers # 8

Все четыре службы up. kern.bootfile с _prev — новое ядро не загрузилось: СТОП, «Справочник», раздел 1.

28. Вернуть узел в кластер [узел]​

ccrd_cli --node --node_id <N> --command enable

Диски узла в чужих пулах: REMOVED ccrd вернёт сам. FAULTED — на узле-владельце пула zpool online zNN <имя диска из строки FAULTED>. Дождаться конца resilver (zpool status zNN). Затем dupcheck.sh.

29. Вернуть пулы [узел], [раб]​

Пулы — обратно на узел тем же способом, что в шаге 19 (для <OWN> — без новых снимков). Должно получиться:

  • работают все ВМ из running-<пул>.txt, через 5 минут гости загрузились (консоль в портале). Windows изредка висит при первом старте — один stop + start;
  • адрес фабрики каждого пула узла (из nvmf-addresses.tsv) поднят: [узел] ifconfig vlan40 | grep 'inet ';
  • zpool status -x — healthy, resilver не идёт, dupcheck.sh — none.

Окно B — Center, эджи, фабрика​

Портал и API недоступны с шага 34 до шага 35, управление ВМ — до шага 43. ВМ работают всё время.

30. Перед началом​

Все узлы на 3.2.0. У каждой ВМ из шага 5 есть отметка. Приостановленные ВМ (actual = 11) — возобновить или, по согласованию с владельцем, остановить.

31. Черновики настроек [mgmt]​

sh /root/mgmtvm-update.sh --source release --version 3.2.0 --snapshots-taken; echo rc=$?

Должно получиться: rc=10 и NOTICE: drafts written: …settings.yml, …nodes.yml. Center ещё работает.

32. Сверить черновики и разложить ключ [mgmt], [раб]​

В /usr/local/etc/vstack/settings.yml сверить каждое значение и заполнить пустые; поля с # CHECK — обязательно:

ПолеЗначение
cluster_id, cluster_node_quantityномер кластера, число узлов
base_urlадрес, по которому клиенты открывают консоли ВМ: имя mgmtvm.<кластер>…, если оно есть в vstack-env.conf узлов и в сертификате портала, иначе IP
mgmtvm_external_ipадрес vtnet0 mgmtVM
cluster_vlan_networkcluster_vlan_network из vstack-env.conf узла, обычно 172.17.127.0/24
publicep_net / publicep_ifpublicep_vlan_network / publicep_vlan_port из vstack-env.conf
storagenet / storagenet_ifiscsi_vlan_network / iscsi_vlan_port
vmnet_net, vxlan_net / vmnet_if, vlan_ifvxlan_vlan_network / vxlan_vlan_port; vxlan_if — vlan40
pool_netподсеть адресов пулов (select lif from storage.pool)
ntp_serversсерверы времени узлов, без повторов
kms_host, wss_fw, grafana_fwкак в старом Center (/usr/local/etc/vstack-dispatcher.conf, allow в конфигах nginx)
nvmf_net, vstack_node_install_nvmf"" и false — фабрика включается в шаге 46
pf_enabled, vmagent_enabled, unified_storage_enabled, logging_enabledfalse

В /usr/local/etc/vstack/inventory/nodes.yml — все узлы и ansible_python_interpreter: /usr/local/bin/python3.11. Затем [mgmt] cp -p /usr/local/etc/vstack/settings.yml /root/settings.yml.reviewed.

Ключ, которым Center ходит на узлы, [раб]:

ssh root@<MGMT> 'cat /usr/local/etc/vstack/ssh/id_ed25519.pub' > provision.pub; test -s provision.pub && cat provision.pub
for h in <адреса узлов>; do
ssh root@$h 'cp -p /root/.ssh/authorized_keys /root/.ssh/authorized_keys.pre-center32'
ssh root@$h 'cat >> /root/.ssh/authorized_keys' < provision.pub
done
ssh root@<MGMT> 'sh -s' <<'EOF'
for h in $(awk '/ansible_host:/ {print $2}' /usr/local/etc/vstack/inventory/nodes.yml); do
ssh -n -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i /usr/local/etc/vstack/ssh/id_ed25519 root@$h hostname || echo "FAIL $h"
done
EOF

Каждый узел ответил своим именем.

33. Снимки mgmtVM [раб]​

ssh root@<OWN> 'sh -s' <<'EOF'
V=$(zfs list -H -o name -t filesystem,volume | grep '/.vm_V4/000000000$'); echo $V
zfs list -H -o name -r $V | grep -v "^$V\$" | tee /root/mgmtvm-volumes.txt
for t in $(cat /root/mgmtvm-volumes.txt); do zfs snapshot $t@pre-center32 || { echo "FAIL $t"; break; }; done
zfs list -t snapshot -o name -r $V | grep pre-center32
EOF

Снимок на каждом томе, FAIL нет. Это точка отката Center; сразу за ним — шаг 34.

34. Обновить Center [mgmt]​

С этого шага портал и API недоступны.

sh /root/mgmtvm-update.sh --source release --version 3.2.0 --snapshots-taken --settings-reviewed --reboot --detach

mgmtVM перезагрузится. Когда снова пускает по ssh (1–2 минуты), — та же команда ещё раз. Ход:

sh /root/mgmtvm-update.sh --status > /tmp/st.txt; head -3 /tmp/st.txt

Готово — phase: done (30–60 минут). Ошибка — код и что делать в «Справочнике», раздел 2.

35. Проверить Center [mgmt]​

for s in postgresql rabbitmq influxd angie grafana vstack_center_dispatcher vstack_center_endpoint vstack_center_image_distributor; do printf "%s: " $s; service $s status >/dev/null 2>&1 && echo up || echo DOWN; done
tail -1 /var/log/vstack_center.log # … finished … (exit=0)
fetch -qo /dev/null --no-verify-peer --no-verify-hostname https://127.0.0.1/ && echo web-OK
su -m postgres -c "psql -At -d vstack -c \"select o.value, p.deprecated_id, p.name from management.cluster_options o left join vm.os_profile p on p.id::text = o.value where o.an_option = 'Edges_profile_id'\""

Должно получиться: все сервисы up, exit=0, web-OK, в портал входит обычный пользователь, ВМ и узлы на месте. Последняя строка — <uuid>|105|EdgeOS 0.8.

Если вместо неё 105|| — профиля эджа v8 нет, и Center не создаст ни одного эджа. Добавить его [mgmt]:

cd /tmp
cat > /root/edge-v8-profile.sql <<'SQL'
\set ON_ERROR_STOP 1
BEGIN;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM vm.os_profile WHERE deprecated_id = 102) THEN RAISE EXCEPTION 'нет профиля эджа 102 — СТОП'; END IF;
END $$;
INSERT INTO vm.os_profile (deprecated_id, name, description, type_id, min_size, sector_size_logical, sector_size_physical, zvol, incarnation,
os_name, owner, guest_customization, def_hv_flt_sigabort_interval, def_hv_flt_sigabort_restarts, uefi, private, id, state,
net_link_pattern, zero_based_link, default_shell, is_cluster_shared)
SELECT 105, 'EdgeOS 0.8', 'vStack Edge 13.0 v8', type_id, 2147483648, 512, 4096, '.vm_images/vStack-edge-13.0-v8', to_timestamp(1750000000),
os_name, owner, guest_customization, def_hv_flt_sigabort_interval, def_hv_flt_sigabort_restarts, uefi, private, '019f7eeb-a99c-7787-ba49-dad7b687321b', 'active',
net_link_pattern, zero_based_link, default_shell, is_cluster_shared
FROM vm.os_profile WHERE deprecated_id = 102
AND NOT EXISTS (SELECT 1 FROM vm.os_profile WHERE deprecated_id = 105 OR zvol = '.vm_images/vStack-edge-13.0-v8');
UPDATE management.cluster_options SET value = (SELECT id::text FROM vm.os_profile WHERE deprecated_id = 105) WHERE an_option = 'Edges_profile_id';
COMMIT;
SQL
su -m postgres -c "psql -X -v ON_ERROR_STOP=1 -d vstack" < /root/edge-v8-profile.sql; echo rc=$?
service vstack_center_dispatcher restart

Должно получиться: UPDATE 1, rc=0, и запрос выше теперь даёт <uuid>|105|EdgeOS 0.8.

36. Пароль эджей не изменился [раб]​

ssh root@<MGMT> 'sh -s' <<'EOF'
tr -d '\n' < /var/db/vstack/secrets/rmq_pass_edge_ctrl | sha256 -q
EOF
cat edge-ctrl-pre.txt

Отпечатки совпадают. Нет — СТОП: старые эджи не подключатся к Center.

37. Неисправленные Linux-ВМ — на virtio-blk [mgmt]​

ВМ из шага 5 с отметкой «не исправлена»:

cd /tmp; su -m postgres -c "psql -X -d vstack -c 'UPDATE vm.machine SET nvme = false WHERE id IN (<id через запятую>)'"

Они будут работать как раньше, но запускаться только на узле своего пула. Когда владелец добавит драйвер, — nvme = true и перезапуск.

38. Внешние системы — на новые номера образов [mgmt]​

В Center 3.2 профиль ОС задаётся UUID, старый номер Center больше не принимает (-253). Таблица «старый номер → UUID»:

cd /tmp; su -m postgres -c "psql -X -At -F ';' -d vstack -c 'select deprecated_id, id, name from vm.os_profile where deprecated_id is not null order by 1'" > /root/os-profile-uuid-map.csv

Владельцам внешних систем, создающих ВМ через API, — заменить номера на UUID по этой таблице.

39. Перевести узлы на новый Center: проверка [mgmt]​

Пока этот шаг и следующий не сделаны, узлы не получают команд Center: у них прописан старый адрес брокера. Новый адрес — .250 в подсети cluster_vlan_network из settings.yml (обычно 172.17.127.250). Проверка с каждого узла [раб]: ssh root@<узел> 'nc -z -w3 <адрес брокера> 5672 && echo amqp-OK'. Нет amqp-OK — СТОП: узел не видит mgmtVM в кластерной сети.

cd /usr/local/share/vstack-center
umask 077
ANSIBLE_CONFIG=$PWD/ansible.cfg ansible-playbook main.yml -t nodes -e @/usr/local/etc/vstack/settings.yml --check --diff > /var/log/nodes-check.log 2>&1; echo rc=$?
sed -n '/PLAY RECAP/,$p' /var/log/nodes-check.log
grep -E '^[-+](Password|password)' /var/log/nodes-check.log | wc -l # 0

Прогон занимает 2–5 минут; после строки PLAY RECAP процесс может висеть до получаса — тогда Ctrl+C. Должно получиться: у каждого узла unreachable=0 failed=0, пароли не меняются. Меняются адрес брокера (на <адрес брокера>) в конфигах контроллеров и telegraf, удаляются старые сборщики метрик, перезапускаются контроллеры; ВМ не затрагиваются. Роль меняет сетевые интерфейсы, конфиг ccrd или пароли — СТОП.

40. Перевести узлы на новый Center [mgmt]​

cat > /root/nodes-run.sh <<'X'
#!/bin/sh
umask 077
cd /usr/local/share/vstack-center
ANSIBLE_CONFIG=$PWD/ansible.cfg ansible-playbook main.yml -t nodes -e @/usr/local/etc/vstack/settings.yml --diff > /var/log/nodes-run.log 2>&1
echo "rc=$?" >> /var/log/nodes-run.log
X
daemon -f sh /root/nodes-run.sh

Через 2–5 минут: sed -n '/PLAY RECAP/,$p' /var/log/nodes-run.log — у каждого узла unreachable=0 failed=0.

41. Запустить сборщик метрик ВМ [раб], каждый узел​

ssh root@<узел> 'sh -s' <<'EOF'
pkill -f vStack-vm-perfdata-feeder; service vstack_vm_perfdata start </dev/null >/dev/null 2>&1
sleep 3; pgrep -lf /usr/local/sbin/vstack-vm-perfdata || echo "НЕ ЗАПУЩЕН"
EOF

42. Повторная проверка роли [mgmt]​

Шаг 39 ещё раз — у каждого узла changed=0. Затем:

cd /usr/local/share/vstack-center
ANSIBLE_CONFIG=$PWD/ansible.cfg ansible-playbook main.yml -t seed-nodes -e @/usr/local/etc/vstack/settings.yml; echo rc=$?

Должно получиться: rc=0, failed=0.

43. Узлы получают команды [mgmt]​

env HOME=/var/db/rabbitmq rabbitmqctl -q list_connections user | sort | uniq -c
grep 'VMs should be started' /var/log/vstack-center-dispatcher/$(date +%F).log | tail -1

Должно получиться: vm_controller, storage_user, nodeside_ctrl, net_user — по числу узлов; 0 VMs should be started. [раб]: dupcheck.sh — none. С этого шага ВМ можно запускать и останавливать.

44. ВМ на NVMe​

  1. Одна Linux-ВМ не из шага 5: vms-stop, wait 1, vms-start на её узел, wait 3 — в госте диск nvme0n1 (или nda0).
  2. Одна исправленная ВМ из шага 5 — так же.
  3. Одна Windows-ВМ — так же: осталась на virtio-blk и загрузилась.
  4. Тестовая ВМ из Linux-образа не из шага 5: создать в портале, загрузилась, удалить.

45. Эджи — на образ v8​

Эджи на старом образе v7 после обновления Center получили диск NVMe и при следующем перезапуске не загрузятся. edge-reimage.sh меняет им загрузочный диск на v8, не пересоздавая эдж: адреса, правила и настройки сохраняются. [mgmt] — список:

cd /tmp; su -m postgres -c "psql -X -At -F' ' -d vstack -c \"select e.vm_id, sp.name, sp.current_node, p.zvol from network.an_edge e join vm.machine m on m.id=e.vm_id join storage.pool sp on sp.guid::text=m.pool::text join vm.os_profile p on p.id=m.os_profile order by 2,1\""

Колонки: номер эджа, пул, узел пула, образ. Эджи с …-v8 пропустить. [раб], на узле каждого пула с эджами, всегда с номерами эджей:

scp tools/edge-reimage.sh root@<узел>:/tmp/
ssh root@<узел> 'env TARGET_IMG=vStack-edge-13.0-v8 DRY_RUN=1 sh /tmp/edge-reimage.sh <vm_id> <vm_id> …' # план, ничего не меняет
ssh root@<узел> 'env TARGET_IMG=vStack-edge-13.0-v8 DRY_RUN=0 sh /tmp/edge-reimage.sh <vm_id>' # сначала один эдж

В выводе сухого прогона у каждого номера должно быть …/.vm_images/vStack-edge-13.0-v7@…: с явными номерами скрипт не проверяет, что это эдж, и опечатка заменит диск чужой ВМ. Через 5 минут после замены [mgmt]:

cd /tmp; su -m postgres -c "psql -X -At -d vstack -c \"select vm_id, ctrl_version, deploy_ts from network.an_edge where vm_id=<vm_id>\""

Должно получиться: ctrl_version заполнен, deploy_ts — время после замены, ВМ за эджем выходят в сеть. Тогда — остальные эджи, по одному узлу за раз, та же проверка. Не подключился — СТОП, откат эджа — «Справочник», раздел «Откат».

46. Фабрика: настройки Center [mgmt]​

cp -p /usr/local/etc/vstack/settings.yml /root/settings.yml.pre-nvmf

В /usr/local/etc/vstack/settings.yml: vstack_node_install_nvmf: true и nvmf_net: "<vxlan>.0/24" (подсеть VXLAN, как в шаге 14). Затем роль — как шаги 39 и 40. В проверке должна меняться только задача Enable NVMe-oF services (по одному changed на узел). nvmf_tgt в выводе — СТОП.

47. Фабрика: фильтр портов [раб], каждый узел​

scp pf-fabric-<кластер>.conf root@<узел>:/root/pf-fabric.conf
ssh root@<узел> 'sh -s' <<'EOF'
test -e /etc/pf.conf && { echo "/etc/pf.conf уже есть — СТОП"; exit 1; }
grep -q ЗАМЕНИТЬ /root/pf-fabric.conf && { echo "заглушки не заменены — СТОП"; exit 1; }
install -m 0600 /root/pf-fabric.conf /etc/pf.conf
kldload -n pf
pfctl -nf /etc/pf.conf && pfctl -f /etc/pf.conf && pfctl -e
sysrc pf_enable=YES pf_rules=/etc/pf.conf
pfctl -sr
EOF

Должно получиться: pf enabled, четыре правила. Если на узле уже есть свой /etc/pf.conf — правила из pf-fabric.conf добавить в него вручную.

48. Фабрика: запустить на узлах [раб], каждый узел​

ssh root@<узел> 'sh -s' <<'EOF'
pgrep -qf '^/usr/local/bin/nvmf_tgt' && { echo "СТОП: nvmf_tgt"; exit 1; }
pgrep -qf '^/usr/local/bin/spdk_tgt' || { nohup service spdk_tgt start >/dev/null 2>&1 </dev/null & }
sleep 8
pgrep -qf vstack-nvmf-initiator || { nohup service vstack_nvmf_initiator start >/dev/null 2>&1 </dev/null & }
sleep 5
pgrep -lf '^/usr/local/bin/spdk_tgt|nvmf-initiator'
sockstat -4l | grep spdk_tgt
EOF
nc -z -w3 <адрес узла> 7000 && echo OPEN || echo blocked # с рабочей машины: blocked

Должно получиться: оба процесса, слушатель *:7000, с рабочей машины — blocked. OPEN — СТОП: фильтр не работает.

49. Фабрика: адреса пулов в Center [раб], [mgmt]​

Адрес каждого пула поднят на узле, где пул сейчас [раб]:

for h in $NODES; do ssh -n root@$h 'ifconfig vlan40'; done | grep -c "inet <vxlan>\.2" # = числу пулов

SQL с адресами из шага 14 [раб]:

V=$(awk -F'\t' '{sub("zpool_","",$1); printf "%s(\x27%s\x27,\x27%s\x27)", (NR>1?", ":""), $1, $3}' nvmf-addresses.tsv)
cat > nvmf-enable.sql <<SQL
\\set ON_ERROR_STOP 1
BEGIN;
CREATE TEMP TABLE want(name text PRIMARY KEY, inet inet UNIQUE);
INSERT INTO want VALUES $V;
DO \$\$ BEGIN
IF (SELECT count(*) FROM storage.pool) <> (SELECT count(*) FROM want)
OR EXISTS (SELECT 1 FROM storage.pool p LEFT JOIN want w USING (name) WHERE w.name IS NULL)
THEN RAISE EXCEPTION 'список пулов не совпадает с storage.pool'; END IF;
END \$\$;
UPDATE storage.pool p SET nvmf_inet = w.inet FROM want w WHERE w.name = p.name;
INSERT INTO management.cluster_options (an_option, value) VALUES ('feature.nvmf','1')
ON CONFLICT (an_option) DO UPDATE SET value = EXCLUDED.value;
SELECT name, nvmf_inet, current_node FROM storage.pool ORDER BY name;
COMMIT;
SQL
grep 'INSERT INTO want' nvmf-enable.sql
scp nvmf-enable.sql root@<MGMT>:/root/

[mgmt]:

cd /tmp
su -m postgres -c "psql -X -At -d vstack -c \"copy (select name, nvmf_inet from storage.pool order by name) to stdout\"" > /root/pool-nvmf-pre.tsv
rc=0; su -m postgres -c "psql -X -v ON_ERROR_STOP=1 -d vstack" < /root/nvmf-enable.sql || rc=$?
echo rc=$rc
[ $rc = 0 ] && service vstack_center_dispatcher restart

Должно получиться: rc=0, у каждого пула свой адрес. Ошибка список пулов не совпадает — транзакция откатилась, ничего не изменено; сверить nvmf-addresses.tsv с пулами кластера. С этого момента Center запускает ВМ на любом узле.

50. Фабрика: проверка на одной Linux-ВМ [раб], [узел]​

sh tools/vapi.sh vms-stop '{"id":<id>,"hard_timeout":90}'; sh tools/vapi.sh wait <id> 1
sh tools/vapi.sh vms-start '{"id":<id>,"node_id":<чужой узел>}'; sh tools/vapi.sh wait <id> 3
sh tools/vapi.sh vm-list '{}' | jq '.result.data[] | select(.vm_id==<id>) | {node,rstart}' # rstart: 1

На чужом узле: sockstat -4 -c | grep ':4420' — есть соединения к адресу пула. В консоли гость загрузился. Затем vms-stop и vms-start с узлом-владельцем пула — ВМ снова на своём узле. Не прошло — откат фабрики, «Справочник», раздел «Откат».

51. Итог​

  • Все узлы на 3.2.0, ONLINE, пулы healthy, dupcheck.sh — none.
  • Состояния ВМ совпадают со списком шага 4.
  • edge-reimage.sh с DRY_RUN=1 на каждом узле — эджей для замены нет.

Объявить клиентам об окончании работ.

После​

  • Через 2–3 дня [mgmt]: sh /root/mgmtvm-update.sh --cleanup-oldgen (список), затем с --snapshots-taken — удаляет данные старого Center. После этого откат Center — только снимками шага 33.
  • Через несколько дней — удалить снимки @pre-center32, @pre_move_* и старые диски эджей *.pre-<время>.
  • Windows на NVMe — отдельной работой: «Windows на NVMe».