rootful / rootless をまたぐ exporter 運用を整理して smartctl-exporter を安定化した
監視 exporter の rootful/rootless 配置ルールを整理し、smartctl-exporter v0.14.0 の NVMe 対応と daemon-reload 忘れ防止を含む運用標準を確立した記録。
はじめに
監視まわりの exporter が増えてくると、単に動かすだけではなく、どれを rootful に置き、どれを rootless に置くかを揃えないと運用が崩れやすくなる。今回はその整理をしながら、smartctl-exporter を実際のパス構成に合わせて再現性のある形に落とし込んだ。
背景にあるのは、すでに進めていた node_exporter の全台展開だった。Prometheus 側で複数ノードを scrape する流れ自体は先にできていたので、次はデバイス寄りの exporter を安全に足せる運用標準が必要になった。特に smartctl-exporter は nvme0 側の癖や rootful / rootless の責務の混在で崩れやすかったので、ここを先に固めることにした。
背景と動機
node_exporter を各サーバーへ広げたメモでは、Storage サーバーの Prometheus に :9100 を順番に追加していくやり方を取っていた。あの時点で scrape 側の集約パターンはできていたが、exporter 自体の置き場所はサービスごとに揺れていた。
今回困っていたのは 2 点で、1 つは rootful / rootless をまたぐ依存が分かりにくくなること、もう 1 つは unit や compose を触ったあとに daemon-reload を忘れて、設定変更が反映されたつもりで進んでしまうことだった。加えて smartctl-exporter v0.14.0 は --smartctl.device-opts を使えないので、NVMe 周りの扱いを別途吸収しないと安定しにくい。
そのため、今回は個別の対処だけではなく、「exporter はどう置くべきか」「依存はどこまで systemd に持たせるか」「変更後に何を必ず叩くか」まで含めてひとつの運用メモにまとめた。
ディレクトリ標準
最初に、rootful / rootless の両方で迷わないようにディレクトリ標準を切った。ここが曖昧だと、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
有効化は標準的な flow で十分だった。
sudo systemctl daemon-reload
sudo systemctl enable --now smartctl-exporter.service
この形にした理由は、ホスト依存の強い exporter は system scope で管理した方が挙動が素直だからだ。特にデバイスや権限が絡むものは rootless で無理をすると、最初は動いても保守のたびに説明コストが増える。
Compose(rootful / smartctl-exporter v0.14.0)
今回の主題はここだった。smartctl-exporter v0.14.0 では --smartctl.device-opts が使えないので、NVMe 向けの -T permissive をラッパーで付与して吸収した。
まず wrapper を用意する。
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 の引数だけで頑張るより分かりやすい。
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 が固まると Prometheus 側で見える問題よりも前に exporter 自体の疎通が崩れるので、まずは短く諦める設定に振った方が運用しやすい。
起動と確認も最短で済ませる。
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 側も unit の型を揃えておく。例として 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"
ここで loginctl enable-linger を忘れると、再起動後やログイン状態で挙動が揺れやすい。rootless を常駐で扱う前提なら、ここは最初に固定しておいた方が良い。
Prometheus の scrape 設定(参考)
Prometheus 側はシンプルで、smartctl-exporter が :9633 で返すようになれば 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 まとめて)
Grafana 側はデバイス固定のフィルタを残さないことを意識した。まとめて見る時のクエリは以下で十分だった。
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 も、接続失敗からの再試行が前提で成立する。ここを systemd の強依存で縛りすぎない方が、結果として復旧性が高い。
daemon-reload を忘れないための仕組み化
もうひとつのハマりどころは daemon-reload 忘れだった。unit や compose を直したのに、systemd 側は古い定義のまま動き続けると、変更が効いているのか効いていないのか分からなくなる。
そこで、まずは雑でもいいから 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
これは権限だけの話ではなく、障害時にどこを見に行くかの話でもある。ホスト依存の強い exporter を rootful へ寄せると、問題の切り分け先が明確になる。逆にアプリ層は user scope に閉じた方が、再起動や継続運転の扱いが軽い。
最終的な運用ルール
最終的に残した運用ルールは単純だった。
- rootful と rootless をまたぐ依存は付けない
- service / compose を触ったら必ず
sdreload - ポートは設計表で固定して重複禁止
- exporter は rootful、アプリは rootless を基本にする
このルールにしておくと、以前まとめた node_exporter の全台展開とも自然につながる。Prometheus の scrape 対象は増えても、運用の責務分離は崩れない。結果として、新しい exporter を足す時に毎回ゼロから判断しなくて済むようになった。
結果
今回のメモで得られたのは、smartctl-exporter の単発対処だけではなく、監視 exporter 全体をどう配置し、どう再起動し、どこで依存を切るかの基準だった。node_exporter を横に広げる時の考え方と、NVMe を扱う smartctl-exporter の安定化を同じ枠組みで扱えるようになったのは大きい。
少なくとも、rootful / rootless を曖昧に混ぜたまま運用するより、どの層が何を持つかを固定した方が復旧も説明も早い。監視基盤は細かいツール選定より、こうした運用の一貫性の方が効いてくると感じた。
今後
今後やることは明確で、既存の *.service と compose/*.yml をこのルールへ寄せていくことだ。依存とポートの衝突しない最小セットへリライトできれば、同じ種類の詰まりはかなり減らせる。
smartctl-exporter に限れば、まず nvme1 で安定運用し、必要に応じて nvme0 を戻す。その上で Prometheus / Grafana 側の見え方を揃えれば、node_exporter と並んでハードウェア寄りの監視がかなり実用的になる。
