当サイトは、アフィリエイト広告を利用しています
dockerを使ってPythonのフレームワークであるflaskで実装したREST APIに
Pythonで書かれた負荷ツールであるLocustを使って負荷をかける方法をまとめる。
LocustはwebアプリケーションやAPIの性能テストを行うことができる負荷ツール。
Locustはサーバー上で動作するwebアプリケーションに対してリクエストを送り
負荷をかけるツールであるため、アプリを通してではなく、システムに直接的に負荷を
かけたい場合は「sysbench」を用いた方がいい。
sysbenchに関しては下記記事で紹介しています。
Locustは他の負荷ツールに比べて下記のような特徴がある
Locustで負荷テストを実行する際のテストケースをPythonプログラムで
書くことができる。
そのため、テスト結果の処理にpythonライブラリを使うことも可能
pipで簡単にインストールすることができる
LocustにはWEBUIがあり、テストの進捗状況をリアルタイムで確認できる
下記のような感じでブラウザでみることができる
Locustを使用してwebアプリケーションに負荷をかけるデータを取得することで
下記のような性能指標を測ることができる
Locustは特定の時間内に発生したリクエストの総数を計測できる
各リクエストのレスポンス時間(サーバーからの応答までの時間)を計測できる
(最小、最大、平均、中央値)で見れる
特定の時間内に処理されたリクエストの数を計測できる
いわゆるリクエストレート(単位時間あたりのリクエスト処理数)
リクエストが失敗した割合を計測できる
リクエストがどれだけの頻度で失敗するかを示す。
下記の環境で行う
「Docker for Windows」のインストール方法については下記記事で 紹介しています
Locustでflask製REST APIに負荷をかけるための実行環境は
dockerコンテナを使って作成する
Locustコンテナとflaskコンテナを作成して
LocustコンテナからLocustを実行し、flaskコンテナ内の
gunicornで起動しているREST APIに対してリクエストを送って
負荷をかける。
全体的な構成は下記のようになる
.|-- api| |-- Dockerfile| |-- api.py| |-- flask_memo.md| |-- requirements.txt| `-- wsgi.py|-- docker-compose.yml`-- locust|-- Dockerfile|-- locustfile.py`-- requirements.txt
dockerコンテナを作成するためのファイル
詳細は後述
flaskのREST APIの実行ファイル
詳細は後述
Locustのテスト設定ファイル
詳細は後述
dockerを使って負荷テストを行うための
コンテナを作成していく
コンテナは
を作成する。
version: "3"services:flask:container_name: "flask"# Dockerfileをビルドbuild:context: ./api/dockerfile: Dockerfiletty: true# プロジェクトをバインドマウントvolumes:- ./api:/workspace/apiports:- "5000:5000"# サーバー実行# ipアドレス ポート番号 パス ファイル名 flaskインスタンス名command: gunicorn --reload --bind 0.0.0.0:5000 --chdir /workspace/api wsgi:apinetworks:example-network:ipv4_address: 172.16.238.10locust:container_name: "locust"# Dockerfileをビルドbuild:context: ./locust/dockerfile: Dockerfiletty: true# プロジェクトをバインドマウントvolumes:- ./locust:/workspace/locustports:- "8089:8089"networks:example-network:ipv4_address: 172.16.238.9networks:example-network:name: example-networkdriver: bridgeipam:driver: defaultconfig:- subnet: 172.16.238.0/24
dockerでコンテナに固定IPをふることに関しては
下記記事でまとめています。
またflaskアプリをgunicornで起動する詳細については下記記事でまとめてます。
# イメージFROM python:3.12# パッケージを最新化RUN python -m pip install --upgrade pip# workspaceディレクトリを作成WORKDIR /workspace# workspaceにrequirements.txtをコピーCOPY requirements.txt /workspace/# requirements.txtのパッケージをインストールRUN pip install -r requirements.txt
Flask==3.0.2gunicorn==21.2.0
# イメージFROM python:3.12# パッケージを最新化RUN python -m pip install --upgrade pip# workspaceディレクトリを作成WORKDIR /workspace# workspaceにrequirements.txtをコピーCOPY requirements.txt /workspace/# requirements.txtのパッケージをインストールRUN pip install -r requirements.txt
locust==2.16.1
docker composeでコンテナ作成を実行する
$ docker compose up -d$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES3fc081b1a740 locust_restapi-prometheus-locust "python3" About a minute ago Up About a minute 0.0.0.0:8089->8089/tcp locusted470c1e22ee locust_restapi-prometheus-flask "gunicorn --bind 0.0…" About a minute ago Up About a minute 0.0.0.0:5000->5000/tcp flask
locustコンテナとflaskコンテナが作成されている
この後、VScodeでコンテナにアタッチして操作する
作成済みのコンテナへのアタッチ方法については
下記で紹介してます。
コンテナをdocker comoposeで作った場合は
拡張機能がインストールされない。一括インストールする方法を
下記で紹介してます
flaskコンテナで起動したgunicornサーバー上で動く
flaskで実装したREST APIについて少し解説する。
flaskのREST APIのルーティングと処理を実装
受信したHTTPメソッドに応じて、usersディクショナリを操作するだけの
REST API。
from flask import Flask, request, jsonify# flaskインスタンス作成api = Flask(__name__)# ディクショナリusers = [{"user_id": "1", "name": "tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]@api.route('/', methods=['GET'])def get_users():# GETリクエストを処理age = request.args.get('age')if age:return jsonify(list(filter(lambda user: user['age'] == int(age), users)))return jsonify(users)@api.route('/', methods=['POST'])def post_user():# POSTリクエストを処理data = request.get_json()users.append(data)return jsonify(users), 201@api.route('/<user_id>', methods=['PUT'])def put_user(user_id):# PUTリクエストを処理data = request.get_json()res_users = list(map(lambda user: user.update(data) or user if user['user_id'] == user_id else user, users))return jsonify(res_users)@api.route('/<user_id>', methods=['DELETE'])def delete_user(user_id):# DELETEリクエストを処理res_users = list(filter(lambda user: user['user_id'] != user_id, users))return jsonify(res_users)
flaskのREST APIを起動するためのファイル
# api.pyのflaskインスタンスをimportfrom api import apiif __name__ == '__main__':# gunicornサーバー起動api.run()
今回はLocustがメインなのでREST APIに関する詳細は割愛する。 REST APIの詳細は下記記事でまとめるので、よければ参照ください
LocustはテストケースをPythonクラスで定義する
LocustのテストケースをPythonスクリプトで作成
from locust import HttpUser, TaskSet, task, between# タスクセットクラスclass UserBehavior(TaskSet):@task(5)def get_users(self):self.client.get("/")@task(4)def get_users_urlparam(self):self.client.get("/", params={"age": 30})@task(3)def post_user(self):self.client.post("/", json={"user_id": "5", "name": "test", "age": 30})@task(2)def put_user(self):self.client.put("/1", json={"name": "updated", "age": 22})@task(1)def delete_user(self):self.client.delete("/3")# ユーザークラスclass WebsiteUser(HttpUser):# hostを指定(コマンドラインから実行時に使用)host = "http://flask:5000"tasks = [UserBehavior]# 5秒から15秒の間隔を置いて実行wait_time = between(5, 15)
LocustのPythonスクリプトでは
の二つを定義する。
※タスクセットクラスは作らず、ユーザークラス内に直接タスクを書くこともできる。
テスト実行するとユーザーごとにタスクセットの中から5つのメソッドのいずれかを
5~15秒ごとに実行するようになる。
taskの横の数字は頻度。具体的にいうと
get_usersタスクが最も頻繁に(5回/10回)、delete_userタスクが最も少なく(1回/10回)実行される
テストの実行のイメージとしては
後述するがLocustでテスト実行時に「大ユーザー数」と「1秒ごとの増加ユーザー数」を設定するので
そのユーザーごとにユーザークラスのユーザーが作成され、タスクセットクラスのタスクを実行する仕組み。
Locustは実行は
webUIからの実行を試してみる
webUIから実行する場合、下記のメリットがある
デメリットとしては下記がある
locustコンテナにログインし
locustfile.pyがあるディクショナリまで移動し、下記コマンドで起動する
$ locust[2024-03-12 15:04:35,778] 24771d714839/INFO/locust.main: Starting web interface at http://0.0.0.0:8089 (accepting connections from all network interfaces)[2024-03-12 15:04:35,793] 24771d714839/INFO/locust.main: Starting Locust 2.16.1
起動後、「http://localhost:8089/」にアクセスするとwebUIが開く
最大の多重度。要は最大何ユーザーまで同時実行するかを設定
1秒ごとに何ユーザーずつ増やすかを設定
負荷をかけるベースのURI
この設定の場合、30ユーザーまで1秒ごとに1ユーザーずつ追加する設定となる
30ユーザーまで達したら、stopするまでテストが実行される。
コマンドラインで実行から実行する場合、下記のメリットがある
デメリットとしては下記がある
コマンドラインでの実行は下記ようにする
locust -f [スクリプトファイル名] --headless -u [ユーザー数] -r [ユーザーの発生率] --run-time [実行時間]
-f
はLocustのテストスクリプトのパスを指定--headless
はGUIなしで実行することを指定-u
はシミュレートするユーザーの数を指定-r
はユーザーの発生率(秒あたり)を指定--run-time
はテストの実行時間を指定locustコンテナにログインし
locustfile.pyがあるディクショナリまで移動し、下記コマンドで起動する
locust -f locustfile.py --headless -u 30 -r 1 --run-time 3m
ホスト名を「locustfile.py」に書いてない場合、下記のように指定することもできる
locust -f locustfile.py --host=http://flask:5000 --headless -u 30 -r 1 --run-time 3m
この設定の場合、30ユーザーまで1秒ごとに1ユーザーずつ追加する設定となる。
テストは3分後に終了する。
csv出力する場合は下記のようにする
locust -f locustfile.py --host=http://flask:5000 --headless -u 30 -r 1 --run-time 3m --csv=output
これでlocustfile.pyと同じディクショナリに下記のファイルが作成される
flaskのREST APIに対して、負荷ツールであるLocustを使って
複数リクエストを送付して負荷をかけてみた。
テストスクリプトをPythonで記述できるのは
かなりのメリットになると思う。
Locustはwebアプリケーションに対して負荷をかけるツールである
システム(もしくはコンテナ)に対して、直接的に負荷をかけたい場合は
Locustではなくsysbench等を使う方法がある。
詳しくは下記で紹介しています。