Мой опыт замены дисков в Hetzner

О том, что Hetzner использует подержанные жесткие диски, не писал только ленивый. Не вижу в этом ничего плохого, учитывая их постоянное уплотнение и снижение отказоустойчивости. Рано или поздно, любые жесткие диски выходят из строя. Но раньше и трава была зеленее, и железо стабильнее.
Отвалился один из дисков, входящих в стандартный софтварный RAID1, созданный с помощью mdadm. На работе сервера это никоим образом не сказалось, но отсутствие резервирования не есть хорошо. После полного бэкапа системы (файлы, базы данных, настройки) написали тикет в техподдержку.
Заблаговременно нужно поставить загрузчик на оставшийся в живых диск (делаем наверняка, вдруг мертвый диск был главным, и с него загружалась система). Для этого достаточно выполнить

grub-install /dev/sdb

(где sdb — живой диск).
При заполнении заявки в панели хостера просят указать серийный номер винчестера, требующего замены, если это представляется возможным. Либо же серийный номер второго винчестера, если первый приказал долго жить и замолчал.
Замену оборудования произвели за 10 минут после нажатия кнопки «Отправить заявку». Такой оперативной работе можно только позавидовать, учитывая, что в качестве подменного диска установили абсолютно новый 3ТБ винчестер (все параметры в SMART были около нуля).

Сервер сразу запустили в боевом режиме (без rescue-mode и лишних потерь драгоценного аптайма). Так как объем винчестера превышает 2ТБ, на дисках используется разметка GPT. Для копирования таблицы разделов на чистый диск в этом случае используется sgdisk. В качестве подстраховки делаем бэкап существующей таблицы разделов на живом винчестере с данными:

sgdisk --backup=/home/backup/sdb.gpt /dev/sdb

Копируем таблицу разделов (особое внимание следует обратить на последовательность аргументов, сначала указывается КУДА, а последним аргументом ОТКУДА копируется таблица):

sgdisk -R /dev/sda /dev/sdb

Генерируем для диска новый UUID:

sgdisk -G /dev/sda

И теперь можно добавить диск в массив (отдельно для каждого раздела):

mdadm /dev/md0 -a /dev/sda1
mdadm /dev/md1 -a /dev/sda2
mdadm /dev/md2 -a /dev/sda3
mdadm /dev/md3 -a /dev/sda4

После этого начинается синхронизация, которая на действующем сервере может затянуться на довольно продолжительное время (вплоть до нескольких дней, зависит от нагрузки). За статусом операции и общим состоянием массива можно следить командой

cat /proc/mdstat

Personalities : [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] 
md3 : active raid1 sda4[2] sdb4[1]
      1822442815 blocks super 1.2 [2/2] [UU]
      
md2 : active raid1 sda3[2] sdb3[1]
      1073740664 blocks super 1.2 [2/2] [_U]
[=============>.] resync = 99.9% (1072884736/1073740664) finish=0.2min speed=65787K/sec
      
md1 : active raid1 sda2[2] sdb2[1]
      524276 blocks super 1.2 [2/2] [UU]
      
md0 : active raid1 sda1[2] sdb1[1]
      33553336 blocks super 1.2 [2/2] [UU]
      
unused devices: 

В моем случае операция синхронизации привела к выпаданию нового диска в Spare и ошибке добавления диска в массив на нескольких разделах (корневой / и загрузочный /boot). Вот тут то и началось самое интересное. При синхронизации происходит чтение текущего активного элемента массива и дублирование информации на новый диск. Если происходят ошибки чтения, то сбойный сектор фиксируется в kern.log, и операция прекращается после нескольких попыток.

Jan 22 19:14:29 server kernel: [294309.656319] end_request: I/O error, dev sdb, sector 1190677512
Jan 22 19:14:29 server kernel: [294309.656359] raid1: sdb: unrecoverable I/O read error for block 1122513920
Jan 22 19:14:29 server kernel: [294309.656362] ata2: EH complete
Jan 22 19:14:29 server kernel: [294309.703991] md: md2: recovery done.

Одним из возможных вариантов выхода из сложившейся ситуации было проверить диск на ошибки чтения, отремапить сбойные сектора и попытаться любыми правдами-неправдами синхронизировать разделы.

Personalities : [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] 
md3 : active raid1 sda4[2] sdb4[1]
      1822442815 blocks super 1.2 [2/2] [UU]

md2 : active raid1 sda3[2](S) sdb3[1]
      1073740664 blocks super 1.2 [2/1] [_U]
      
md1 : active raid1 sdb2[1]
      524276 blocks super 1.2 [2/1] [_U]

md0 : active raid1 sda1[2] sdb1[1]
      33553336 blocks super 1.2 [2/2] [UU]
      
unused devices: 

Репамить наживую, как оказалось, умеет hdparm (следующую команду копировать не рекомендую в целях безопасности, лучше вбить заново в консоли):

hdparm --write-sector 1190677512 --yes-i-know-what-i-am-doing /dev/sdb

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

Дальше в ход пошла математика, операции перевода блоков в сектора были выведены экспериментально (зачем искать легкие пути).
Вывод badblocks (частичный)

badblocks -v -s /dev/sdb3 
Checking blocks 0 to 1073741823
Checking for bad blocks (read-only test): 
561257984

Смотрим на таблицу разделов в секторном представлении с помощью

parted /dev/sdb 'unit s print'

Model: ATA ST3000DM001-9YN1 (scsi)
Disk /dev/sdb: 5860533168s
Sector size (logical/physical): 512B/4096B
Partition Table: gpt

Number  Start        End          Size         File system  Name  Flags
 5      2048s        4095s        2048s                           bios_grub
 1      4096s        67112959s    67108864s                       raid
 2      67112960s    68161535s    1048576s                        raid
 3      68161536s    2215645183s  2147483648s                     raid
 4      2215645184s  5860533134s  3644887951s                     raid

Нас интересует раздел под номером 3, смещение 68161536 от начала диска. Весь раздел размером 2147483648 секторов или 1073741824 блока (что следует из вывода badblocks выше). Значит для получения сбойного сектора делаем следующее:
Берем блок из вывода badblocks 561257984, умножаем на 2, прибавляем смещение 68161536. Полученный сектор 1190677504 и его окрестность пробуем читать hdparm —read-sector 1190677504 /dev/sdb. Про окрестность упомянуто не просто так, мне для получения требуемых секторов пришлось в операцию перевода добавить везде «плюс 2».

После исправления 300+ секторов (частично вручную, частично примитивными bash-скриптами) синхронизация завершилась успешно. В течение всей работы по восстановлению дисков производился мониторинг SMART старого диска, в котором изменялся не в лучшую сторону параметр Current_Pending_Sector, в конце концов он обнулился. Выросло значение Reallocated_Sector_Ct, что показывает, что сектора были успешно заменены на резервные.

В финале можно повторить установку загрузчика для обоих дисков (чтобы наверняка), а также проверить чек-суммы ядра и загрузчика (для тех, кто сначала читает, а потом делает — скопируйте до операций ремапа папку /boot в безопасное место, например на живой раздел).

Ссылки: