TECH · HOMELAB · OBSERVABILITY

VMが起動しない日の話 -- Proxmoxクラスタで e1000e・NFS・監視ギャップが同時に刺さった複合障害

Proxmoxクラスタでブログサーバの自動起動が失敗。追っていくとNICのHardware Unit Hang、QNAPのNFSv4 pseudoroot外れ、ストレージ監視の欠落が同時に噛み合った複合障害でした。調査ログと恒久対策までの全記録です。

tech 2026-05-13 38 min read by ちらりん
cover · 1024×1024

はじめに

ブログサーバのVMが起動しませんでした。

2026年4月22日 14時51分。nuc2(Proxmox pve2)の dmesg に e1000e 0000:00:1f.6 eno1: Detected Hardware Unit Hang が連発し始めたのが発端です。ネットワークが不安定になったので物理電源ボタンを短押しして再起動を走らせました。

復帰後、ちらりんブログのVM(VMID 100)が自動起動に失敗します。エラーは Starting VM 100 failed: mkdir /mnt/pve/nas01/dump: Read-only file system。一次対応でVM自体は数分で動かせましたが、掘り下げていくと3つの独立した問題が同時に噛み合っていたことが分かりました。

  1. nuc2 の Intel I219-V が e1000e Hardware Unit Hang を激増させていた
  2. QNAP NAS の NFSv4 pseudoroot bind mount が1週間前から外れていた
  3. そもそもストレージの inactive を検知できる監視がなかった

本記事はこの3点セットを解きほぐして、恒久対策まで持っていった調査ログです。同じ構成(Proxmox + QNAP NFS + Intel I219-V)を使っている方の役に立てば幸いです。


発端: VM 100 の自動起動が Read-only file system で失敗

nuc2 の再起動は 14:56:25 に完了しました。その直後、VM 100 の自動起動が失敗したログが残っています。

snippet
Apr 22 14:56:39 nuc2 pve-guests[3142]: Starting VM 100 failed:
  mkdir /mnt/pve/nas01/dump: Read-only file system

/mnt/pve/nas01 は QNAP NAS (192.168.1.251) を NFSv4 でマウントしている領域です。ここにはバックアップ (dump) と ISO、それから local-iso 相当のボリュームが載っています。

VMの起動シーケンスで「dumpディレクトリを作れない」というのは、普通ならバックアップが走る時の話であって、VM起動自体が止まる直接原因にはなりません。しかし Proxmox はストレージのヘルスチェックの一環でこの書き込みを試みるため、pending 判定で起動前チェックが止まる挙動を見せます。


一次対応: まずVMを動かす

原因調査の前にサービスは戻したい。chillarin-sv01 のCDROMがこのストレージ上のISOを参照していたので、一時的に切り離してから起動しました。

bash
# pve2 上で実行
qm set 100 --delete ide2    # CDROMデバイスを外す
qm start 100

これで VM 100 は起動し、ブログは復帰しました。一次対応と恒久対策は分ける。一次対応は「ISO参照を外す」だけで、根本原因には触れていません。ここから犯人探しに入ります。


原因1: QNAP NFSv4 の pseudoroot bind mount が外れていた

まず疑ったのはNFS共有そのものの状態です。Proxmox 側で pvesm status を叩くとこうなっていました。

snippet
Name             Type     Status           Total            Used       Available        %
local             dir     active        98497780         9523904        83922496    9.67%
local-lvm     lvmthin     active      1843466240       149320765      1694145474    8.10%
nas01         nfs      inactive               0               0               0    0.00%

inactive。クラスタ全ノード(pve1/pve2/pve3)で同じ状態でした。

NFSサーバ側の exports は正常です。

bash
$ showmount -e 192.168.1.251
Export list for 192.168.1.251:
/nas01                  172.16.1.0/24

mount コマンドで見ると、マウント自体は成立しているように見えます。

snippet
192.168.1.251:/nas01 on /mnt/pve/nas01 type nfs4 (rw,...)

ところが中身を見に行くと空でした。

bash
$ ls /mnt/pve/nas01/
$ stat -f /mnt/pve/nas01/
  File: "/mnt/pve/nas01/"
    ID: ... Namelen: 255     Type: nfs
Block size: 4096       Fundamental block size: 4096
Blocks: Total: 128    Free: 128    Available: 128

Total 128ブロック。テラバイト級のはずのNASが128ブロックしか見えない。これは QNAP の NFSv4 pseudoroot 仕様で、本来は /share/CACHEDEV1_DATA/nas01 への bind mount が差し込まれる /share/NFSv=4/nas01/ ディレクトリが空のままになっていた状態でした。

NASに入って確認すると、タイムスタンプは4月14日 01:31。今回の騒ぎの 約1週間前 にこのbind mountが外れていました。ファームアップデートか、スケジュールタスクの副作用か、どこかで外れたのに誰も気づかず今日まで来た、という筋です。

直し方

最初に試した /etc/init.d/nfs reexport では直りませんでした。exports の再読み込みだけでは pseudoroot の bind は戻りません。

bash
# QNAP側で実行(admin)
/etc/init.d/nfs restart

これで /share/NFSv=4/nas01/ 配下に bind mount が入り直り、fsid が再発行されました。ただしクライアント側(Proxmox)は stale file handle を掴んだままなので、強制アンマウントが必要です。

bash
# 全pveノードで実行
umount -f -l /mnt/pve/nas01
pvesm set nas01 --disable 1
pvesm set nas01 --disable 0

--disable のトグルで Proxmox 側にマウント再試行を促します。これで3ノードとも active に戻りました。

学んだこと

NFSv4 の pseudoroot は「見た目はマウントできている、でも中身はカラ」という気づきにくい半死状態を作ります。mount の出力や showmount -e だけでは発見できません。stat -f のブロック数を見る、df の Size 列を見る、もしくは定期的に canary ファイルを置いて読めるか確認する、といった能動的な監視が必要です。


原因2: nuc2 の e1000e Hardware Unit Hang が激増していた

ここで nuc2 の dmesg に戻ります。ネットワーク不安定の原因は Intel I219-V の e1000e ドライバで、これは古くから知られているバグです。

snippet
e1000e 0000:00:1f.6 eno1: Detected Hardware Unit Hang:
  TDH                  <ae>
  TDT                  <b4>
  next_to_use          <b4>
  next_to_clean        <ad>
buffer_info[next_to_clean]:
  time_stamp           <1005a3b5c>
  next_to_watch        <ae>
  jiffies              <1005a3b80>
  next_to_watch.status <0>
MAC Status             <40080083>
PHY Status             <796d>
PHY 1000BASE-T Status  <3800>
PHY Extended Status    <3000>
PCI Status             <10>

TSO(TCP Segmentation Offload)/ GSO(Generic Segmentation Offload)との組み合わせで送信キューがスタックする、というやつです。NIC自体を交換するまで完治はしませんが、TSO/GSO を切ると実害をかなり減らせます。

発生回数の推移

journald のブート別ログ (journalctl -b -N) から Hardware Unit Hang の件数を数えるとこうでした。

Boot期間Hardware Unit Hang 件数
-52025-10-20 〜 2025-11-210
-42025-11-21 〜 2026-03-206,575
-3(短期間)296
-2(短期間)335
-12026-04-11 〜 2026-04-22(今回)23,104

4ヶ月前からじわじわ出始めて、直近の稼働期間では1桁増えました。温度か、トラフィックパターンか、ドライバの挙動の変化か、一意には絞れません。ただ対策自体は定番なので先に恒久化します。

TSO/GSO の無効化(即時+恒久)

手動で止めるならワンライナー。

bash
# pve2 上で実行
ethtool -K eno1 tso off gso off

恒久化は /etc/network/interfaceseno1 セクションに post-up フックを入れます。

snippet
iface eno1 inet manual
    post-up /sbin/ethtool -K eno1 tso off gso off

これで次回以降、ブリッジが上がる時点で自動的に TSO/GSO を落とします。

他ノードはどうか

nuc1 は別チップ(Intel I225-V、igc driver)で、ブート -5 以降の全期間でゼロ件でした。igc は e1000e と実装が違うので同じ轍は踏みません。nuc3 は Wi-Fi 運用(iwlwifi)なので対象外です。今回の対策は nuc2 のみに入れました。

学んだこと

e1000e は「前触れなく激増する」タイプのバグです。0 → 1桁 → 4桁 → 5桁 と、閾値なく増えます。ドライバ統計を監視メトリクスに入れておくべきでした。Prometheus の node_exporter には node_netstat_*node_network_transmit_drop_total がありますが、e1000e 固有の Hardware Unit Hang は dmesg テキストからLokiで拾う必要があります。ここは別途仕込みます。


原因3: ストレージの inactive を検知できる監視がなかった

今回の根っこはここです。NFS が1週間も inactive だったのに気づけなかった。監視基盤(chillarin-ops の Prometheus + Grafana)は動いていましたが、Proxmox ストレージの active/inactive を示すメトリクスがそもそも存在しませんでした

prometheus-pve-exporter が出してくるのは pve_storage_infopve_storage_shared の2系統だけ。status ラベルも値もありません。調べた限りでは2026年4月時点の pve-exporter 3.x にストレージステータスは実装されていません。

ないなら自分で作ります。node_exporter の textfile collector を使って、pvesm status の出力を毎分メトリクス化することにしました。

1. メトリクス書き出しスクリプト

各 pve ノードに /usr/local/bin/pve_storage_status.sh を配置します。

bash
#!/bin/bash
# pve_storage_status.sh: pvesm status を Prometheus textfile に書き出す

set -eu
TEXTFILE_DIR="/var/lib/node_exporter/textfile"
TMPFILE="$(mktemp ${TEXTFILE_DIR}/pve_storage.prom.XXXXXX)"

{
  echo "# HELP pve_storage_active Proxmox storage active state (1=active, 0=inactive)"
  echo "# TYPE pve_storage_active gauge"
  /usr/sbin/pvesm status | awk 'NR>1 {
    if ($3 == "active") v=1; else v=0;
    printf "pve_storage_active{storage=\"%s\",type=\"%s\"} %d\n", $1, $2, v
  }'
  echo "# HELP pve_storage_last_update_timestamp Unix timestamp of last update"
  echo "# TYPE pve_storage_last_update_timestamp gauge"
  date +'pve_storage_last_update_timestamp %s'
} > "$TMPFILE"

mv "$TMPFILE" "${TEXTFILE_DIR}/pve_storage.prom"

注意点: cron 環境ではPATHに /usr/sbin が含まれません。ここで最初ハマりました。pvesm を絶対パスで呼ぶのが確実です。

2. cron で毎分実行

snippet
# /etc/cron.d/pve-storage-status
* * * * * root /usr/local/bin/pve_storage_status.sh

3. node_exporter に textfile collector を有効化

systemd unit の ExecStart に引数を足します。

snippet
# /etc/systemd/system/node_exporter.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/local/bin/node_exporter \
  --collector.textfile.directory=/var/lib/node_exporter/textfile
bash
systemctl daemon-reload
systemctl restart node_exporter

Prometheus 側で pve_storage_active{storage="nas01"} が取れるようになります。

4. アラートルール

alert_rules.yml に2つ追加しました。

yaml
- alert: PVEStorageInactive
  expr: pve_storage_active == 0
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "Proxmox storage {{ $labels.storage }} is inactive on {{ $labels.instance }}"
    description: "pvesm status reports storage={{ $labels.storage }} as inactive for >5 minutes."

- alert: PVEStorageStatusStale
  expr: time() - pve_storage_last_update_timestamp > 300
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "pve_storage_status.sh has not updated in over 5 minutes on {{ $labels.instance }}"

PVEStorageStatusStale は監視メトリクス自体が更新されなくなったケース(スクリプト停止、cron停止、node_exporter停止)を拾うための dead-man switch です。監視を監視する、というやつです。

学んだこと

監視は「既存メトリクスの組み合わせで表現できないもの」が必ず残ります。今回の pve_storage_active は代表例で、standard exporter には入っていません。textfile collector は自前メトリクスの最小単位として便利ですが、dead-man switch をセットで書かないと「黙って止まる」 典型になります。


対策3点セットのまとめ

今回の対応で入れたものを一枚にまとめます。

対策場所目的
ethtool -K eno1 tso off gso off/etc/network/interfaces に恒久化pve2 (nuc2)e1000e Hardware Unit Hang 抑制
QNAP /etc/init.d/nfs restartNAS-QNAPNFSv4 pseudoroot 再構築
pve_storage_status.sh + cron + node_exporter textfile全pveノードストレージ inactive の可視化
PVEStorageInactive / PVEStorageStatusStale アラートchillarin-ops (Prometheus)inactive 状態を5分以内に検知

一次対応で外した CDROM も、NFSが戻ったので元通りマウントし直して構成を復元しました。


教訓

調査を通じて残った気づきを並べます。

  • 「VMが起動しない」は症状であって原因ではない。エラーメッセージ(Read-only file system)が指すレイヤーと、本当の原因レイヤー(NFSサーバ側のbind mount外れ)は離れていることが多い。
  • 半死状態は気づけない。NFSv4 pseudoroot が外れても mount は成功します。見かけ上のステータスではなく、実データが読めるかを確認するヘルスチェックが必要。
  • 標準メトリクスには穴がある。pve-exporter にストレージステータスがない、というのは使ってみないと分かりません。textfile collector で埋められる部分は早めに埋める。
  • dead-man switch をセットで書く。自前メトリクスは「値が来ないこと」自体を検知しないと意味がない。
  • 複合障害は因果が絡む。NICハングで再起動 → 再起動でVM自動起動 → その時に初めてNFS半死が顕在化、という順序でした。1つずつ単独なら気づく仕組みがあったかもしれませんが、3つ同時だと「一番外側の現象」しか見えなくなります。

ホームラボでも本番サイトでも、複合障害は同じ顔で来ます。今回は幸い、ブログサーバは一次対応で数分で復帰し、恒久対策を冷静に入れる時間が取れました。その余裕は「最初にサービスを戻す」と「原因を追う」を切り離せたからです。分けて進める、を次も守りたいと思います。


有料コンテンツ案内

本記事で触れた恒久対策の実ファイルは、有料コンテンツ「自宅サーバ構築シリーズ」で提供しています。

  • pve_storage_status.sh 本番スクリプト
  • node_exporter systemd override ファイル全量
  • Prometheus alert_rules.yml の該当セクション
  • QNAP NFSv4 pseudoroot 状態確認用のチェックスクリプト
  • 本件で使った journalctl / pvesm / ethtool の調査コマンド集

興味があれば 有料コンテンツ案内ページ をご覧ください。


自宅サーバ運用の全体像は 自宅サーバ運用の完全ガイド — Proxmox + Cloudflare Tunnel + Docker で個人ブログを公開し続ける にまとめています。Proxmox クラスタ・公開経路・Docker 本番化・障害対応までを 1 ページで通読できる Pillar ガイドです。


自宅サーバとネットワークの観測の全体像は 自宅 Observability の完全ガイド — Prometheus + Grafana + Loki で家のサーバとネットワークを観測し続ける にまとめています。監視基盤の設計判断から複合障害・retention 運用までを 1 ページで通読できる Pillar ガイドです。

· · ·

コメント