自己紹介
こんにちは,ZOZOテクノロジーズの内定者さっとです.
APIフレームワークの中ではFastAPIは結構好きで、個人で使うならほぼほぼこれを使っています。
FastAPIについては、以前に何個か記事を書います!
興味ある方は最後にリンクを貼っているので、そちらも見ていただければ思います!
※本記事はZOZOテクノロジーズ#4の22日目です.
概要(3行)
- Dockerを使ってFastAPIとMySQLの環境を構築
- 負荷実験ツールとしてLocustをローカルにインストール
- Locustを使ってコンテナへ向けて負荷実験
FastAPIとは?
Python3.6以上で動作する高速なAPIフレームワークです。
https://fastapi.tiangolo.com/
2019年初頭頃、少し話題になったと思います。
特徴としては、Node.jsやGoと同等のパフォーマンス性能でかつFlaskライクな書き方で簡単にAPIを生やすことができます。ハッカソンなど時間が限られた開発で有効なフレームワークだと思います。
また、Swaggerが自動で生成されるため、動作チェックやドキュメント作成の時間が大幅に短縮されます。
Locustとは?
Pythonで書かれた、負荷テストツールです。
https://locust.io/
負荷実験のシナリオはPythonで記述し、簡単に実装することができます。また、実行結果はWebで確認することができ、攻撃対象へアクセスするユーザ数の設定や実験の停止などテストツールを制御することができます。
事前準備
- Docker及びDocker Composeがインストール済み
- Python3.6以上がインストール済み
- 以下のファイル構成を用意(GitHubに完成版があります)
.
├── api
│ ├── db.py
│ ├── main.py
│ └── model.py
├── docker
│ ├── mysql
│ │ ├── Dockerfile
│ │ ├── conf.d
│ │ │ └── my.cnf
│ │ └── initdb.d
│ │ └── schema.sql
│ └── uvicorn
│ ├── Dockerfile
│ └── requirements.txt
├── docker-compose.yml
└── locustfile.py
APIサーバとMySQLの用意
docker-composeを使って必要なAPIサーバとDBを構築します。
version: '3'
services:
# MySQL
db:
container_name: "db"
# path配下のDockerfile読み込み
build: ./docker/mysql
# コンテナが落ちたら再起動する
restart: always
tty: true
environment:
MYSQL_DATABASE: sample_db
MYSQL_USER: user
MYSQL_PASSWORD: password # ユーザのパスワード
MYSQL_ROOT_PASSWORD: password # ルートパスワード
ports:
- "3306:3306"
volumes:
- ./docker/mysql/initdb.d:/docker-entrypoint-initdb.d # 定義通りテーブルを作成
- ./docker/mysql/conf.d:/etc/mysql/conf.d # MySQLの基本設定(文字化け対策)
networks:
- local-net
# FastAPI
api:
# db起動後に立ち上げる
links:
- db
container_name: "api"
build: ./docker/uvicorn
restart: always
tty: true
ports:
- 8000:8000
volumes:
- ./api:/usr/src/api
networks:
- local-net
# コンテナ間で通信を行うためのネットワークブリッジ
networks:
local-net:
driver: bridge
詳しい内容については、以下の記事で書いているので参考にしてください。
FastAPIをMySQLと接続してDockerで管理してみる
Locustのインストール
以下のコマンドで、ローカルにLocustをインストールします。
$ python -m pip install locustio
負荷を受けるAPIサーバのメインコード
- ユーザの情報を取得や登録する簡単なAPIを用意しました。
- ORMであるSQLAlchemyでMySQLを操作しています。
- 各エンドポイントの定義(def)の前にasyncとつけて非同期化しています。
# -*- coding: utf-8 -*-
from fastapi import FastAPI
from db import session # DBと接続するためのセッション
from model import UserTable # Userテーブルのモデル
# fastapiでエンドポイントを定義するために必要
app = FastAPI()
# ユーザ情報を返す GET
@app.get("/users/{user_id}")
async def read_user(user_id: int):
# DBからuser_idを元にユーザを検索
user = session.query(UserTable).\
filter(UserTable.id == user_id).first()
# ユーザが見つからなかった場合
if (user is None):
return {"code": 404, "message": "User Not Found"}
return user
# ユーザ情報を登録 POST
@app.post("/users")
# クエリでnameとageを受け取る
async def create_user(name: str, age: int):
user = UserTable()
user.name = name
user.age = age
session.add(user)
session.commit()
負荷実験シナリオ
- UserTaskSetクラスに実験シナリオを定義していきます。
- on_startメソッドに初期設定を定義します。
- taskに書かれた数字は重みで、
@task(2)
は@task(1)
の2倍実行されることを表しています。
from locust import HttpLocust, TaskSet, task, between
import random
import string
class UserTaskSet(TaskSet):
def on_start(self):
self.client.headers = {'Content-Type': 'application/json;'}
@task(1)
def fetch_user(self):
self.client.get("/users/{}".format(random.randint(0, 100)))
@task(2)
def create_user(self):
# ランダムに適当に名前を生成
name = ''.join(random.choices(string.ascii_letters, k=10))
age = random.randint(0, 80)
self.client.post("/users?name={}&age={}".format(name, age))
class UserLocust(HttpLocust):
task_set = UserTaskSet
wait_time = between(0.100, 1.500)
実行
- コンテナを立ち上げます。
$ docker-compose up -d --build
- Locustを起動します。
APIサーバはlocalhost:8000で待ち受けているので、これに向けて負荷をかけるように指定してあげます。
$ locust -f locustfile.py --host=http://localhost:8000
LocustをWebで監視
- Locustを起動すると以下のログが通知されます。
[2019-12-02 02:13:58,632] hoge/INFO/locust.main: Starting web monitor at *:8089
[2019-12-02 02:13:58,633] hoge/INFO/locust.main: Starting Locust 0.13.2
- ログでは、*:8089でリッスンしているみたいなので、http://localhost:8089 へアクセスします。
初回起動時では、Number of users to simulateでどのぐらいのユーザ数でアクセスするかと
Hatch rateで毎秒何人ユーザを増やすかを設定できます。
- テストが実行されると、Statisticsページではログが流れます。
ログでは、リクエスト数やレスポンスタイム、失敗した数などが見れます。
- Chartsページでは、一行ごとのリクエスト数やレスポンスタイム、現在のユーザ数がチャートで表示されます。
- 最後に、上部のSTOPで簡単に負荷を停止することができます。
おわりに
今回は、Locustを使った負荷実験環境の構築を行いました。
今後は、FastAPIって本当に早いの?を検証するために、この環境を使い様々なAPIフレームワークと比較したいと思います。
今回使ったコードは、GitHubにあげているので、興味ある方はぜひ触ってみてくださいね!
https://github.com/sattosan/stress_fastapi