Proxmox VE 9.2上でTrueNAS Scale 25.10をVMとして実行しています。HDDはSAS HBAカードに接続されていて、PCIパススルーでVMに接続する設定になっています。この場合Proxmox側からは直接HDDの情報を取得できないので、TrueNAS側から温度情報を拾ってくる仕組みが必要になります。

そこで今回はTrueNASの温度をProxmoxに橋渡しして、CoolerControl でファンをコントロールしていきます。

センサー情報が取得できるかチェック

必要なパッケージのインストール

apt update
apt install lm-sensors smartmontools jq

モジュールを読み込ませる

## 恒久的
echo nct6775 > /etc/modules-load.d/nct6775.conf
## 一時的
modprobe nct6775
## 読み込めたか確認
lsmod | grep nct6775

以降はCoolerControlで調べられることなので飛ばしていい。
一応備忘録として、CoolerControlを使わずに調べる方法を残しておく。

Proxmoxに接続したHDDの温度確認

## ディスク一覧
lsblk -o NAME,MODEL,SIZE,TRAN
## 
smartctl -A /dev/sda

実行するとこんな風に温度が取得できます。

Vendor Specific SMART Attributes with Thresholds:
ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE      UPDATED  WHEN_FAILED RAW_VALUE
194 Temperature_Celsius     0x0022   119   099   000    Old_age   Always       -       33

ファン制御チップを確認

sensors
nct6799-isa-0290
Adapter: ISA adapter
fan1:                     0 RPM  (min =    0 RPM)
fan2:                   767 RPM  (min =    0 RPM)
fan3:                     0 RPM  (min =    0 RPM)
fan4:                   904 RPM  (min =    0 RPM)
fan5:                   903 RPM  (min =    0 RPM)
fan7:                  1085 RPM  (min =    0 RPM)
pwm1:                      128%  (mode = pwm)
pwm2:                       48%  (mode = pwm)
pwm3:                       48%  (mode = pwm)
pwm4:                       38%  (mode = pwm)
pwm5:                       38%  (mode = pwm)
pwm7:                       38%  (mode = pwm)

パスを確認

for d in /sys/class/hwmon/hwmon*; do
  echo "=== $d ==="
  cat "$d/name"
  ls -l "$d"/pwm* "$d"/fan*_input 2>/dev/null
done
=== /sys/class/hwmon/hwmon6 ===
nct6799
-r--r--r-- 1 root root 4096 Oct 20 21:03 /sys/class/hwmon/hwmon6/fan1_input
-rw-r--r-- 1 root root 4096 Oct 20 21:03 /sys/class/hwmon/hwmon6/pwm1
-rw-r--r-- 1 root root 4096 Oct 20 21:03 /sys/class/hwmon/hwmon6/pwm1_enable

こんな感じでファイルがいっぱいあります。

pwm*_enablePWMの制御モード0=最大出力
1=手動制御
2~=自動制御
pwm*PWMの値0~255
fan1_input回転数(rpm)

PWMの書き込み権限があるか確認

D=$(grep -l '^nct6799$' /sys/class/hwmon/hwmon*/name | head -n 1)
D=${D%/name}

echo "デバイス: $D"

for f in "$D"/pwm[0-9] "$D"/pwm[0-9]_enable "$D"/pwm[0-9]_mode "$D"/fan[0-9]_input; do
  [ -f "$f" ] || continue
  printf '%-15s = %-5s ' "${f##*/}" "$(cat "$f")"
  if [ -w "$f" ]; then
    echo "書き込み権限あり"
  else
    echo "読み取り専用"
  fi
done
デバイス: /sys/class/hwmon/hwmon6
pwm1            = 255   書き込み権限あり
pwm2            = 85    書き込み権限あり
pwm3            = 85    書き込み権限あり
pwm4            = 67    書き込み権限あり
pwm5            = 67    書き込み権限あり
pwm7            = 67    書き込み権限あり
pwm1_enable     = 5     書き込み権限あり
pwm2_enable     = 5     書き込み権限あり
pwm3_enable     = 5     書き込み権限あり
pwm4_enable     = 5     書き込み権限あり
pwm5_enable     = 5     書き込み権限あり
pwm7_enable     = 5     書き込み権限あり
pwm1_mode       = 1     書き込み権限あり
pwm2_mode       = 1     書き込み権限あり
pwm3_mode       = 1     書き込み権限あり
pwm4_mode       = 1     書き込み権限あり
pwm5_mode       = 1     書き込み権限あり
pwm7_mode       = 1     書き込み権限あり
fan1_input      = 0     書き込み権限あり
fan2_input      = 764   書き込み権限あり
fan3_input      = 0     書き込み権限あり
fan4_input      = 902   書き込み権限あり
fan5_input      = 897   書き込み権限あり
fan7_input      = 1090  書き込み権限あり

ファンの特定

#!/bin/bash
N=$1
D=$(grep -l '^nct6799$' /sys/class/hwmon/hwmon*/name | head -1)
D=${D%/name}
[[ "$N" =~ ^[0-9]+$ && -n "$D" && -w "$D/pwm${N}_enable" ]] || exit 1

CTRL="$D/pwm${N}_enable"
OLD=$(cat "$CTRL") || exit 1
trap 'echo "$OLD" > "$CTRL"' EXIT
trap 'exit 1' INT TERM

echo "変更前:"
sensors | grep -E 'fan[0-9]+:'
echo 0 > "$CTRL"
sleep 8
echo "最大回転時:"
sensors | grep -E 'fan[0-9]+:'
echo "テスト終了(元の設定に復帰)"
# ./fantest.sh 7
変更前:
fan1:                     0 RPM  (min =    0 RPM)
fan2:                   765 RPM  (min =    0 RPM)
fan3:                     0 RPM  (min =    0 RPM)
fan4:                   897 RPM  (min =    0 RPM)
fan5:                   903 RPM  (min =    0 RPM)
fan7:                  1102 RPM  (min =    0 RPM)
最大回転時:
fan1:                     0 RPM  (min =    0 RPM)
fan2:                   762 RPM  (min =    0 RPM)
fan3:                     0 RPM  (min =    0 RPM)
fan4:                   905 RPM  (min =    0 RPM)
fan5:                   897 RPM  (min =    0 RPM)
fan7:                  2993 RPM  (min =    0 RPM)
テスト終了(元の設定に復帰)

回転数が速くなったファンがわかる。

番号とマザーボードの対応表

番号M/Bのコネクタ接続先備考
1AIO_PUMP
2CPU_FAN1CPU
3CPU_FAN2
4CHA_FAN1ケースM/B CPU側
5CHA_FAN2ケースM/B 拡張カード側
7CHA_FAN3ケースHDD

CoolerControlのインストール

リポジトリ追加とインストール

## ↓たぶんいらない
apt install curl apt-transport-https
## リポジトリ追加
curl -fsSL https://apt.coolercontrol.org/setup.sh | sh
## インストール
apt update
apt install coolercontrold

自動更新無効化
サプライチェーン攻撃が怖いので更新は手動で行う方針にします。

## 更新しない
apt-mark hold coolercontrold
## 更新する
apt-mark unhold coolercontrold

起動

systemctl enable --now coolercontrold
systemctl status coolercontrold

LAN内からアクセスできるようにする

デフォルトでは127.0.0.1からしかアクセスできないので、設定を変更します

systemctl stop coolercontrold
vi /etc/coolercontrol/config.toml
# ipv4_address = "127.0.0.1"
ipv4_address = "0.0.0.0"

再起動

systemctl start coolercontrold

これで https://<ProxmoxのIPアドレス>:11987/ にアクセスするとCoolerControlの管理画面が開けます。

TrueNASのHDDの温度を取得できるようにする

TrueNASの温度が取得できるか試す
PVE側で実行。下記の100は実際のVMIDに置き換える。

qm guest exec 100 -- midclt call disk.temperatures | jq '."out-data" |= fromjson'
{
  "exitcode": 0,
  "exited": 1,
  "out-data": {
    "sdi": 29.0,
    "sdf": 29.0,
    "sdg": 27.0,
    "sdh": 27.0,
    "sdb": 27.0,
    "sdc": 27.0,
    "sde": 27.0,
    "sdd": 26.0,
    "sda": null,
    "nvme2n1": 28.85,
    "nvme3n1": 28.85,
    "nvme1n1": 27.85,
    "nvme0n1": 28.85
  }
}

CoolerControlに橋渡しするためのスクリプト作成

#!/bin/bash
VMID=100
OUT=/run/truenas-temps
INTERVAL=120
mkdir -p "$OUT"

write() { echo "$2" > "$OUT/$1.tmp" && mv "$OUT/$1.tmp" "$OUT/$1"; }

while true; do
  if raw=$(qm guest exec "$VMID" --timeout 20 -- midclt call disk.temperatures 2>/dev/null) \
     && temps=$(jq -ce '."out-data" | fromjson' <<<"$raw" 2>/dev/null); then

    # ディスク個別 (null は除外、℃ → ミリ℃)
    while read -r name mdeg; do
      write "$name" "$mdeg"
    done < <(jq -r 'to_entries[] | select(.value != null) | "\(.key) \(.value * 1000 | round)"' <<<"$temps")

    # 集計: HDD(sd*) と NVMe の最大値
    for pair in sd:hdd_max nvme:nvme_max; do
      prefix=${pair%%:*}; label=${pair##*:}
      max=$(jq -r --arg p "$prefix" '[to_entries[] | select((.key | startswith($p)) and .value != null) | .value] | if length > 0 then (max * 1000 | round) else empty end' <<<"$temps")
      [ -n "$max" ] && write "$label" "$max"
    done
  fi
  sleep "$INTERVAL"
done

systemd サービス化

[Unit]
Description=Export TrueNAS VM disk temperatures for CoolerControl
After=pve-cluster.service

[Service]
ExecStart=/usr/local/sbin/truenas-temps.sh
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now truenas-temps
ls -l /run/truenas-temps
cat /run/truenas-temps/hdd_max

/run/truenas-temps にデバイスごとのファイルが作成されます。hdd_max がすべてのHDD (/dev/sd*)の最大値なので、これをCoolerControlに読ませれば、HDDの温度でファンコントロールが可能です。

CoolerControlの設定

デバイス→カスタムセンサーで追加します。

このようにゲストOS側の温度情報も取り込めました。

次はファンカーブの設定をします。その前にマザーボード上のコネクタ名とファンが合うように名前を変更しておきます。

HDDのファンカーブを設定します。

冷却→プロファイルを追加で「グラフ」を選択→温度ソースを先ほど追加したHDDにします。カーブと、頻繁にファンが変わらないようにヒステリシスも設定しておきます。

最後に先ほど作成したプロファイルをファンに適用します。

他もファンもコントロールしたければ、同様に設定していきます。

今回の設定で1つ注意点があります。TrueNASとの橋渡しに使用したスクリプトは120秒間隔で温度を取得しています。NASとして使用中のHDDなら急激に温度が変わることはないので120秒でも十分です。しかしSSDは急激に温度が変わるので、nvme_max の値を参考にしていると冷却が遅れかねません。