ある日のDeNAの内定者Slackにて天才的な発想が生まれた...
『DeNA20内定者でアドベントカレンダーやりませんか?』
なんて面白いアイデアなんだ。やろう。やってやろうじゃないか。我らが内定者の力でやり遂げようじゃないか。
ということで勢いに乗った僕は、卒論そっちのけで1日目のカレンダーを書くことにしました。卒論より何倍もこっちの方がたのすぃ... DeNA内定者アドベントカレンダーやっていき。
DeNA 内定者 アドベントカレンダー 1日目
さあさあ記念するべき1日目の記事にしては少し地味かもしれませんが、最近Prometheusを触る機会があったので、それを紹介してみようと思います。もっと面白いとかクレイジーな記事は他の人に任せました。
ていうか、企業の名前を借りて記事書くのってめっちゃ緊張するやん。
Prometheusとは?
オープンソースのモニタリングシステムになります。メトリクスベースの収集システムと強力なデータモデルとクエリ言語を兼ね備えているようですね。2016年に2番目のCNCFのプロジェクトとなり、2018年にはGraduatedのプロジェクトに認定されました。要するに最近のイケてる監視ツール。まあ、僕にとっては面白そうなおもちゃ以外の何者でもないんですけどね。(真の価値を理解できていない)
Hello Prometheus!
とりあえずは起動してみましょう。起動すれば雰囲気で色々わかるようになるはず...
global:
scrape_interval: 10s
scrape_configs:
- job_name: prometheus
static_configs:
- targets:
- localhost:9090
docker run -d -p 9090:9090 -v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml --name prometheus prom/prometheus
http://localhost:9090 にアクセスするとPrometheusのコンソールがお出迎えしてくれます。
今回主にお世話になるはページ以下の二つになります。
- http://localhost:9090/targets 監視対象の一覧
- http://localhost:9090/graph クエリの実行コンソール
yamlファイルにtargetとして指定されたものが/targets
に現在の監視状態とともに一覧となって表示されているはずです。Prometheusのサーバー自体も監視できるみたいですね。
取集したログを用いてなんやかんやしたくなった場合には/graph
にQueryを入力すると結果を返してくれます。Query自体はPromQLという独自の言語でできているみたいですね。
参考: https://prometheus.io/docs/prometheus/latest/querying/basics/
Node Exporter
プルベースの収集システムを持つPrometheusは、定期的に監視対象からデータを取得して回っています(今回は10秒ごと)。Node Exporterはたまに遊びに来るPrometheusに、データをまとめて渡すという役割を担っています。推奨されていないようですが適当にDockerで起動しておきます。
# 参考: https://github.com/prometheus/node_exporter
docker run -d -p 9100:9100 -v /:/host:ro --name node-exporter --pid host prom/node-exporter --path.rootfs=/host
scrape_configs:
...
- job_name: node
static_configs:
- targets:
- host.docker.internal:9100
設定を追加後Prometheusを再起動して/target
を確認すると、追加の監視対象としてNodeが追加されています。また/graph
の画面にて指定のQueryを入力することでCPU使用料やMemoryの使用料などを確認することができるようになります。もうこの時点で楽しい。最高。
Custom Metrics
もちろん自前でメトリクスを定義してPrometheusに収集させることもできます。以下は公式サイトから持ってきた例になります。2秒ごとにインクリメントするメトリクスを配信するサーバーみたいですね。
package main
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func recordMetrics() {
go func() {
for {
opsProcessed.Inc()
time.Sleep(2 * time.Second)
}
}()
}
var (
opsProcessed = promauto.NewCounter(prometheus.CounterOpts{
Name: "myapp_processed_ops_total",
Help: "The total number of processed events",
})
)
func main() {
recordMetrics()
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":2112", nil)
}
scrape_configs:
...
- job_name: app_server
static_configs:
- targets:
- host.docker.internal:2112
targetとして追加してあげることでPrometheusが自動でデータの収集を開始します。Queryには自前で定義した値を用います。この例ですとmyapp_processed_ops_total
ですね。ログ収集なんかができそうや。
Alert Manager
監視ツールなので異常検知ももちろんできます。PrometheusではAlert Managerというサーバーを別で用意して検知したアラートを捌かせています。休日にピコン!と『サーバーがヤバイよ』とメールを寄越してくるあれですね。Prometheusのやることはあくまでも監視であり、それ以上でもそれ以下でもないというスタンスのようです。以下はSlack通知の例。
global:
slack_api_url: YOUR_SLACK_INCOMING_WEB_HOOK_URL
route:
receiver: 'slack-notifications'
receivers:
- name: 'slack-notifications'
slack_configs:
- channel: "#general"
docker run -d -p 9093:9093 -v $PWD/alertmanager.yml:/etc/prometheus/alertmanager.yml --name alert-manager prom/alertmanager --config.file /etc/prometheus/alertmanager.yml
http://localhost:9093
にてAlertManagerにご対面。次は起動したAlert Managerの存在をPrometheus側で設定していきます。
...
alerting:
alertmanagers:
- static_configs:
- targets:
- host.docker.internal:9093
...
簡単。これでPrometheusが何かしらの異常を検知した場合にはAlert ManagerにAlertを送信するというフローが出来上がりました。今はAlertが何も設定されていないので、永遠に発火しませんが 笑。次はこのAlert条件を設定していきます。
...
rule_files:
- rules.yml
...
groups:
- name: isConnected
rules:
- alert: InstanceDown
expr: up == 0
for: 1m
監視対象のサーバーの中で落ちているものが存在し、その状態が1分続くようであればPrometheusが異常と判断してAlertを送ります。送られたAlertManagerが設定されている通りにAlertを処理します。今回ですとSlackに通知を送るというアクションです。これが分業ってやつですかね。
Push Gateway
定期実行タスクの結果なども収集することができます。ただし、定期実行タスクではプル型の収集システムを持つPrometheusには、永続的にメトリクスを提供することはできません。そのため、Push Gatewayを中間サーバーとして用いてここにタスクのメトリクスをPushしていきます。どうでもいいんですが、名前がまどろっこしいと思うのは僕だけでしょうか。
docker run -p 9091:9091 prom/pushgateway
...
scrape_configs:
- job_name: pushgateway
honor_labels: true
static_configs:
- targets:
- host.docker.internal:9091
Push Gatewayの起動もPrometheusへの設定も簡単ですね。Push GatewayにメトリクスをPush場合は基本的にライブラリが用意されているようです。コマンドからも送信できるみたいですね。
echo "some_metric 3.14" | curl --data-binary @- http://pushgateway.example.org:9091/metrics/job/some_job
具体的な例は後述していきます。
ISUCON9の監視
さてさてPrometheusの最も基本的な機能を紹介したので、実際にサーバーを監視してみようと思います。監視するサーバーは転がっていたisucon9にしました。画像が多くて少しめんどくさかったです.. ちなみに言語はGolangを用いました。では、いこう!
Machine Metrics
まずは基本的なCPUとMemoryですね。Node Exporterをサーバーで起動しておきます。
wget https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz
tar -zxvf node_exporter-0.18.1.linux-amd64.tar.gz
./node_exporter-0.18.1.linux-amd64/node_exporter
手軽。簡単。便利。の三拍子ですね。
Benchmark Metrics
あまり有用性はないかもしれませんが、実験も兼ねてBenchmarkerを動かした際のScoreもPrometheusで収集させてみます。Push Gatewayが使いたいだけってやつですね。
wget https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz
tar -zxvf pushgateway-1.0.0.linux-amd64.tar.gz
pushgateway-1.0.0.linux-amd64/pushgateway
scoreGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "isucon9_benchmarker_score"
})
scoreGauge.Set(float64(score))
registry := prometheus.NewRegistry()
registry.MustRegister(scoreGauge)
push.New("http://localhost:9091", "isucon9").Gatherer(registry).Push()
Application Metrics
Applicationの監視は必須なので、これもPrometheusでメトリクスを収集していきます。どうせMiddlewareで噛ませていい感じに自動でメトリクス集めてくれるやつあるやろと探したら、それっぽいものを見つけたので今回はこれを使っていきます。
import (
...
"github.com/prometheus/client_golang/prometheus/promhttp"
metrics "github.com/slok/go-http-metrics/metrics/prometheus"
"github.com/slok/go-http-metrics/middleware"
...
)
func main() {
...
mdlw := middleware.New(middleware.Config{
Recorder: metrics.NewRecorder(metrics.Config{}),
})
h := mdlw.Handler("", mux)
go func() {
if err := http.ListenAndServe(":8001", promhttp.Handler()); err != nil {
log.Panicf("error while serving metrics: %s", err)
}
}()
log.Fatal(http.ListenAndServe(":8000", h))
...
}
Prometheus側の設定
上記の監視対象らをPrometheusで設定していきます。Applicationはエンドポイントが/metrics
ではなく/
となっているので、metrics_path: /
を設定して上書きしています。
global:
scrape_interval: 10s
scrape_configs:
- job_name: app_server
metrics_path: /
static_configs:
- targets:
- [PUBLIC_IP_ADDRESS]:8001
- job_name: node
static_configs:
- targets:
- [PUBLIC_IP_ADDRESS]:9100
- job_name: pushgateway
honor_labels: true
static_configs:
- targets:
- [PUBLIC_IP_ADDRESS]:9091
結果
さてさて、まずはライブラリに書かれていたQueryを適当に実行してみると...
おお!楽しい! どこのRequestが重いとかも結構わかるやん!
どうせならGrafanaもやろう!となったので軽く調べてPrometheusと繋いでいきます。
CPUの使用料・ベンチマーカーのスコア・メモリの利用状況などもメトリクスを表示させて集めてみます。
やったぜ。
ApplicationのQueryはPCが発熱し始めましたのでやめました。やっぱりメモリ8GBは人権がないんですかね... それでは卒論執筆に戻ります 笑。