Делаем охлаждение серверного NVMe диска для микросервера HPE Proliant Gen 10 Plus
Друзья, всем привет! Сегодня поговорим о том, как сделать апгрейд микросервера HPE Proliant Gen 10 Plus, установив на него серверный NVMe диск от Samsung (модель PM1725B) на 1600 гигабайт. NVMe SSD диски очень быстрые по сравнению с SATA SSD (быстрее в 10 -12 раз), и недавно я узнал, что можно установить в шину PCIe моего сервера HHHL (низкопрофильный) NVMe SSD диск от Samsung и впоследствии установить на него Linux (это будет тема следующей инструкции). Но проблема NVMe SSD в том, что они сильно нагреваются (данный диск в режиме idle уже был 57 градусов Цельсия). Решено было делать собственную систему охлаждения с ШИМ-вентилятором, т.к. на материнской плате сервера нет свободных PWM разъемов.
Что вам потребуется
- 1600 ГБ Серверный NVMe Samsung PM1725B [MZPLL1T6HAJQ-00005] — 1шт.
- Микроконтроллер Seeeduino Seeed Studio XIAO ESP32-C3 — 1шт.
- Адаптер USB-А — USB-C — 1шт.
- Вентилятор Noctua NF-A6X15 — 1шт.
- Соединительный кабель 4-Pin Molex to 4 PWM — 1шт.
- Соединительные провода 2pin x 2pin — 2шт.
- Кабель для передачи данных Cablexpert CC-SATAMF-715-50CM — 1шт.
- Набор длинных гаек M3 длиной 7 мм
- Воздуховод, напечатанный на 3D-принтере — 1шт.
Шаг 1 : Установка серверного NVMe диска
Устанавливаем серверный NVMe диск Samsung PM1725B в PCIe4 x16 слот, предварительно сняв железный кронштейн с Riser board, т. к. он будет мешать при установке ШИМ-вентилятора.
Включаем сервер и смотрим в UEFI, определяется ли диск.
Если все хорошо, загружаем Linux и проверяем температуру NVMe диска командой (путь к NVMе /dev/nvme0n1, у вас, возможно, будет свой):
watch -n 2 'nvme smart-log /dev/nvme0n1 | grep -i temperature'
В моем случае она уже была 57 градусов в idle режиме, что достаточно много.
Выключаем сервер.
Шаг 2 : Прошивка микроконтроллера Seeeduino
Микроконтроллер Seeeduino c адаптером будем устанавливать в свободный USB 3.2 Gen 1 порт внутри микросервера Proliant Gen 10 Plus, изначально предназначенный для boot-флешки.
Прошиваем контроллер данным скетчем в Arduino IDE:
#define FAN_PWM 4 // PWM на GPIO4 #define FAN_TACH 3 // Tach на GPIO3 const int pwmFreq = 25000; // Частота PWM (Гц) const int pwmResolution = 8; // Разрешение PWM (бит) volatile unsigned long rpmPulses = 0; // Кол-во импульсов с тахометра unsigned long lastDataTime = 0; // Время последнего получения температуры bool emergencyMode = false; // Режим аварии (нет данных) const int pulsesPerRev = 2; // Импульсов с тахометра на один оборот int currentPwm = 25; // Текущий PWM int targetPwm = 25; // Целевой PWM // Кривая зависимости температуры и PWM вентилятора struct FanCurvePoint { int temp; int pwm; } fanCurve[] = { {30, 25}, {45, 40}, {55, 120}, {65, 200}, {75, 255} }; const int curvePoints = sizeof(fanCurve) / sizeof(fanCurve[0]); // Обработчик прерывания для подсчёта импульсов с тахометра void IRAM_ATTR countRPM() { rpmPulses++; } // Конец: функция подсчёта импульсов тахометра void setup() { Serial.begin(115200); delay(500); // Настройка PWM: используем ledcAttach() согласно Espressif Arduino 3.x ledcAttach(FAN_PWM, pwmFreq, pwmResolution); // Установка начального значения PWM ledcWrite(FAN_PWM, currentPwm); // Настройка входа для тахометра с подтягивающим резистором pinMode(FAN_TACH, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(FAN_TACH), countRPM, FALLING); lastDataTime = millis(); Serial.println("=== FAN Controller started ==="); } // Конец: инициализация и настройка void loop() { // Чтение данных из Serial для получения температуры if (Serial.available()) { String input = Serial.readStringUntil('n'); input.trim(); if (input.startsWith("temperature")) { int temp = input.substring(input.indexOf(':') + 1).toInt(); if (temp > 0 && temp < 120) { targetPwm = mapTemperatureToPwm(temp); lastDataTime = millis(); emergencyMode = false; } else { Serial.printf("Invalid temp: %dn", temp); } } } // Если не получаем данные дольше 8 секунд — включаем аварийный режим с максимальным PWM if (millis() - lastDataTime > 8000) { emergencyMode = true; targetPwm = 255; Serial.println("EMERGENCY: No data from NVMe!"); } smoothFanSpeed(); // Каждые 2 секунды выводим RPM в Serial static unsigned long lastRpmTime = 0; if (millis() - lastRpmTime > 2000) { detachInterrupt(digitalPinToInterrupt(FAN_TACH)); unsigned long rpm = (rpmPulses * (60000 / 2000)) / pulsesPerRev; Serial.printf("RPM: %lu (pulses: %lu)n", rpm, rpmPulses); rpmPulses = 0; attachInterrupt(digitalPinToInterrupt(FAN_TACH), countRPM, FALLING); lastRpmTime = millis(); } } // Конец: основной цикл обработки // Функция, которая по температуре возвращает PWM согласно кривой, // выключает вентилятор при температуре ниже 30, // и не даёт PWM опускаться ниже 24 при включенном вентиляторе. int mapTemperatureToPwm(int temp) { if (temp < 30) return 0; // Вентилятор выключен при temp < 30°C for (int i = 0; i < curvePoints - 1; i++) { if (temp <= fanCurve[i + 1].temp) { int pwm = map(temp, fanCurve[i].temp, fanCurve[i + 1].temp, fanCurve[i].pwm, fanCurve[i + 1].pwm); if (pwm < 24) pwm = 24; // Минимальный PWM для стабильного запуска вентилятора return pwm; } } return fanCurve[curvePoints - 1].pwm; } // Конец: функция маппинга температуры на PWM // Функция плавного изменения скорости вентилятора к целевому значению // Минимальный PWM 24, если targetPwm не 0, иначе полный стоп (0) void smoothFanSpeed() { if (targetPwm < 24) { // Если цель ниже порога запуска — сразу выключаем вентилятор currentPwm = 0; } else { // Плавно приближаемся к targetPwm if (currentPwm < targetPwm) { currentPwm += 5; if (currentPwm > targetPwm) currentPwm = targetPwm; } else if (currentPwm > targetPwm) { currentPwm -= 2; if (currentPwm < targetPwm) currentPwm = targetPwm; } // Минимальный порог для запуска и поддержания if (currentPwm < 24) currentPwm = 24; } ledcWrite(FAN_PWM, currentPwm); Serial.printf("PWM set to: %dn", currentPwm); } // Конец: плавное изменение скорости вентилятора
Этот скетч управляет скоростью 4-х проводного вентилятора с ШИМ управлением и обратной связью по тахометру. Он регулирует скорость вращения вентилятора в зависимости от температуры, получаемой через последовательный порт от NVMe диска, используя предустановленную температурную кривую. Скетч включает аварийный режим с максимальными оборотами при отсутствии данных более 8 секунд, обеспечивает плавное изменение скорости и периодически выводит текущие показания оборотов в монитор порта.
Далее, включаем сервер.
В Linux создаем файл nvme_temp_sender.sh:
# nano /usr/local/bin/nvme_temp_sender.sh
И вставляем в него следующее:
#!/bin/bash DEVICE="/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_XX:XX:XX:XX:XX:XX-if00" NVME_DEVICE="/dev/nvme0n1" while true; do # Ждём появления устройства ESP32 while [ ! -e "$DEVICE" ]; do echo "[WARN] ESP32 не найден, жду подключения..." sleep 2 done echo "[INFO] Подключение к $DEVICE" # Открываем устройство на запись через дескриптор 3 exec 3> "$DEVICE" while true; do # Если устройство отключено — выходим из внутреннего цикла для переподключения if [ ! -e "$DEVICE" ]; then echo "[WARN] ESP32 отключен, перезапуск..." exec 3>&- break fi # Читаем температуру NVMe TEMP=$(nvme smart-log "$NVME_DEVICE" 2>/dev/null | awk '/^temperature/ {print $3; exit}') if [[ -z "$TEMP" ]]; then echo "[ERROR] Не удалось получить температуру NVMe!" TEMP=0 fi # Отправляем строку в ESP32 echo "temperature: $TEMP" >&3 sleep 2 done done
Путь к Seeeduino (DEVICE=»/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_XX:XX:XX:XX:XX:XX-if00″) у вас будет свой, найдите его по пути /dev/serial/by-id/. Также путь к NVMе (NVME_DEVICE=»/dev/nvme0n1″), у вас, возможно, будет свой.
Сохраните файл и сделайте его исполняемым:
# chmod +x /usr/local/bin/nvme_temp_sender.sh
Далее, создайте службу nvme_temp_monitor.service:
# nano /etc/systemd/system/nvme_temp_monitor.service
И вставьте в файл следующее:
[Unit] Description=NVMe Temperature Monitor for ESP32 over USB Serial After=network.target [Service] Type=simple User=root ExecStart=/usr/local/bin/nvme_temp_sender.sh Restart=always RestartSec=5 StandardOutput=syslog StandardError=syslog SyslogIdentifier=nvme_temp_monitor [Install] WantedBy=multi-user.target
Сохраните файл и перечитайте все unit-файлы systemd без перезапуска самого systemd:
# systectl daemon-reload
Включаем автозапуск службы nvme_temp_monitor в текущей сессии:
# systemctl enable --now nvme_temp_monitor.service
Далее, запускаем службу nvme_temp_monitor:
# systemctl start nvme_temp_monitor
Вкратце, служба nvme_temp_monitor запускает скрипт (nvme_temp_sender.sh), который каждые 2 секунды считывает температуру NVMe-диска и отправляет её по серийному порту USB на Seeeduino. Если Seeeduino вытащить/вставить — скрипт сам переподключается.
Шаг 3 : Подключение микроконтроллера Seeeduino к ШИМ-вентилятору
Вставляем микроконтроллер в USB 3.2 Gen 1 порт. Берем 2pin female и припаиваем черный провод к D1 (GPIO 3), а красный провод к D2 (GPIO 4) на микроконтроллере. Затем нам нужно будет разрезать соединительные провода от 4-Pin Molex, которые идут к белому 4 PWM коннектору (желтый и черный провод), а из черного 4 PWM коннектора нужно физически вытащить черный и красный провода с фиксирующими пинами (для этого придется испортить один черный 4 PWM коннектор, а также отрезать провода от 4-Pin Molex) и вставить фиксирующие пины в свободные отверстия белого 4 PWM коннектора (черный слева, т.к. это ШИМ).
Далее, черный и красный провод белого 4 PWM коннектора припаиваем к проводам соответствующего цвета 2pin male коннектора и соединяем его с 2pin female на микроконтроллере. На все пайки проводов надевайте термоусадки.
Питание в 12 вольт для ШИМ-вентилятора будем брать от разъема SATA 7+15pin на сервере с 4-го лотка. Берем кабель для передачи данных (Cablexpert CC-SATAMF-715-50CM) и отрезаем от female-разъема желтый (12 вольт) и два черных провода (Ground). Изолируем отрезанные места проводов от female-разъема и все контакты female-разъема посредством термоклея. Male-коннектор кабеля вставляем в SATA 7+15pin female в 4-м лотке.
Мне пришлось пожертвовать 4-м лотком и взять питание с SATA, но в моей конфигурации дисков он всегда был пустым, поэтому это ни на что не повлияло. Припаиваем желтый и черный провода от коннектора к проводам соответствующего цвета на белом 4 PWM коннекторе. Второй черный провод припаиваем к Ground на микроконтроллере также через 2pin коннектор (откусив от него второй красный провод, т. к. он не нужен). Это не обязательно, т. к. Ground на SATA и Ground на микроконтроллере (через USB-A) должны быть общими. Я старался все делать через коннекторы, чтобы потом было все удобно подключать/отключать.
Сам ШИМ-вентилятор кладем на радиатор NVMe диска в режиме на обдув (т. е. будем дуть на радиатор). Также убираем заглушку с задней стенки сервера, предназначенной для iLO. С этого выреза на задней стенке будет осуществляться забор воздуха.
Шаг 4 : Тестируем ШИМ-вентилятор
Включаем сервер и проверяем обороты и ШИМ-сигнал командой:
cat /dev/ttyACM0 | awk '/RPM:/ {rpm=$2} /PWM set to:/ {pwm=$4} rpm && pwm {print "RPM=" rpm, "PWM=" pwm}'
Если все хорошо, вы должны увидеть что-то типа такого:
RPM=1410 PWM=154 RPM=1410 PWM=154 RPM=1410 PWM=159 RPM=1800 PWM=159 RPM=1800 PWM=159 RPM=1800 PWM=159
Вы должны увидеть изменение оборотов и PWM (т. е. ШИМ).
Также проверяем температуру командой:
watch -n 2 'nvme smart-log /dev/nvme0n1 | grep -i temperature'
Вы должны увидеть что-то подобное:
temperature : 44 C Warning Temperature Time : 0 Critical Composite Temperature Time : 0 Temperature Sensor 1 : 44 C Temperature Sensor 2 : 43 C Temperature Sensor 3 : 43 C
В моем случае температура понизилась на 13 градусов в режиме idle, что является отличным результатом. Но потом посмотрим, как поведет себя температура под нагрузкой.
Шаг 5 : Проектирование воздуховода
Чтобы сделать приток воздуха еще более эффективным, я спроектировал в Solidworks воздуховод для моего вентилятора. STL-модель можно скачать здесь. После печати воздуховода в шестигранное углубление я установил длинную гайку M3 (предварительно покрыв ее поверхность двухкомпонентным клеем Poxipol). Когда клей высох через 10 минут, я закрепил воздуховод на место заглушки для iLO посредством винта M3 и скрепил воздуховод с вентилятором посредством соединительных винтов.
П.С.
Также рекомендую установить микросервер HPE Proliant Gen 10 Plus в вертикальном положении (для этого в комплекте предусмотрены резиновые противоскользящие накладки), чтобы забор воздуха для NVMe был внизу, а выдув основного кулерного вентилятора был вверху.
Profit :)