13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-03
Docker Compose Prometheus Grafana Locust

負荷試験の基礎知識

この2つを教えてもらい読みました。良かったです! 特に本の方。

この記事でやることは?

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

やること

言語やミドルウェアは何でもいいですが、今回は、
Go + MySQL で超シンプルな API サーバを立て、それを Locust で攻撃し、Prometheus + Grafana で負荷を監視します

やらないこと

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

1. 環境構築

Install Docker for Mac から Docker をインストールし、docker-compose で構築しました。

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

ディレクトリ構成

image.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

Grafana に Data Sources を追加

http://localhost:3000 に admin:admin でログインしてやっていく

Prometheus

image.png

MySQL

image.png

Grafana に Dashboard を追加

左の + アイコンから、import を選び、
image.png

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

グラフを3つ手にできます。(これは Mysql-Prometheus の例)
image.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)
	}
}

いざ攻撃

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

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

負荷をかけていく

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

3. Prometheus + Grafana で監視

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

おしまい

参考

13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?