Prometheusを使ってISUCON9の監視をやってみる

ある日のDeNAの内定者Slackにて天才的な発想が生まれた...

『DeNA20内定者でアドベントカレンダーやりませんか？』

**なんて面白いアイデアなんだ。やろう。やってやろうじゃないか。我らが内定者の力でやり遂げようじゃないか。**

ということで勢いに乗った僕は、卒論そっちのけで1日目のカレンダーを書くことにしました。卒論より何倍もこっちの方がたのすぃ... DeNA内定者アドベントカレンダーやっていき。

# DeNA 内定者 アドベントカレンダー 1日目

さあさあ記念するべき1日目の記事にしては少し地味かもしれませんが、最近Prometheusを触る機会があったので、それを紹介してみようと思います。もっと面白いとかクレイジーな記事は他の人に任せました。

ていうか、企業の名前を借りて記事書くのってめっちゃ緊張するやん。


### Prometheusとは?

https://prometheus.io/

オープンソースのモニタリングシステムになります。メトリクスベースの収集システムと強力なデータモデルとクエリ言語を兼ね備えているようですね。2016年に２番目のCNCFのプロジェクトとなり、2018年にはGraduatedのプロジェクトに認定されました。要するに最近のイケてる監視ツール。まあ、僕にとっては面白そうなおもちゃ以外の何者でもないんですけどね。(真の価値を理解できていない)

## Hello Prometheus!

とりあえずは起動してみましょう。起動すれば雰囲気で色々わかるようになるはず...

```yaml:prometheus.yml

global:
    scrape_interval: 10s
scrape_configs:
    - job_name: prometheus
      static_configs:
          - targets:
              - localhost:9090
```

```sh

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で起動しておきます。

```shell

# 参考: 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
```

```yaml:prometheus.yml
scrape_configs:
    ...
    - job_name: node
      static_configs:
          - targets:
              - host.docker.internal:9100
```

設定を追加後Prometheusを再起動して`/target`を確認すると、追加の監視対象としてNodeが追加されています。また`/graph`の画面にて指定のQueryを入力することでCPU使用料やMemoryの使用料などを確認することができるようになります。もうこの時点で楽しい。最高。

## Custom Metrics

もちろん自前でメトリクスを定義してPrometheusに収集させることもできます。以下は公式サイトから持ってきた例になります。2秒ごとにインクリメントするメトリクスを配信するサーバーみたいですね。

https://prometheus.io/docs/guides/go-application/

```golang

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)
}
```

```yaml:prometheus.yml
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通知の例。


```yml:alertmanager.yml
global:
  slack_api_url: YOUR_SLACK_INCOMING_WEB_HOOK_URL
route:
  receiver: 'slack-notifications'
receivers:
  - name: 'slack-notifications'
    slack_configs:
      - channel: "#general"
```

```sh

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側で設定していきます。

```yml:prometheus.yml
...
alerting:
    alertmanagers:
        - static_configs:
            - targets:
                - host.docker.internal:9093
...
```

簡単。これでPrometheusが何かしらの異常を検知した場合にはAlert ManagerにAlertを送信するというフローが出来上がりました。今はAlertが何も設定されていないので、永遠に発火しませんが 笑。次はこのAlert条件を設定していきます。


```yml:prometheus.yml
...
rule_files:
    - rules.yml
...
```

```yml: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していきます。どうでもいいんですが、名前がまどろっこしいと思うのは僕だけでしょうか。

```sh

docker run -p 9091:9091 prom/pushgateway
```

```yml:prometheus.yml
...
scrape_configs:
    - job_name: pushgateway
      honor_labels: true
      static_configs:
        - targets:
            - host.docker.internal:9091

```

Push Gatewayの起動もPrometheusへの設定も簡単ですね。Push GatewayにメトリクスをPush場合は基本的にライブラリが用意されているようです。コマンドからも送信できるみたいですね。

```sh

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をサーバーで起動しておきます。

```sh

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が使いたいだけってやつですね。

```sh
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
```

```go:/cmd/bench/main.go
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で噛ませていい感じに自動でメトリクス集めてくれるやつあるやろと探したら、それっぽいものを見つけたので今回はこれを使っていきます。

https://github.com/slok/go-http-metrics

```go:main.go

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: /`を設定して上書きしています。

```yaml:prometheus.yml

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を適当に実行してみると...

<img width="1440" alt="スクリーンショット 2019-11-21 22.44.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185233/de7ef499-8751-402b-439d-800a8a8f7c5c.png">

おお！楽しい！ どこのRequestが重いとかも結構わかるやん！
どうせならGrafanaもやろう!となったので軽く調べてPrometheusと繋いでいきます。

CPUの使用料・ベンチマーカーのスコア・メモリの利用状況などもメトリクスを表示させて集めてみます。

<img width="1440" alt="スクリーンショット 2019-11-22 0.11.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185233/dc1769d1-9d8b-88c5-7e23-c0b072e12781.png">

**やったぜ。**

ApplicationのQueryはPCが発熱し始めましたのでやめました。やっぱりメモリ8GBは人権がないんですかね... それでは卒論執筆に戻ります 笑。