これまでJmeterを使っていたのですが、
楽にスケーリングができそうということで最近はLocustを使うようになったので、
備忘録を兼ねて使い方をまとめます!
自分の現場でも使っているようなコード・コマンドをコピペでそのまま使えるようまとめてみていますので、
読者のみなさまの「Locustを使ってみる」ハードルを下げられたら幸いです!
環境構築
ここではCentOSベースのサーバーに構築してみます。
# pythonのバージョンを確認(2023/01時点では3.7以上が必要)
$ python --version
Python 3.11.0
# インストール
$ pip3 install locust
公式のインストール手順はこちらです
高負荷なテストを行いたい場合、file open数も気にしておくとよいです。
仮に同時に300リクエストを実効するテストを行いたい場合file openも300以上にしておく必要があります。
# 確認
ulimit -n
> 65535
# 変更したい場合
vi /etc/security/limits.conf
とりあえず動くところまで
準備
Locustの動作に関わる定義はlocustfile.py
に記載します。
ひとまずgetとpostのAPIにリクエストするサンプルです。
from locust import task, constant_throughput, HttpUser
# クラス名は任意(HttpUser)を継承する
class SampleUser(HttpUser):
# API(シナリオ)ごとに@taskを付与したメソッドを定義する
# @taskの引数には負荷をかける比重を指定(この実装だとget:post = 2:1の割合でAPIコールする)
@task(2)
def get_request(self):
value = "value"
self.client.get(f"/sample?key={value}")
@task(1)
def post_request(self):
self.client.post("/sample", json={"key": "value"})
# 1スレッド当たりの1秒間の最大実行数を指定
# 1に指定しておけばスレッド数以上の性能がでないので、負荷をコントロールしやすい
wait_time = constant_throughput(1)
# (任意)UI上のデフォルトのホストを指定する
host = 'http://localhost:8080'
起動
$ locust
# これと一緒
# locust -f ./locustfile.py
8089ポートでUIが起動するので http://localhost:8089 にアクセスします。
- Number of users
- 想定ユーザー数
- アクセス元のスレッド数と考えればOK
- 目標rps = ユーザー数 / (レスポンスタイム + time wait)
- Spawn rate
- 例えば10と設定すると1秒間に10ずつユーザー数が増えていく
- Host
- プロトコル+ホスト+ポートを指定
http://localhost:8080
とか
Start swarmingで起動!
Chartsタブを見ると試験状況が確認できます!
簡単に見方や使い方を
- STATUS
- 〇〇users部分で現在のユーザ数(スレッド数)を把握できます
- 「Edit」で「Number of users」と「Spawn rate」を再設定できます
- →段階的に負荷を上げたい場合にはこのEditから
- RPS
- 現在のRPSが表示されています
- 複数のAPIの合計のrpsが表示されているので、個別の情報が見たい場合は「Statistics」タブから
- FAILURES
- HTTPステータスが200以外の割合が表示されています
- 基本200以外があったら負荷に耐えきれてない系だと思います
master / slave構成
Locustはmaster/slaveを使用することでスケーリング可能です!
ここではDockerを使用して構成してみたいと思います。
※Kubernetesとかにも応用が利くはず…!
docker準備
インストール手順は省略しますが、
以下のコマンドが叩けていればOKです!
$ docker --version
Docker version 20.10.23, build 7155243
$ docker-compose --version
docker-compose version 1.28.2, build 67630359
起動sh作成
環境変数によって起動モードを変更できるようシェルを作成します。
#!/bin/bash
echo "mode: $MODE"
if [ "$MODE" == 'master' ]; then
locust --master
elif [ "$MODE" == 'slave' ]; then
locust --slave --master-host=locust-master
fi
Dockerfile
FROM python:3.11.0
RUN pip3 install locust
WORKDIR /locust
COPY ./locustfile.py .
COPY ./run.sh .
RUN chmod +x ./run.sh
ENTRYPOINT ["./run.sh"]
docker-compose.yml
slaveを3台起動する例です!
※コマンド的にはslave → workerとなっていたのでworkerが正しい言葉なのかも…
version: '2'
x-locust-service: &locust-service
build: .
services:
locust-master:
<<: *locust-service
ports:
- "8089:8089"
environment:
MODE: master
locust-slave-1:
<<: *locust-service
environment:
MODE: slave
locust-slave-2:
<<: *locust-service
environment:
MODE: slave
locust-slave-3:
<<: *locust-service
environment:
MODE: slave
起動
docker-compose -f docker-compose.yml up -d
# 開発時は毎回build
# docker-compose -f docker-compose.yml up -d --build
カスタマイズ
ここからは「やりたいこと」をベースにLocustのカスタマイズ方法をまとめます!
エラー情報をログに出力
UIの「Failures」でもエラーが発生したことはわかるのですが、
原因まで追おうとすると情報が足りないことが多いです。
そんなときは@events.request.add_listener
を使用してログを出力するとよいです!
※コードは変更点のみ
from locust import events
import uuid
import logging
class SampleUser(HttpUser):
@task(1)
def error(self):
# listenerでリクエスト・レスポンスの紐づけをするためにcontextを設定を設定しておく
self.client.get("/ng", context={"requestId": str(uuid.uuid4())})
# 1リクエストが完了した段階で呼ばれるListenerの定義
@events.request.add_listener
def logging_response(request_type, name, response_time, response_length, response,
context, exception, **kwargs):
# エラー発生時のみログ出力
if not response.ok:
requestId = context["requestId"]
logging.info(f"[request event] path: {name} status: {response.status_code} body: {response.text} requestId: {requestId}")
[2023-01-26 10:53:49,471] XXXX-XXXXXXXX/INFO/root: [request event] path: /ng status: 500 body: ng requestId: f92ccc00-a970-41e5-9a68-b434474f705f
[2023-01-26 10:53:50,471] XXXX-XXXXXXXX/INFO/root: [request event] path: /ng status: 500 body: ng requestId: ac01dd82-ab75-4af6-874c-fb793c72e2fb