Docker Compose | Prometheus | Grafana | Locust |
---|---|---|---|
負荷試験の基礎知識
この2つを教えてもらい読みました。良かったです! 特に本の方。
- 『Amazon Web Services負荷試験入門―クラウドの性能の引き出し方がわかる』 : AWS と書いてありますが、クラウド全般の負荷試験入門書として読めました。
- Webアプリケーション負荷試験実践入門 : ↑ の著者による、サッと読めるスライド
この記事でやることは?
実際に手元で 攻撃 & 監視をやってみるぞ! というはなし。
ソースコードは全て GitHub にあげてあります
「 https://github.com/sensuikan1973/stress_test_sample 」
やること
言語やミドルウェアは何でもいいですが、今回は、
Go + MySQL で超シンプルな API サーバを立て、それを Locust で攻撃し、Prometheus + Grafana で負荷を監視します 。
やらないこと
監視用に独自のクエリを書くことはやりません。 また、本題でない部分は 楽に雑に 実装します。
1. 環境構築
Install Docker for Mac から Docker をインストールし、docker-compose で構築しました。
主に以下2つの記事を参考にさせていただきました。
- Prometheus + Grafana を Mac 上で docker-compose で起動
- 今日から始めるDocker【docker-composeを使ってGo & Mysqlをよしなに起動しよう編】
ディレクトリ構成
全部内容書くと長くなるので、詳細は https://github.com/sensuikan1973/stress_test_sample を見てください。
というわけで yml とか Dockerfile とかの話は省略し、さっそく docker を立ち上げます。
$ docker-compose build && docker-compose up
http://localhost:9090/targets で Prometheus の Targets を確認するとこんな感じになってるはず。
Grafana に Data Sources を追加
http://localhost:3000 に admin:admin でログインしてやっていく
Prometheus
MySQL
Grafana に Dashboard を追加
下記の3つを追加し、それぞれ環境に合わせた設定をすれば、
グラフを3つ手にできます。(これは Mysql-Prometheus の例)
2. Locust で攻撃
攻撃対象の API サーバ
Go で書いたこれ。
ごく単純(かつテキトー)なものですが、DB の write/read 両方あるので、負荷のモニタリング例としていい感じかと思います。
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 にしたがって、
github.com/prometheus/client_golang/prometheus/promhttp
を使っています。
いざ攻撃
こんな感じのシンプルなシナリオを書いて、
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 で監視
攻撃しながらグラフを眺めると、負荷の高まりを感じることができて嬉しい!!!!!!
ボトルネックを特定していきましょう。
おしまい