【入門編】Prometheus ExporterのメモリリークをGoのPprofで特定して修正する実録トラブルシューティング – 運用監視・オブザーバビリティ活用バイブル

やあ。オブザーバビリティの世界へようこそ。
システムが「なぜ動いているのか」「なぜ今、死にかけたのか」を解き明かす旅は、エンジニアにとって最高にスリリングな冒険だ。

今日は、PrometheusのカスタムExporterを運用する際に誰もが一度は直面する、「静かに忍び寄るメモリリーク」という悪夢を、Goの最強の武器である`pprof`を使って完全に駆逐する方法を伝授しよう。

この技術をマスターすれば、もう「原因不明の再起動」に怯える必要はなくなる。現場の緊張感が、確信に変わる瞬間を体験してほしい。

—

1. メモリリークの「不穏な気配」を察知せよ

カスタムExporterを運用していると、Grafanaのメモリグラフが階段状に右肩上がりを描くことがある。これは単なる「GoのGC(ガベージコレクション)の遅延」ではなく、「二度と解放されないメモリ」が積み上がっている証拠だ。

特にExporterでありがちなのが、以下のパターンだ。

  • HTTPクライアントの使い回し忘れ: 毎リクエストごとに新しい接続を張り、ソケットが溢れる。
  • Goroutineの放置: メインループから抜けたのに、チャネルの送受信待ちでGoroutineが宙に浮いている。
  • Contextの伝播ミス: キャンセルシグナルが伝わらず、背後でタスクが動き続ける。

これらは、Prometheusがスクレイピングするたびに少しずつ悪化していく。まずは、この「見えない穴」を可視化することから始めよう。

—

2. pprof:Goエンジニアの「レントゲン」

Goには標準で`net/http/pprof`という強力なプロファイリングツールが備わっている。これを使えば、メモリの使われ方をまるでレントゲン写真のように詳細に確認できる。

ステップ1:pprofを組み込む

あなたのExporterの`main.go`の先頭付近に、以下の1行を追加するだけだ。これだけで、`/debug/pprof/`というエンドポイントが解放される。

import (
_ “net/http/pprof” // これをインポートするだけで、自動的にハンドラが登録される
“net/http”
)

func main() {
// 監視用ポートを別途立てるのがベストプラクティス
go func() {
http.ListenAndServe(“localhost:6060”, nil)
}()
// …以降、メインのExporter処理
}

ステップ2:ヒーププロファイルを採取する

Exporterがメモリを食い始めたら、以下のコマンドで現状を吸い上げる。

30秒間、ヒープ使用状況をサンプリングして保存する
go tool pprof http://localhost:6060/debug/pprof/heap

ステップ3:犯人を探す「視覚的」アプローチ

ターミナルで `top` コマンドを打ってもいいが、`web` と打ち込んでみてほしい。ブラウザが立ち上がり、メモリ消費の「ヒートマップ」が表示される。
最も大きな箱(ノード)が、メモリを食い荒らしている犯人だ。 これが「どの関数から呼ばれているか」が一目でわかる。感動するだろう?

—

3. Goroutineリークを修正する:現場の鉄則

もし `pprof` で「Goroutine数が右肩上がり」であると判明したら、それは確実にコードの構造的欠陥だ。

よくある誤った実装例

// 毎リクエストで監視用のGoroutineを無限に生み出している
func recordMetrics(ch chan<- prometheus.Metric) { go func() { // ここで重い処理を実行し、終了条件がないためGoroutineが一生終わらない data := fetchExternalData() ch <- prometheus.MustNewConstMetric(...) }() }

「極限まで安全な」修正実装

Prometheusの `Collect` インターフェースを利用する際は、Context を使ってライフサイクルを制御するのがプロの作法だ。

func (c myCollector) Collect(ch chan<- prometheus.Metric) { // タイムアウト付きのContextを設定する ctx, cancel := context.WithTimeout(context.Background(), 5time.Second) defer cancel() // 処理が終わったら必ずリソースを解放する // 同期的に処理を行うか、Waitgroupで厳密に制御する result := fetchWithContext(ctx) ch <- prometheus.MustNewConstMetric(...) } ---

最後に:初心者の君へ送るエール

初めて `pprof` を触ったとき、自分の書いたコードが「こんなに無駄なメモリを使っていたのか」とショックを受けるかもしれない。だが、落ち込む必要は全くない。

「計測できないものは改善できない」。

この格言通り、メモリリークを特定できた時点で、君はすでに一人前の運用エンジニアだ。今回の `pprof` は、メモリだけでなく、CPUのボトルネックやデッドロックの調査にも使える。

まずはローカルの小さなツールでいい。`import _ “net/http/pprof”` を追加し、グラフを眺めてみることから始めよう。その先に、システムの挙動を完全に掌握できるエンジニアとしての新しい景色が待っているはずだ。

さあ、次はどんな深い迷宮に潜り込む? またいつでも聞きに来てくれ。

タイトルとURLをコピーしました