[負荷試験] API サーバを Locust で攻撃し、Prometheus + Grafana で監視

|<b><a href='https://docs.docker.com/compose/' target="_blank">Docker Compose </b>| <b><a href='https://prometheus.io/' target="_blank"> Prometheus </b> | <b><a href='https://grafana.com/' target="_blank"> Grafana </b> | <b><a href='https://docs.docker.com/compose/' target="_blank"> Locust </b>|
|:-----------------:|:------------------:|:------------------:|:------------------:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/159680/661193f7-5c44-c022-763e-c509bdf25fb9.png" width="200">|<img src="https://qiita-image-store.s3.amazonaws.com/0/159680/95f03013-b19d-53c6-0f87-cf7a520d3290.png" width="200">|<img src="https://qiita-image-store.s3.amazonaws.com/0/159680/36afafa1-3718-015f-d3f5-3ab6e9c3cf06.png" width="200">|<img src="https://qiita-image-store.s3.amazonaws.com/0/159680/e3f50a73-cf47-ff14-b3a7-77b3a2c99053.png" width="200">|

## 負荷試験の基礎知識
この2つを教えてもらい読みました。良かったです!　特に本の方。

* 『[Amazon Web Services負荷試験入門―クラウドの性能の引き出し方がわかる](https://www.amazon.co.jp/dp/4774192627)』 : AWS と書いてありますが、クラウド全般の負荷試験入門書として読めました。
* [Webアプリケーション負荷試験実践入門](https://www.slideshare.net/taruhachi/20150224-45099119) : ↑ の著者による、サッと読めるスライド

## この記事でやることは？
**実際に手元で 攻撃 & 監視をやってみるぞ！** というはなし。
ソースコードは全て GitHub にあげてあります
「 **https://github.com/sensuikan1973/stress_test_sample** 」

### やること
言語やミドルウェアは何でもいいですが、今回は、
<font color="orange"> **Go** + **MySQL** で超シンプルな API サーバを立て、それを **Locust** で攻撃し、**Prometheus** + **Grafana** で負荷を監視します </font>。

### やらないこと
監視用に独自のクエリを書くことはやりません。 また、本題でない部分は 楽に雑に 実装します。

## １. 環境構築
[Install Docker for Mac](https://docs.docker.com/docker-for-mac/install/) から Docker をインストールし、docker-compose で構築しました。

主に以下2つの記事を参考にさせていただきました。

* [Prometheus + Grafana を Mac 上で docker-compose で起動](https://qiita.com/moaikids/items/7de6f071574a64290769)
* [今日から始めるDocker【docker-composeを使ってGo & Mysqlをよしなに起動しよう編】](https://qiita.com/yu-croco/items/0c0d5083298f7db9e1bb)

### ディレクトリ構成
![image.png](https://qiita-image-store.s3.amazonaws.com/0/159680/7d1501ff-a311-9961-1e7c-5182f72775f3.png)

全部内容書くと長くなるので、詳細は **https://github.com/sensuikan1973/stress_test_sample** を見てください。

というわけで yml とか Dockerfile とかの話は省略し、さっそく docker を立ち上げます。

```
$ docker-compose build && docker-compose up
```

http://localhost:9090/targets で Prometheus の Targets を確認するとこんな感じになってるはず。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/159680/b15f753b-4442-9784-cf2e-df2f4bf74ab2.png)


### Grafana に Data Sources を追加
http://localhost:3000 に admin:admin でログインしてやっていく

#### Prometheus
![image.png](https://qiita-image-store.s3.amazonaws.com/0/159680/1f707262-131c-18cf-036a-a83c8166e41e.png)

#### MySQL
![image.png](https://qiita-image-store.s3.amazonaws.com/0/159680/7cd3bf46-e230-cf2b-3235-c4dd600d615a.png)

### Grafana に Dashboard を追加
左の + アイコンから、import を選び、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/159680/7ed39dec-78db-af37-b207-c88b09d88c01.png)

下記の3つを追加し、それぞれ環境に合わせた設定をすれば、

* [Mysql-Prometheus](https://grafana.com/dashboards/6239)
* [Go Processes](https://grafana.com/dashboards/6671)
* [Node Exporter Server Metrics](https://grafana.com/dashboards/405)

グラフを3つ手にできます。(これは Mysql-Prometheus の例)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/159680/ca802c63-c46b-ece1-e849-ff28fa6bc079.png)

## 2. Locust で攻撃
### 攻撃対象の API サーバ
Go で書いたこれ。
ごく単純(かつテキトー)なものですが、DB の write/read 両方あるので、負荷のモニタリング例としていい感じかと思います。

```hello.go
package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/prometheus/client_golang/prometheus/promhttp"

	_ "github.com/go-sql-driver/mysql"
)

// Greeting : record of greetings table
type Greeting struct {
	ID   int    `json:"id"`
	Text string `json:"text"`
}

func main() {
	http.Handle("/metrics", promhttp.Handler())

	http.HandleFunc("/greetings", func(w http.ResponseWriter, req *http.Request) {
		db, err := sql.Open("mysql", "root:password@tcp(mysql:3306)/sample_for_qiita")
		if err != nil {
			panic(err.Error())
		}
		defer db.Close()

		if req.Method == "GET" {
			rows, err := db.Query("SELECT * FROM greetings ORDER BY RAND() LIMIT 1")
			if err != nil {
				panic(err.Error())
			}

			for rows.Next() {
				greeting := Greeting{}
				err := rows.Scan(&greeting.ID, &greeting.Text)
				if err != nil {
					panic(err.Error())
				}
				fmt.Fprintf(w, greeting.Text)
			}
		}

		if req.Method == "POST" {
			body, err := ioutil.ReadAll(req.Body)
			if err != nil {
				panic(err.Error())
			}
			var greeting Greeting
			error := json.Unmarshal(body, &greeting)
			if error != nil {
				log.Fatal(error)
			}
			_, err = db.Exec("insert into greetings (text) values (?)", greeting.Text)
			if err != nil {
				panic(err.Error())
			}
		}
	})

	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal("ListenAndServe failed.", err)
	}
}
```

* Prometheus の公式ドキュメント [INSTRUMENTING A GO APPLICATION FOR PROMETHEUS](https://prometheus.io/docs/guides/go-application/) にしたがって、`github.com/prometheus/client_golang/prometheus/promhttp` を使っています。

### いざ攻撃
こんな感じのシンプルなシナリオを書いて、

```locustfile.py
from locust import HttpLocust, TaskSet, task
import random
import string

class GreetingTaskSet(TaskSet):
    def on_start(self):
        self.client.headers = {'Content-Type': 'application/json; charset=utf-8'}

    @task
    def fetch_greeting(self):
        self.client.get("/greetings")

    @task
    def create_greeting(self):
        # 雑にランダム文字列で済ませちゃう。 (注: random.choices は 3.6 以降でしか使えません)
        self.client.post("/greetings", json={"text": ''.join(random.choices(string.ascii_letters, k=10))})

class GreetingLocust(HttpLocust):
    task_set = GreetingTaskSet

    min_wait = 100
    max_wait = 1000
```

負荷をかけていく

```bash
# ローカルを発射台として攻撃する
$ locust -f locustfile.py --no-web -H http://localhost:8080
```

## 3. Prometheus + Grafana で監視
攻撃しながらグラフを眺めると、負荷の高まりを感じることができて嬉しい！！！！！！
ボトルネックを特定していきましょう。

おしまい

## 参考
* [Prometheus + Grafana を Mac 上で docker-compose で起動](https://qiita.com/moaikids/items/7de6f071574a64290769)
* [今日から始めるDocker【docker-composeを使ってGo & Mysqlをよしなに起動しよう編】](https://qiita.com/yu-croco/items/0c0d5083298f7db9e1bb)
* [PrometheusとGrafanaを組み合わせて監視用ダッシュボードを作る](https://knowledge.sakura.ad.jp/12058/)
* [ソーシャルゲームが高負荷に見舞われる原因と対策](https://speakerdeck.com/takihito/sosiyarugemugagao-fu-he-nixian-tuteirutoki-he-gaqi-kotuteirufalseka)
* [Locustの気持ち](https://qiita.com/kazunee/items/607b03ca61bb5d5fdbe6)
* [DockerでPrometheus, Grafana, Alertmanagerを動かす](https://samskeyti.hatenablog.com/entry/2018/12/08/171729)
* [prometheus のデータを grafana でグラフ表示してみた](https://qiita.com/tukiyo3/items/da379f6e5844c99b65df)