はじめに

smartctl-exporter を安定させるため、exporter とアプリを rootful / rootless に分け、変更後の再読込手順を揃えました。NVMe 向けの対処と、監視基盤を運用するルールをまとめます。

前回の node_exporter 全台展開で、Storage の Prometheus が各ホストを監視する構成はできていました。今回は smartctl-exporter の nvme0 周りと、rootful / rootless の責務の混在を整理します。

背景と動機

node_exporter の展開では、Storage の Prometheus に各サーバーの :9100 を追加しました。scrape は集約できましたが、exporter の置き場所は揃っていませんでした。

問題は2つありました。rootful / rootless 間の依存が分かりにくいことと、unit や compose の変更後に daemon-reload を忘れることです。さらに smartctl-exporter v0.14.0 は --smartctl.device-opts を使えず、NVMe 向けの対処が必要でした。

インフラ構築後の保守で同じ判断を繰り返さないよう、配置、依存、変更後のコマンドを揃えました。

ディレクトリ標準

unit と compose の置き場所を、次のディレクトリに揃えました。

  # ルートフル(管理者常駐)
sudo mkdir -p /opt/containers/{compose,systemd}/{rootful,rootless}
sudo mkdir -p /opt/prometheus/exporters/smartctl/bin
  

smartctl-exporter のようにデバイスを扱うものは rootful、prometheus や loki などのアプリは rootless に置きます。両方で同じ階層を使います。

systemd(rootful)

smartctl-exporter は rootful の oneshot + RemainAfterExit=yes にしました。既存の node-exporter や promtail と同じ形式です。

  # /etc/systemd/system/smartctl-exporter.service
[Unit]
Description=smartctl-exporter (rootful)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/containers/compose/rootful
ExecStart=/usr/bin/podman compose up -d
ExecStop=/usr/bin/podman compose down
TimeoutStopSec=30s

[Install]
WantedBy=multi-user.target
  

次の手順で有効にします。

  sudo systemctl daemon-reload
sudo systemctl enable --now smartctl-exporter.service
  

デバイスや権限に依存する exporter は system scope に置き、保守時の確認先を揃えます。

Compose(rootful / smartctl-exporter v0.14.0)

smartctl-exporter v0.14.0 は --smartctl.device-opts を使えないため、ラッパーで NVMe 向けの -T permissive を付けました。

ラッパーは次のとおりです。

  sudo tee /opt/prometheus/exporters/smartctl/bin/smartctl-wrapper >/dev/null <<'SH'
#!/bin/sh
case " $* " in
  *"/dev/nvme"*) exec /usr/sbin/smartctl -T permissive -d nvme "$@";;
  *)             exec /usr/sbin/smartctl -T permissive "$@";;
esac
SH
sudo chmod +x /opt/prometheus/exporters/smartctl/bin/smartctl-wrapper
  

nvme0 と nvme1 で smartctl を呼ぶ際に permissive を付けます。Apple NVMe の exit=4 を吸収するための対処です。

compose は次のようにしました。

  # /opt/containers/compose/rootful/smartctl-exporter.yml
services:
  smartctl-exporter:
    image: docker.io/prometheuscommunity/smartctl-exporter:v0.14.0
    container_name: smartctl-exporter
    user: root
    privileged: true
    restart: unless-stopped
    ports:
      - "9633:9633"
    devices:
      - /dev/nvme0:/dev/nvme0
      - /dev/nvme1:/dev/nvme1
    volumes:
      - /opt/prometheus/exporters/smartctl/bin/smartctl-wrapper:/usr/local/bin/smartctl-wrapper:ro
    command:
      - --web.listen-address=:9633
      - --smartctl.path=/usr/local/bin/smartctl-wrapper
      - --smartctl.interval=60s
      - --smartctl.timeout=3s
      - --smartctl.retries=0
      - --smartctl.device=/dev/nvme0
      - --smartctl.device=/dev/nvme1
  

--smartctl.timeout=3s と --smartctl.retries=0 を明示しました。/metrics が止まらないよう、応答しないデバイスは短い時間で諦めます。

起動して応答を確認します。

  sudo systemctl restart smartctl-exporter.service
curl -s 127.0.0.1:9633/metrics | grep 'smartctl_device{device="nvme'
  

まだ止まる場合は、まず nvme1 だけに絞ります。

  # 一時的にnvme1のみ
command:
  - --web.listen-address=:9633
  - --smartctl.path=/usr/local/bin/smartctl-wrapper
  - --smartctl.device=/dev/nvme1
  - --smartctl.interval=60s
  - --smartctl.timeout=3s
  - --smartctl.retries=0
  

scrape を復旧してから、問題のデバイスを戻す順序で切り分けます。

systemd(rootless)の雛形

rootless は mktxp.service を例に、次の形式へ揃えます。

  # /opt/containers/systemd/rootless/mktxp.service
[Unit]
Description=Podman Compose Stack for mktxp
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/prometheus/exporters/mktxp/compose.d
ExecStart=/usr/bin/podman-compose up -d
ExecStop=/usr/bin/podman-compose down
TimeoutStopSec=30s

[Install]
WantedBy=default.target
  

user scope で有効にします。

  systemctl --user daemon-reload
systemctl --user enable --now /opt/containers/systemd/rootless/mktxp.service
loginctl enable-linger "$USER"
  

常駐させる rootless サービスでは loginctl enable-linger も設定します。再起動後やログイン状態に動作が左右されないようにします。

Prometheus の scrape 設定(参考)

smartctl-exporter が :9633 で応答すれば、Prometheus に次の scrape 設定を加えます。

  scrape_configs:
  - job_name: "smartctl"
    scrape_interval: 60s
    scrape_timeout: 10s
    static_configs:
      - targets: ["storage-server:9633"]
  

node_exporter と同じく、中央の Prometheus が各 exporter を pull します。scrape 設定と exporter の安定化を分けて確認します。

Grafana クエリ(nvme0 / nvme1 まとめて)

nvme0 / nvme1 をまとめて見るクエリです。

  smartctl_device{device=~"nvme.*"}
# 温度例
smartctl_temperature_celsius{device=~"nvme.*"}
# 健康
smartctl_health_ok{device=~"nvme.*"}
  

device="nvme1" が残ると、nvme0 を戻しても片方しか表示されません。exporter の変更に合わせてダッシュボードも確認します。

よくある障害と復旧コマンド

復旧時に使うコマンドをまとめました。

  # /metricsが固まる→nvme0でブロックのサイン。まず除外して疎通回復
# その後ラッパーで戻すのが安全。

# コンテナがStopping状態で動かない
podman rm -f smartctl-exporter || true

# 名前重複エラーで起動できない
podman run --replace -d --name smartctl-exporter ...

# 9633が開いているか
ss -ltnp | grep :9633

# exporterがnvme0/1を何台見てるか
podman logs smartctl-exporter | sed -n 's/.*Number of devices found.*/&/p'
podman logs smartctl-exporter | grep -E 'readjson|Invalid Log Page|device not found|Listening on'
  

Stopping のまま動かない場合やコンテナ名が衝突した場合は、一度状態を戻して監視を復旧します。

rootless / rootful の依存をどう扱うか

systemd の依存は、system 同士、user 同士の同じスコープ内で扱います。rootful / rootless 間には厳密な依存を付けません。

運用方針は次のとおりです。

  • 依存は疎結合にして、ネットワーク越しの再試行で吸収する
  • 同一スコープ内だけ After= / Wants= / Requires= / PartOf= を使う
  • ポート重複は設計表で防ぐ

promtail -> loki と prometheus -> exporters は接続失敗後の再試行で復旧させます。

daemon-reload を忘れないための仕組み化

unit や compose の変更後は daemon-reload が必要です。古い定義のまま動かさないため、次の手順に揃えました。

sdreload を用意します。

  # /usr/local/bin/sdreload
#!/bin/sh
systemctl daemon-reload || true
systemctl --user daemon-reload || true
echo "[done] daemon-reload (system & user)"
chmod +x /usr/local/bin/sdreload
  

変更後は再読込、再起動、状態確認を行います。

  # rootful(system scope)
sdreload
systemctl restart smartctl-exporter.service
systemctl status smartctl-exporter.service -n 30

# rootless(user scope)
sdreload
systemctl --user restart loki.service promtail.service
systemctl --user status loki.service promtail.service -n 30
  

この手順を毎回使い、再読込の漏れを防ぎます。

rootful / rootlessの配置基準

配置の基準は次のとおりです。

  • rootful(system): カーネルやデバイスに触るもの
  • rootless(user): アプリ層

対象サービスは次のように分けます。

  • rootful: smartctl-exporter, node-exporter
  • rootless: loki, prometheus, promtail, grafana

ホスト側の問題は system scope、アプリ側の問題は user scope で確認します。配置を固定すると、復旧時の確認先も決まります。

最終的な運用ルール

変更後の運用ルールです。

  • rootful と rootless をまたぐ依存は付けない
  • service / compose を触ったら必ず sdreload
  • ポートは設計表で固定して重複禁止
  • exporter は rootful、アプリは rootless を基本にする

node_exporter の scrape 対象が増えても、配置の基準は変えずに追加できます。

結果

NVMe の対処に加えて、exporter の配置、再起動、依存の範囲を揃えました。node_exporter の全台展開と smartctl-exporter の保守を、同じルールで扱えます。

今後

今後は既存の *.service と compose/*.yml もこの形式へ寄せ、依存とポートの衝突を減らします。

smartctl-exporter はまず nvme1 で安定させ、必要に応じて nvme0 を戻します。その後、Prometheus / Grafana に両デバイスが表示されるか確認します。