Featured image of post GoでPrometheus用のExporterをつくる

GoでPrometheus用のExporterをつくる

自作Exporter

Prometheusでスクレイプする用のExporterを自作してみた.


やったことのまとめ

  • GoPrometheusクライアント1を使って, 自分で設定したメトリクスを表示できるExporterを作成した
  • 作成したExporterPrometheusDockerで動かし, スクレイプできることを確認した

https://github.com/uzimihsr/example-exporter

つかうもの

やったこと

Exporterの作成

# 作業用ディレクトリとgo.modの作成
$ pwd
/path/to/workspace
$ mkdir example-exporter && cd example-exporter
$ go mod init github.com/uzimihsr/example-exporter
go: creating new go.mod: module github.com/uzimihsr/example-exporter
$ touch main.go

main.goはこんな感じで作る.
書き方はDocsの例2Prometheusクライアントのサンプルコード3を参考にした.

package main
import (
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// メトリクスの準備
var (
// Counter
exampleCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "example_total",
Help: "Example Counter",
},
[]string{"hoge"},
)
// Gauge
exampleGauge = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "example_number",
Help: "Example Gauge",
},
[]string{"fuga"},
)
)
// 30秒ごとにCounterの値を1つ増やす関数
func count() {
for {
exampleCounter.With(prometheus.Labels{"hoge": "hogehoge"}).Inc()
time.Sleep(30 * time.Second)
}
}
// 10秒ごとにGaugeに乱数をセットする関数
func setRandomValue() {
for {
rand.Seed(time.Now().UnixNano())
n := -1 + rand.Float64()*2
exampleGauge.With(prometheus.Labels{"fuga": "fugafuga"}).Set(n)
time.Sleep(10 * time.Second)
}
}
func main() {
// 各メトリクスの値を更新する関数をgo routineで実行
go count()
go setRandomValue()
// /metricsへのリクエストを処理
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":2112", nil)
}
view raw main.go hosted with ❤ by GitHub

ポイントは/metricsへのリクエストをハンドリングする処理と別にgo routineを使ってメトリクスを更新する処理を入れているところ.
こうすることで各メトリクスが並行に更新され, リクエストが来るたびにメトリクスの最新の値を返すことができる.

試しに動かしてみる.

# exporterの実行
$ go run main.go

ブラウザで http://localhost:2112/metrics を開く.

こんな感じでPrometheusが読めるフォーマットのメトリクスが表示された.
メトリクス定義に記述したメトリクス名, 説明文, ラベルが反映されていることがわかる.

# HELP example_number Example Gauge
# TYPE example_number gauge
example_number{fuga="fugafuga"} -0.0056473753420378525
# HELP example_total Example Counter
# TYPE example_total counter
example_total{hoge="hogehoge"} 13

Prometheusでスクレイプ

せっかくなので, Prometheusでスクレイプしてみる.
今回は試しにDocker上でPrometheusのコンテナと今回作った example-exporter のコンテナを同時に動かす.

example-exporter 用のDockerfileの書き方はGoアプリをDockerのscratchイメージで動かすを参考にする.

# ビルド用イメージ
FROM golang:1.14
# mainパッケージがあるディレクトリ(.)をまるごとコピー
COPY . ./goapp
WORKDIR ./goapp
# goapp内のgo.mod, go.sumで依存関係を管理している場合に使用
RUN go mod download
# クロスコンパイル
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app .
# バイナリを載せるイメージ
FROM scratch
WORKDIR goapp
# ビルド済みのバイナリをコピー
COPY --from=0 /app ./
# httpsで通信を行う場合に使用
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENTRYPOINT ["./app"]
view raw Dockerfile hosted with ❤ by GitHub

さらにPrometheusの設定ファイルprometheus.ymlと,
2つのコンテナを動かすためにdocker-compose.ymlを作成する.

global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'example-exporter'
static_configs:
- targets: ['example-exporter:2112'] # docker-composeで起動していればコンテナ名で名前解決できる
view raw prometheus.yml hosted with ❤ by GitHub
version: '3'
services:
prometheus:
image: prom/prometheus
container_name: prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- 9090:9090
example-exporter:
build: ./ # ディレクトリの内容をビルドしたimageを使う
container_name: example-exporter
ports:
- 2112:2112

この状態でdocker-composeで2つのコンテナを起動する.

# ディレクトリの状態
$ tree .
.
├── Dockerfile
├── docker-compose.yml
├── go.mod
├── go.sum
├── main.go
└── prometheus.yml

# docker-composeでコンテナを起動
$ docker-compose up -d
Creating network "example-exporter_default" with the default driver
Creating example-exporter ... done
Creating prometheus       ... done

コンテナが起動した状態で http://localhost:9090/graph をホストのブラウザで開くと, Prometheusの画面が表示される.
無事に起動できていれば example-exporter がスクレイプできている.

試しに example-exporter のメトリクス名でクエリを投げてみると, ちゃんと時系列の値が表示される.
Prometheus
Prometheus

example_totalmain.goで設定した通り30秒ごとに値が加算され,
example_numbermain.goで設定した値の変化の間隔(10秒)がPrometheusscrape_interval(15秒)より短いために15秒ごとに値が変化している.

やったぜ.
自作のExporterをスクレイプして, 設定したとおりにメトリクスが変化していることが確認できた.

おわり

PrometheusでスクレイプできるかんたんなExporterを自作してみた.
Prometheus自体がGoで書かれているだけあって, Goだとかなり楽に記述できた. と思う.
特にgo routineのおかげでメトリクスとHTTPハンドラの並行処理が簡単に書けるのがいいと思った.
自作のAPIとかにこんな感じでExporterを実装すれば監視がめちゃめちゃ楽になりそう.

おまけ