うちの屋根には長州産業の太陽光パネルが載っている。定格1.1kWの小さなセットで、パワーコンディショナもカラーモニターも長州産業だ(契約の顛末は1.1kWの太陽光発電に145万円払っていた話に書いた)。

このモニターはWi-Fiで家のLANにつながっていて、ブラウザで開くと発電と消費の数字が出る。私はこの数字を自宅のRaspberry Pi 5に取り込んで、Grafanaでグラフにしている。数字が来なくなったらSlackに通知が飛ぶようにもしてある。

先日そのアラートが鳴った。調べたら、壊れていたのはモニタではなかった。直したついでに構成が1段短くなったので、その記録を残す。

構成

モニターのページはSPAで、数字を返すAPIが見当たらない。なので、Puppeteerでページを開いて画面の数字を読み、Prometheusの形式で出す小さなexporterを書いた。Raspberry Pi 5のDocker Swarmで動かしている。

長州産業のカラーモニター(Wi-Fi)
   ↑ ブラウザでページを開いて、画面の数字を読む
exporter(Puppeteer、コンテナ)
   ↑ 15秒ごとに取りに行く
Prometheus → Grafana → Slack

このexporterは以前、51日間同じ値を出し続けていたことがある(その話)。それ以来、「値が10分取れない」「値が30分変わらない」のどちらかでプロセスが自分で落ち、Swarmが起こし直す作りになっている。

土曜の昼、「データが来ていない」が鳴った

Slackに届いたのはこれだ。

🚨 太陽光: データが来ていない 太陽光モニタのメトリクスが10分以上届いていません

20分後には復旧の通知も来た。何が起きたのかをClaude Codeに聞いて、Prometheusの履歴とコンテナのログを並べてもらった。

時刻起きたこと
12:20モニタの数字が動かなくなる
12:50「30分変わらない」でexporterが自分で再起動
12:50〜13:29再起動後、モニタにつながらない。10分ごとに再起動を3回繰り返す
13:10「データが来ていない」が発火
13:29つながって値が戻る

ログには ERR_EMPTY_RESPONSE が並んでいた。接続はできるのに、中身のない応答が返ってくる。

ここまでで出た見立ては「モニタが応答しなくなった」だった。8月の凍結も似た形だったので、私もそう思った。

負荷を減らそう、は空振りだった

再起動の履歴を30日分見ると、10月4日から12日間は再起動が1回もなかった。それが前日の夜に4回、この日に5回と、急に増えていた。

モニタが弱っているなら負荷を減らそう、という話になった。最初の提案は「5秒ごとの取得間隔を延ばす」だったが、コードを読み直すと的外れだった。exporterはページを一度開いたまま、5秒ごとに画面に出ている数字を読んでいるだけで、モニタへは通信していない。間隔を延ばしても、モニタの負担は変わらない。

それなら実際にモニタへ何が飛んでいるのかを見よう、ということになった。

中継のログに答えがあった

exporterはモニタに直接つながっていなかった。Raspberry Pi本体でsocatが中継していて、socatがモニタの .local の名前(mDNS)を引いて接続していた。

そのsocatのログに、名前が引けなかった記録が残っていた。

E getaddrinfo("<モニタの名前>.local", ...): Name or service not known

日ごとに数えるとこうなる(ログが残っているのは10月1日から)。

日付名前が引けなかった回数
10/1〜10/151日 1〜88回
10/16648回
10/17(15時まで)2,230回

前日の夜、再起動が続いた21時から22時過ぎまでだけで581回あった。昼の欠測も夜の欠測も、同じ原因だった。

決め手は、モニタのIPアドレスに直接つなぐと、その間もページがちゃんと返ってきたことだ。モニタは最初から元気だった。 壊れていたのは「名前からIPを引く」ところで、モニタに電源を入れ直してもらう必要はなかった。

名前が引けなくなった理由は、まだ突き止めていない。家のネットワークの設定を少し前に変えていて、時期は近い。ただ、関係があるかは確かめていないので、ここでは原因として書かない。

IPを固定して、名前をやめた

名前が当てにならないなら、名前を使わなければいい。

ただ、モニタのIPはルーターが自動で割り当てている(DHCP)。そのまま書き込むと、何かの拍子に番号が変わった時点でまたつながらなくなる。そこでルーター側で「このモニタには毎回同じIPを渡す」という予約を入れた。モニタ本体の設定には触れていない。

そのうえで、socatの接続先を名前からIPに書き換えた。直後から名前解決の失敗は出なくなり、値も戻った。

中継ごと要らなくなった

ここで気づいた。socatを挟んでいたのは、たぶん名前のためだ。コンテナの中からは .local の名前が引けないので、引けるRaspberry Pi本体に中継を置いたのだと思う。最初に組んだときの記録が残っていないので、推測ではある。

名前をやめたなら、中継もいらないはずだ。コンテナの中からモニタのIPに直接リクエストを投げてみると、認証を求める401が返ってきた。届いている。

exporterの接続先をモニタのIPに変えてサービスを更新すると、新しいコンテナがすぐにページを開いて数字を読み始めた。socatはサービスを止め、設定ファイルを消し、パッケージもアンインストールした。

ついでに、起動のたびに失敗していた古いWireGuardの設定も片付けた。今の外からの接続はTailscaleなので、もう使っていない。設定ファイルは空なのに、自動起動の登録だけが残っていた。

長州産業のカラーモニター(IPを予約で固定)
   ↑ IPで直接
exporter(コンテナ)
   ↑
Prometheus → Grafana → Slack

部品が1つ減って、名前解決という壊れどころも1つ減った。

思ったこと

  • アラートの文面は見立てを固定してしまう。 「データが来ていない」と来ると、データを出す側が悪いと思い込む。実際は、その手前の名前解決が壊れていた
  • 的外れな対策は、コードを1回読めば分かる。 「負荷を減らす」は筋が通って聞こえたが、取得の仕組みを確かめたら効かないと分かった
  • 中継のログが一番正直だった。 exporterのログには「空の応答」としか出ていなかった。「名前が引けない」と書いてあったのは、間にいたsocatのログだけだった
  • 何かのために置いた部品は、その理由がなくなったら外す。 理由の記録が残っていなくても、今の構成で要るかどうかは試せば分かる

家の太陽光のグラフは、今日もGrafanaに流れている。