Pull to refresh

Экстремальное восстановление данных с деградировавшего 5го рейда

Reading time 8 min
Views 13K

Написано на реальных событиях.


Любое повторение действий и необдуманные решения могут привести к полной утрате данных. Не для HowTo-шников, данный материал лишь для воссоздания картины о представлении данных на дисковых носителях.

Итак, приступим. Вводные данные:
  • 7 дисков, 2 primary-раздела на каждом;
  • 1й раздел 7и кратное зеркалирование (RAID1);
  • 2й раздел RAID5, под которым крутится LVM.

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

Что уже сделано, не вернешь, надо как-то восстанавливать данные. т.к. резервных копий, как обычно нет. План действий:
  1. Скопировать уцелевшые данные на новый диск(и);
  2. Восстановить исходный порядок дисков;
  3. Вычленить убитый диск (см. пункт 1);
  4. Вычислить формат рейда (meta), как оказалось soft-raid начиная с какой-то версии для себя выделяет 1 кб в начале дисков, раньше он хранил эти данные в шапке диска.;
  5. Вычислить размер чанка/страйпа, так же был изменен с 64к на 512к;
  6. собрать диски воедино
  7. Восстановить LVM и переписать логические тома


По пункту 1.


Все тривиально, покупаем новые диски, большего объема, делаем на них LVM, выделяем под каждый диск отдельный LV, и копируем 2й раздел при помощи dd. С данными нам придется играть достаточно долго.

Теперь по остальным пунктам.


Теория следующая: чтобы определить порядок дисков и размер чанков необходимо найти какой-нибудь лог-файл, в котором будет проставлена дата и время события. Сказано-сделано. в моем случае лог-файлы были в отдельном lv-разделе. Для работы нам так же понадобится каталог достаточного размера (в моем случае я выделил себе под работу 200 гб). Приступаем к изучению. берем первые 64к с каждого диска:
for i in a b c d e f g; do dd if=/dev/jbod/sd${i} bs=64 count=1024 of=/mnt/recover/${i}; done

Получаем 7 файлов по 64 кб. Тут мы сразу можем понять, какой формат мета-данных md используется. к примеру, для metadata 1, 1.0, 1.1, 1.2 — выглядит следующим образом:
mega@megabook ~ $ dd if=/dev/gentoo/a bs=1024 count=64 | hexdump -C
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001000 fc 4e 2b a9 01 00 00 00 00 00 00 00 00 00 00 00 |.N+.............|
00001010 ee 6f de dc c3 94 9c 58 47 d0 cc 91 9c f7 c5 35 |.o.....XG......5|
00001020 6d 65 67 61 62 6f 6f 6b 3a 30 00 00 00 00 00 00 |megabook:0......|
00001030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001040 96 e2 6c 4e 00 00 00 00 05 00 00 00 02 00 00 00 |..lN............|
00001050 00 5c 00 00 00 00 00 00 00 04 00 00 07 00 00 00 |.\..............|
00001060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001080 00 04 00 00 00 00 00 00 00 5c 00 00 00 00 00 00 |.........\......|
00001090 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000010a0 00 00 00 00 00 00 00 00 64 23 f5 c9 5f 2a 64 68 |........d#.._*dh|
000010b0 e8 92 f2 1a 8c ca ad 98 00 00 00 00 00 00 00 00 |................|
000010c0 9a e2 6c 4e 00 00 00 00 12 00 00 00 00 00 00 00 |..lN............|
000010d0 ff ff ff ff ff ff ff ff f6 51 38 f5 80 01 00 00 |.........Q8.....|
000010e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001100 00 00 01 00 02 00 03 00 04 00 05 00 fe ff 06 00 |................|
00001110 fe ff fe ff fe ff fe ff fe ff fe ff fe ff fe ff |................|
*
00001400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
64+0 записей считано
64+0 записей написано
скопировано 65536 байт (66 kB)00010000
, 0,000822058 c, 79,7 MB/c

mega@megabook ~ $ mdadm -D /dev/md0
/dev/md0:
Version : 1.2
Creation Time : Sun Sep 11 20:32:22 2011
Raid Level : raid5
Array Size : 70656 (69.01 MiB 72.35 MB)
Used Dev Size : 11776 (11.50 MiB 12.06 MB)
Raid Devices : 7
Total Devices : 7
Persistence : Superblock is persistent

Update Time : Sun Sep 11 20:32:26 2011
State : clean
Active Devices : 7
Working Devices : 7
Failed Devices : 0
Spare Devices : 0

Layout : left-symmetric
Chunk Size : 512K

Name : megabook:0 (local to host megabook)
UUID : ee6fdedc:c3949c58:47d0cc91:9cf7c535
Events : 18

Number Major Minor RaidDevice State
0 253 21 0 active sync /dev/dm-21
1 253 22 1 active sync /dev/dm-22
2 253 23 2 active sync /dev/dm-23
3 253 24 3 active sync /dev/dm-24
4 253 25 4 active sync /dev/dm-25
5 253 26 5 active sync /dev/dm-26
7 253 27 6 active sync /dev/dm-27

Это пример metadata 1.2. Отличительная особенность в том, что блоки будут примерно одинакового содержания (за исключением номера диска), остальное же место будет забито NULLами. Информация о рейде находится по адресам: 0x00001000-0x00001ffff.
Для более ранних версий metadata, информация о рейде записывалась в область разметки дисков и на устройствах сразу начинались данные. Выглядит этот примерно так для metadata 0.9:
~ # dd if=/dev/jbod/sdb bs=1024 count=1 | hexdump -C
1+0 записей считано
1+0 записей написано
скопировано 1024 байта (1,0 kB), 0,0200084 c, 51,2 kB/c
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000200 4c 41 42 45 4c 4f 4e 45 01 00 00 00 00 00 00 00 |LABELONE........|
00000210 1b 72 36 1f 20 00 00 00 4c 56 4d 32 20 30 30 31 |.r6. ...LVM2 001|
00000220 66 6d 59 33 4a 35 6b 72 46 73 6d 52 51 41 47 66 |fmY3J5krFsmRQAGf|
00000230 4c 30 72 53 6b 69 59 6e 31 43 6c 72 66 61 66 70 |L0rSkiYn1Clrfafp|
00000240 00 00 fa ff ed 02 00 00 00 00 06 00 00 00 00 00 |................|
00000250 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000260 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 |................|
00000270 00 f0 05 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000280 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000400


Любопытная особенность всплыла после дальнейшего изучения первых 64х кб с дисков. Как оказалось, LVM хранит информацию о разметке LV в открытом тексте. выглядит это следующим образом:
vg0 {
id = "xxxx"
seqno = 64
status = ["RESIZEABLE", "READ", "WRITE"]
extent_size = 8192 # 4 Megabytes
max_lv = 0
max_pv = 0

physical_volumes {

pv0 {
id = "xxxxx"
device = "/dev/md2" # Hint only

status = ["ALLOCATABLE"]
dev_size = 5848847616 # 2,72358 Terabytes
pe_start = 384
pe_count = 713970 # 2,72358 Terabytes
}
}

logical_volumes {
--//--
log {
id = "l8OVMc-BUAj-YrIT-w8mh-YkvH-riS3-p1h6OY"
status = ["READ", "WRITE", "VISIBLE"]
segment_count = 2

segment1 {
start_extent = 0
extent_count = 12800 # 50 Gigabytes

type = "striped"
stripe_count = 1 # linear

stripes = [
"pv0", 410817
]
}
segment2 {
start_extent = 12800
extent_count = 5120 # 20 Gigabytes

type = "striped"
stripe_count = 1 # linear

stripes = [
"pv0", 205456
]
}
}
--//--
}

Супер! Что нам нужно из этого. Основными цифрами, которыми стоит руководствоваться, это:
  • extent_size = 8192 # 4 Megabytes
  • pe_start = 384
  • stripes = [ pv0, 410817]
  • extent_count = 5120

Что же это за цифры…
  • extent_size — что-то вроде размера кластера. минимальная единица, на которые поделено все пространство VG. Почему 4 мегабайта и в каких таких попугаях измеряется? Я так же задался этим вопросом, когда увидел, но как оказалось, все просто — размер кластера 512 байт * 8192 = 4 Мб
  • pe_start — где заканчивается шапка LVM и откуда начинаются данные.
  • extent_count — количество блоков выделенное для раздела.
  • stripes = [ pv0, xxxx ] — с какого места и на каком PV находится раздел.

Не забываем, что у нас 5й рейд на 7и дисках, делим все цифры на 6. (т.к. 7й — parity)
пробуем найти хоть какой-нибудь лог. Руками/глазами просматривать — утопия, пишем скрипт:
dd if=/dev/recover/sda bs=512 skip=$[(8192*410817+384)/6] | hexdump -C | grep 'Aug 28' | head

где:
skip=(extent_size*stripes+pe_start)/6
bs=512 -- те самые попугаи

после недолгого шуршания дисков, получаем на выходе что-то вроде этого:
02d08100 41 75 67 20 32 38 20 30 30 3a 30 36 3a 30 36 20 |Aug 28 00:06:06 |
02d081c0 3d 0a 41 75 67 20 32 38 20 30 30 3a 30 36 3a 30 |=.Aug 28 00:06:0|
02d08410 41 75 67 20 32 38 20 30 30 3a 30 36 3a 30 37 20 |Aug 28 00:06:07 |
02d08570 70 0a 41 75 67 20 32 38 20 30 30 3a 30 36 3a 30 |p.Aug 28 00:06:0|
02d085d0 65 78 74 3d 0a 41 75 67 20 32 38 20 30 30 3a 30 |ext=.Aug 28 00:0|
02d086b0 64 3d 2a 29 29 22 0a 41 75 67 20 32 38 20 30 30 |d=*))".Aug 28 00|
02d08710 65 73 74 61 6d 70 0a 41 75 67 20 32 38 20 30 30 |estamp.Aug 28 00|
02d08770 73 3d 30 20 74 65 78 74 3d 0a 41 75 67 20 32 38 |s=0 text=.Aug 28|
02d089c0 6f 72 64 3d 2a 29 29 22 0a 41 75 67 20 32 38 20 |ord=*))".Aug 28 |
02d08a20 6d 65 73 74 61 6d 70 0a 41 75 67 20 32 38 20 30 |mestamp.Aug 28 0|

берем адрес: 02d08770, делим на 512 с остатком, получаем:
mega@megabook ~ $ echo $[0x02d08770/512]
92227
mega@megabook ~ $ echo $[0x02d08770/512/2048]
45

накидываем пару мегабайт и смотрим, что там будет:
for i in a b c d e f g; do dd if=/dev/recover/sd${i} of=/mnt/recover/${i} bs=512 count=1024 skip=$[(8192*410817+384)/6+(48*2048)] ; done

Получаем 7 файлов по 512кб. Открываем текстовым редактором и смотрим даты, какой из файлов начитается с контрольных сумм (parity). Выстраиваем порядок дисков, а так же смотрим, какого размера chunk. Если через 64кб начинаются контрольные суммы, то 64кб, если нет, значит скорее всего 512 или большего размера. Повторяем действие со смещением на 1 блок:
for i in a b c d e f g; do dd if=/dev/recover/sd${i} of=/mnt/recover/${i}.1 bs=512 count=1024 skip=$[(8192*410817+384)/6+(48*2048)+1] ; done

Строим таблицу на бумаге. какой из дисков первый, на каком месте parity-блок. Не лишним будет сказать, что существует 4 порядка следования 5х RAIDов: левый асинхронный, левый синхронный, правый асинхронный и правый синхронный. Подробней описано здесь: www.accs.com/p_and_p/RAID/LinuxRAID.html.

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

Теперь, зная размер чанков, тип таблицы мета-данных и порядок следования разделов можно попробовать поиграть с mdadm для воссоздания рейда. Стратегическая хитрость заключается в том, что mdadm умеет создавать деградированные рейды, если вместо реального диска написать слово missing. Пользуясь полученными знаниями пробуем создать рейд. Обязательно указываем тип метаданных! И ни в коем случае не повторяем нижеследующую команду без досконального изучения вышеизложенного материала!. Для проверки правильности сборки массива я пользовался разделом виртуальной машины на 2 гб.
lvcreate -L2G -nnagios recover
mdadm -C /dev/md0 -l 5 -n 7 --metadata 0.9 -c 64 /dev/recover/[a-f] missing
dd if=/dev/md0 bs=512 skip=$[8192*XXXX+384] count=$[8192*512] | dd of=/dev/recover/nagios
cfdisk /dev/recover/nagios

Если cfdisk говорит, что таблица разметки дисков не верная, разрушаем массив и повторяем создание массива со смещением дисков… первый кидаем в конец. т.е.
mdadm -S /dev/md0
mdadm -C /dev/md0 -l 5 -n 7 --metadata 0.9 -c 64 /dev/recover/[b-f] missing /dev/recover/a

копируем раздел, смотрим содержимое, и так пока не найдем нужную последовательность. можно конечно посидеть с калькулятором и на основании данных, что мы вычислили при просмотре раздела логов, а так же адресов, вычислить точный порядок.
Обращаем внимание, что вместо седьмого диска sdg я написал missing, а так же порядок дисков должен быть не a-g, а тем, что вы получили на предыдущем этапе. Напомню, что missing заставит рейд отказаться от пересчета parity-блоков, т.к. будет считать себя деградировавшим и работать в аварийном режиме.

После того, как найдете, который из разделов у вас первый, выбираем небольшой раздел LV и копируем его по образу и подобию, как я копировал nagios. пробуем смонтировать. В случае, если у вас выкинут не тот диск (missing), скорее всего в dmesg будет сообщено, что проблемы с журналом диска (т.к. один из дисков вносит свою лепту в виде кривых данных). повторяем пункты останова, создания и копирования дисков с замещением позиции missing. т.е. добавляем, к примеру, добавляем диск sdg, который у меня был на месте missing, а заместо диска sdf пишем missing:
mdadm -C /dev/md0 -l 5 -n 7 --metadata 0.9 -c 64 /dev/recover/[b-e] missing /dev/recover/sdg /dev/recover/a

И так до тех пор, пока полученные данные не будут максимально актуальными.
На сим, думаю, рассказ окончить.
лишь добавлю пару слов по восстановлению LVM (чтобы не мучаться с этими адресами).
тут все просто. Если вы добъетесь актуальности — он сам сможет активироваться, если же нет, достаете бекапы, обычно в /etc/lvm/backup/VG-NAME. Я, учитывая, что у меня корневой раздел лежал на том же LVM, вынес этот каталог в /boot/lvm и сделал на него симлинки. и далее все просто:
vgcfgrestore -f /path/to/backup-file vg-name

Если это не поможет, скажем, начнет ругаться на контрольные суммы, можно немного хакнуть это дело:
dd if=/dev/zero of=/dev/md0 bs=512 count=10
pvcreate /dev/md0
pvdisplay /dev/md0 | grep 'PV UUID'

далее правим файл бекапа, меняем там UUID, создаем VG с тем же именем что и был раньше на этом разделе
vgcreate vg0 /dev/md0
vgcfgrestore -f /path/to/backup-file vg0

после этих махинаций все должно восстановиться и VG будет актуальным на момент создания бекап-файла.

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

Смотрите так же:


Tags:
Hubs:
+64
Comments 34
Comments Comments 34

Articles