В современных серверах устанавливается очень большой объем памяти. Иногда модули памяти ломаются и при ошибке сервер перезагружается. Если повезет, то умный системный контроллер подсветит неисправный модуль памяти, но может и не подсветить, тогда нужно искать, переустанавливая модули. Ситуация с перезагрузками сервера повторяется редко, но каждый раз это очень больно для бизнес-критичных приложений.
Для диагностики модулей есть хорошая программа memtest86+, но если памяти у нас 1ТБ, то полное тестирование растягивается на несколько дней, а бизнес не может так долго ждать.
Как же быть? В этой публикации я поделюсь опытом тестирования памяти сервера Gigabyte R292-4S0 с СУБД на Enteprice Linux 8 (EL8) и 1 ТБ памяти двумя методами:
С EFI загрузкой memtest86+ v7;
С автоматизированным созданием сотни libvirt-KVM виртуальных машин с memtest86+ внутри.
Запуск memtest внутри виртуальной машины... "Фу...", - скажут некоторые. И будут неправы:
На Хабре описан успешный пример такого запуска в VirtualBox;
У меня есть положительный опыт с автоматизированной PXE загрузкой сотни виртуальных машин с memtest в ESXi. Гипервизор с ошибкой в памяти падал за минуты, пока за несколько тестов мы не вычислили неисправный модуль. А с рабочими ("боевыми") виртуальными машинами сбой проявлялся раз в неделю;
Такое тестирование по моему подсчету охватывает ~97% памяти, и велик шанс того, что именно тут есть сбойные ячейки;
Такое тестирование существенно быстрей.
Приступим.
Нам понадобится сам memtest86+, его можно найти на сайте https://memtest.org. Можно было бы попробовать установить его через yum. Но, увы, в тестируемом мной EL8 так установился только memtest86+ v5.01, который EFI загрузку вообще не поддерживает. Нужна версия memtest 6 или новее.
EFI загрузка memtest+
Скачиваем Binary Files (.bin/.efi), распаковываем memtest64.efi в созданную папку /boot/efi/EFI/memtest (/boot/efi - примонтированная ФС VFAT). Мы готовы к EFI-загрузке .
Как загрузить memtest через EFI-загрузчик? На тестируемом сервере Gigabyte во время запуска нажимаем F10, попадаем в меню выбора загрузочного устройства, в нем выбираем EFI-shell.
В EFI-shell нужны следующие команды:
map покажет, какие FAT-совместимые файловые системы у нас есть;
fs0: (с двоеточием) переключится на первую файловую систему (ранее она у нас была смонтирована в /boot/efi);
cd EFI/memtest поменяет текущую папку на ту, куда вы положили memtest (тут даже работает автодополнение TAB, удобно);
ls позволит посмотреть, какие файлы есть в текущей папке;
memtest64.efi - имя скачанного бинарного файла, который нужно запустить.
После этого загрузится сам memtest, в моем случае он еще несколько минут просто так показывал зависший экран и ничего не делал. Видимо, определял объем работы. Затем работа пошла и продолжалась... 95 часов (4 дня) до первого прохода!
libvirt-KVM загрузка memtest86+
Для загрузки memtest внутри виртуальных машин нам потребуется добавить в наш EL8 дополнительные утилиты для управления kernel-virtual-machine. Запустим сразу libvirtd.service и уберем поднятый внутренний сетевой мост default - сеть нам тут не пригодится.
yum install -y qemu-kvm libvirt virt-install virt-manager virt-viewer qemu-img
systemctl enable --now libvirtd.service
virsh net-autostart default --disable
virsh net-destroy default
Создадим пустую папку, например, /opt/vm-memtest, положим в нее уже знакомый memtest64.efi. Далее в ней же необходимо создать пустой файл empty.
touch empty
В этой же папке создадим скрипт запуска, который будет использовать подобную команду:
virt-install -n memtest1 \
--memory 12288 \
--vcpus 2 \
--disk "none" \
--network "none" \
--os-variant "detect=off,name=generic" \
--location "/opt/vm-memtest,kernel=memtest64.efi,initrd=empty" \
--noautoconsole \
--serial file,path=/opt/vm-memtest/console-out-vm-memtest1 \
--extra-args "console=ttyS0,115200 console=tty0"
Разберем, что она делает:
задаст имя новой виртуальной машины - memtest1;
выделит 12GB RAM;
выделит 2vCPU;
не будет выделять диск;
не будет подключать сетевые адаптеры;
отключит контроль успешной загрузки ОС со стороны virt-install;
запустит ядро из /opt/vm-memtest/memtest64.efi с пустым initrd файлом (без него virt-install не получится, empty - это костыль);
не будет открывать консоль виртуальной машины в virt-viewer;
подключит COM-порт из файла /opt/vm-memtest/console-out-vm-memtest1 (файл будет создан автоматически);
ядру ОС виртуальной машины будут переданы параметры загрузки о том, что свой вывод нужно дублировать как в указанный COM-порт (файл), так и на консоль tty0 (к ней можно подключиться через virt-viewer, если захочется).
Объём памяти в 12ГБ появился удвоением (2vcpu в вм) деления количества памяти в системе на общее количество vcpu в 4-х сокетном сервере.
До создания виртуальных машин нужно остановить "боевые" приложения и не забыть, что СУБД (если она у вас есть) должна вернуть свои huge pages в ОС, отключить swap, а еще сбросить все КЭШи из памяти на диск:
swapoff -a
sysctl -w vm.nr_hugepages=0
sync
echo 3 > /proc/sys/vm/drop_caches
Конечный скрипт запуска получился такой:
vm-manage.sh
#!/bin/bash
# Written by Alex Golikov 2023.11.22
## Prepare host to provide KVM service
# yum install -y qemu-kvm libvirt virt-install virt-manager virt-viewer qemu-img
# systemctl enable --now libvirtd.service
# virsh net-autostart default --disable
# virsh net-destroy default
## To get Memory per One vCPU in this system run:
# awk '/MemTotal/{ printf("%.0f\n",$2/1024/'$(grep -c processor /proc/cpuinfo)') }' /proc/meminfo
vcpu_per_vm=2
memory_per_vm=12288
vmprefix="memtest"
## uncomment to limit VM number
#max_vm_number=3
#create lastvm to spend all the rest available memory
lastvm=true
#memtest_kernel="memtestx64.efi.6.20"
memtest_kernel="memtestx64.efi"
startvm() {
#avail_mem=$(awk '/MemAvailable/{ printf("%.0f",$2/1024) }' /proc/meminfo)
avail_mem=$(free -m | awk '/Mem/{print $7}')
#calculate vm_num (number of VMs to create)
vm_num=$(($avail_mem / $memory_per_vm))
[[ -z "${vm_num}" ]] && echo "Unable to calculate vm_num (number of VMs to create)" && exit 1
#Check vm limit variable
[[ -n ${max_vm_number} ]] && [[ ${vm_num} -gt ${max_vm_number} ]] && vm_num=${max_vm_number}
#Check current memtest VMs that have already created before
current_vm_number=$(virsh list --all| awk '{if($2~/'${vmprefix}'/) {print $2}}' | sed 's|'${vmprefix}'||' | sort -n | tail -1)
[[ -z "${current_vm_number}" ]] && current_vm_number=0
echo "Creating ${vm_num} VMs with ${memory_per_vm}MB onboard to test ${avail_mem}MB of RAM."
for (( i=$((${current_vm_number}+1)) ; i<=$((${vm_num} + ${current_vm_number})); i++ )); do
echo -e "##############\n\nCreating ${vmprefix}${i}..."
virt-install -n ${vmprefix}${i} \
--memory ${memory_per_vm} \
--vcpus ${vcpu_per_vm} \
--disk "none" \
--network "none" \
--os-variant "detect=off,name=generic" \
--location "${MyDir},kernel=${memtest_kernel},initrd=empty" \
--noautoconsole \
--serial file,path=${MyDir}/console-out-vm-${vmprefix}${i} \
--extra-args "console=ttyS0,115200 console=tty0"
done
}
stopvm() {
for vm in $(virsh list --all | awk '{if($2~/'${vmprefix}'/) print $2}'); do
echo -e "##############\n\nRemoving $vm"
virsh destroy $vm
virsh undefine $vm
done
echo -e "##############\n\nWork completed"
}
checklogs() {
for console_out in $(ls -1 console-out-vm-*); do
echo -en "\n${console_out} "
sed -n 's/.*\(Time: [ 0-9:]*\).*\(Pass: [ 0-9]*\).*\(Errors: [0-9]*\).*/\1 \2 \3/p' $console_out
done
echo -e "\n\nRun command 'rm -f ${MyDir}/console-out-vm-*' to remove VM console logs"
}
[[ "$1" =~ start|stop|check ]] || { echo "Usage: $0 {start|stop|check}"; exit 1; }
MyDir=$(dirname "$0") && cd "${MyDir}" && MyDir="$(pwd)"
[[ "$1" == "start" ]] && {
startvm
[[ "${lastvm}" == "true" ]] && {
memory_per_vm=$(free -m | awk '/Mem/{print $7 - 100 }')
#memory_per_vm=$(awk '/MemAvailable/{ printf("%.0f",$2/1024 - 100) }' /proc/meminfo)
#Create one more VM if we have at least 500MB of RAM left
[[ "${memory_per_vm}" -gt 500 ]] && {
echo "Spending the last ${memory_per_vm}MB"
startvm
}
}
echo -e "##############\n\nWork completed:"
virsh list --all | awk '{if($2~/'${vmprefix}'/) print $0}'
free -m
}
[[ "$1" == "stop" ]] && stopvm
[[ "$1" == "check" ]] && checklogs
Команда запуска
./vm-manage.sh start
последовательно запустит почти сотню виртуальных машин:
Команда анализа выводов в консолях виртуальных машин:
./vm-manage.sh check
Команда остановки всех созданных виртуальных машин:
./vm-manage.sh stop
Память во всех запущенных виртуальных машинах была протестирована за 5 часов, что существенно быстрей, чем на bare-metal.
Выводы
Сравним два описанных метода.
Бесспорно, метод с нативной загрузкой memtest выполняет свою работу качественней, но он это делает неприемлемо медленно для больших объемов памяти, возможности по параллельному тестированию используются не полностью, а процессор не загружается целиком.
Об этом можно косвенно судить даже по скорости оборотов вентиляторов сервера. Во время нативного исполнения memtest их скорость отображалась как 10К RPM, а с libvirt они же разогнались до 20K RPM. "Здесь мерилом работы считают усталость", - посмеются некоторые, но в случае с нативным исполнением memtest сервер никак не выдает метрики загрузки своих процессоров; приходится выкручиваться.
Метод тестирования через виртуальные машины охватывает ~97% памяти, нагружает процессоры целиком (зафиксировано с node_exporter), и в большинстве случаев этот результат будет достаточным, чтобы с стресс-тесте отбраковать неисправный модуль. Основной упор на то, что метод должен быстро воспроизводить неисправность. Ведь серия тестов может быть длинной, возможно придется несколько раз менять конфигурацию памяти в сервере перед тем, как мы найдем "виновный" модуль.
Проверка с применением виртуализации потенциально может выявить какие-то проблемы, но она абсолютно не гарантирует, что вся память исправна. Зачем тогда проверять? Затем, что этот метод раскрывает себя хорошо на системах, которые иногда сами сбоят из-за ошибок в памяти. Данный метод позволяет относительно быстро локализовать неисправность, повышая её воспроизводимость.
Комментарии (11)
INSTE
01.12.2023 14:07+2А почему бы просто кучу qemu-system-x86_64 из cli не запустить? libvirt кажется избыточным.
ruata
01.12.2023 14:07+1ИМХО есть варианты попроще Prime95 в режиме Torture Test или обычный stress с нужным набором --vm --vm-bytes --vm-stride --vm-hang --vm-keep или еще варианты https://wiki.archlinux.org/title/Stress_testing#Discovering_Errors
Если памяти много с виртуалками непонянто в каком именно модуле сбой
memtest на физике может указать вполне конкретный модуль и без management платы (да и не каждая укажет) можно конечно разобрать и через MCE в современных CPU + mcelog
iminfinitylol
01.12.2023 14:07выход из строя памяти это что то из разряда невозможного как по мне, и произойти такое может тролько при "исключительных навыках" персонала обслуживания, на моей практике с неисправностью памяти я сталкивался раза 3 и то на десктопах, не на серверах, особенности техпроцесса производства просто не допускают таких проблем.
но для масштабных систем контр таких проблем на жиденьких комплектующих обычно решается партизированием серверов на большое количество слабых систем и системы распределения, это обеспечивает стабильность и легкую диагностикуn27051538 Автор
01.12.2023 14:07+1Да, память ломается значительно реже, чем диски. Но ломается все. И enterprice сервера (mb) и их бп, и вентиляторы и память в них. Ломается память даже в контроллере СХД. ВСЕ это видел многократно. В продвинутых серверах и СХД сбой памяти не приводит к простою в обслуживании (и такое видел).
А вот поломок десктопов наоборот не видел (просто я с ними не работаю). Но я не утверждаю, что они не ломаются.
Про деление сильных систем на много более слабых - это холивар про идеальный мир. По-прежнему остается набор ПО, который плохо партиционируется. Мир по-прежнему нуждается в многосокетных системах, хотя все мечтают о микросервисах.
Comraddm
01.12.2023 14:07На десктопах и лептопах память сбоит довольно часто. Но не по причине выхода из строя, а по причине пыли, попавшей на контакты модуля или слота памяти. Сталкивался десятки раз. Обычные продув и прочистка, как правило, лечат сабж.
Comraddm
01.12.2023 14:07Файл EFI называется memtest64.efi, а в скрипте он идет как memtest.x64.efi.
Надо бы подправить.
А еще попробовал повторить на Almalinux 9.3, ругается при запуске:
ERROR Unable to open file: /opt/vm-memtest/console-out-vm-memtest1: Permission denied
Если закомментировать строчку
--serial file,path=${MyDir}/console-out-vm-${vmprefix}${i} \
то запускается.
n27051538 Автор
01.12.2023 14:07+1Возможно там есть нюанс с SELinux. На моей системе он был отключен. Попробуйте его отключить тоже. Вероятно ему контекст консольного файла не нравится.
Comraddm
01.12.2023 14:07Вы правы. После отключения SELinux все работает!
Вижу, вы исправили имя EFI в скрипте, но не до конца, надо еще убрать лишний x, чтобы стало memtest64.efi :)
И вопрос про методику тестирования. Допустим, гипервизор упал в процессе тестирования. Что делать дальше?
Запускать половину виртуальных машин с memtest и тестировать половину памяти или вынимать половину планок памяти физически и тестировать?
brammator
В кои-то веки хабрастатья, а не «как нам внедрить культуру рубашек василькового цвета в нашем смузи-стартапе»