Webシステムの負荷試験を行う際、最近はjmeterではなくlocustがオススメと聞いたので、試してみました。
- シナリオをpythonで書けるので複雑なケースに対応できる
- 多重度を上げる際、jmeterだと1ユーザ1スレッドになるが、locustなら少ないリソースでも負荷をかけやすい
結論から言うと、公式ドキュメント通りにやれば動きます。
が、割と最近に出た1.0で破壊的変更があったようで、少し前の外部記事を元にすると動きません。そしてハマる(実際ハマった)。
この記事はそういった人がエラーメッセージから逆引きするもののつもりで書いています。
なお、いくつか動かない実装として他記事を引用していますが、あくまでlocustの破壊的変更によるものであり、引用元記事が間違っている訳ではないことは注釈しておきます。
細かな仕様や説明は当記事よりそちらの方が詳しいので、ご参考まで。
前提環境
- 端末: MacBook Pro Mojave
- 環境: docker for mac 2.0.0.3
- locust: 1.0.2
$ docker -v
Docker version 18.09.2, build 6247962
$ docker-compose -v
docker-compose version 1.23.2, build 1110ad01
とある事情で、諸々最新ではない。
シングル構成でlocustを実行する
コンテナでlocustをインストール
公式のlocustio/locustを使ってもいいのだが、cmdではなくentrypointでlocust
が指定されている。
locustの起動でエラーが起きるとコンテナが即死するだけなので、ログを追いにくくデバッグが辛い。
なのでまずはpythonイメージを元にして試してみる。
Webインターフェースを使えるように8089ポートを繋いでおく。
$ docker run -it -p 8089:8089 python:3.6 /bin/bash
locustのインストールはpipを叩くだけ。
このとき、古いやり方のpip install locustio
だとLocust package has moved from 'locustio' to 'locust'. Please update your reference (or pin your version to 0.14.6 if you dont want to update to 1.0)
と怒られる。
$ pip install locust
あと、そのままだとエディタがなくて辛いので、vimなどは適宜入れる。
$ apt-get update -y
$ apt-get install -y vim
locustfileを作る
最初、こちらの記事を参考にしたのだが、これはこのままでは動かなかった。
from locust import HttpLocust, TaskSet, task, between, constant
class UserBehavior(TaskSet):
@task(1)
def profile(self):
self.client.get("/sample", verify=False)
class WebsiteUser(HttpLocust):
task_set = UserBehavior
wait_time = constant(0)
実行すると、まず以下のエラーが出る。
ImportError: The HttpLocust class has been renamed to HttpUser in version 1.0. For more info see: https://docs.locust.io/en/latest/changelog.html#changelog-1-0
エラーメッセージ通り、ver1.0 の仕様変更によるもの。
このエラー自体は、単にHttpLocust
をHttpUser
に変えれば良い。
しかしそれ以外にも、この状態で起動すると、あとで実際にリクエストを送るタイミングで以下のエラーが発生する。
10b1687a2aee/ERROR/locust.user.task: Cannot choose from an empty sequence
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/locust/user/task.py", line 280, in run
self.schedule_task(self.get_next_task())
File "/usr/local/lib/python3.6/site-packages/locust/user/task.py", line 408, in get_next_task
return random.choice(self.user.tasks)
File "/usr/local/lib/python3.6/random.py", line 260, in choice
raise IndexError('Cannot choose from an empty sequence') from None
IndexError: Cannot choose from an empty sequence
どうやらTaskSet
周りの実装方法が変わったらしい。
task_set = UserBehavior
はtasks = {UserBehavior:1}
と書く必要がある。
結果、完成したファイルは以下の通り。
パス指定などについては引用元記事を参照まで。
from locust import HttpUser, TaskSet, task, between, constant
class UserBehavior(TaskSet):
@task(1)
def profile(self):
self.client.get("/sample", verify=False)
class WebsiteUser(HttpUser):
tasks = {UserBehavior:1}
wait_time = constant(0)
locustを起動する
以下を実行する。
(実際にはファイル名がこのままなら-f
以降は省略できるが、常にこの名前に縛られるのも不便なので、今から明示的に指定)
$ locust -f ./locustfile.py
このあとでブラウザから http://localhost:8089/
につなぐと、WebUIが出てくる
- 最大ユーザ(接続)数
- 接続数の増加率(1秒間に何接続増やすか)
- アクセス先URL
図の例の場合、10ユーザになるまで1秒に2ユーザずつ増やし(1秒で2人、2秒で4人……)、10人に到達したら以降10ユーザでアクセスし続ける(10ユーザになったら止まるわけではない)。
なお、私の場合、アクセス先URLの指定をホスト名のみにしたら(e.g. http://hoge
)、以下のエラーが発生した。
ConnectionError(MaxRetryError("HTTPConnectionPool(host='hoge.huga.com', port=80): Max retries exceeded with url: /sample(Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x....>: Failed to establish a new connection: [Errno -5] No address associated with hostname',))",),)
curlではホスト名のみでも正常にアクセスできたが、locustではFQDNにしないと接続できなかった。
詳細は調べていない。
実行した後はWeb上に色々記事があるので、そちらを参照まで。
docker-compose で並列実行する。
せっかくlocustを使うので、master/slave構成で使いたいと言う欲が出る。
docker-credential-desktopのエラー回避
docker for macの2.0.0.3のバグ(?)で、docker-composeの初期化で以下のエラーが出た
$ docker-compose up -d
Creating network "locust_default" with the default driver
Creating volume "locust_tests" with default driver
Pulling locust-master (locustio/locust:)...
Traceback (most recent call last):
File "docker-compose", line 6, in <module>
File "compose/cli/main.py", line 71, in main
File "compose/cli/main.py", line 127, in perform_command
File "compose/cli/main.py", line 1080, in up
File "compose/cli/main.py", line 1076, in up
File "compose/project.py", line 475, in up
File "compose/service.py", line 352, in ensure_image_exists
File "compose/service.py", line 1217, in pull
File "compose/progress_stream.py", line 101, in get_digest_from_pull
File "compose/service.py", line 1182, in _do_pull
File "site-packages/docker/api/image.py", line 381, in pull
File "site-packages/docker/auth.py", line 48, in get_config_header
File "site-packages/docker/auth.py", line 96, in resolve_authconfig
File "site-packages/docker/auth.py", line 127, in _resolve_authconfig_credstore
File "site-packages/dockerpycreds/store.py", line 25, in __init__
dockerpycreds.errors.InitializationError: docker-credential-desktop not installed or not available in PATH
[60116] Failed to execute script docker-compose
確かに、/Applications/Docker.app/Contents/Resources/bin/
をみてもdocker-credential-desktop
なんて存在しない。
https://github.com/docker/for-mac/issues/3785 によると、2.1.0.0では解消されているので、更新すれば良い。
が、何かしらの事情で更新できない場合、~/.docker/config.json
のcredsStore
を手修正してdesktop
からosxkeychain
にすれば動くようになる。
{
"auths" : {
"https://index.docker.io/v1/" : {
}
},
"stackOrchestrator" : "swarm",
"experimental" : "disabled",
"credsStore" : "desktop <= ここをosxkeychainに変える",
"credSstore" : "osxkeychain"
}
チケットに書いてある通り、確かにどう見てもtypoに見える。。。
docker-composeファイルを作る
こちらの記事を参考にしたのだが、これもそのままでは動かなかった。
ちなみに、このYAMLはアンカーとエイリアスがあるが、パース後に実際に生成されるオブジェクトが知りたい場合は、YAMLをjsonにするオンラインパーサがあるので、活用するとわかりやすいかも。
version: "3.4"
x-common: &common
image: locustio/locust
environment: &common-env
TARGET_URL: http://example.com
LOCUSTFILE_PATH: /tests/basic.py
volumes:
- tests/:/tests
services:
locust-master:
<<: *common
ports:
- 8089:8089
environment:
<<: *common-env
LOCUST_MODE: master
locust-slave:
<<: *common
environment:
<<: *common-env
LOCUST_MODE: slave
LOCUST_MASTER_HOST: locust-master
docker-composeを実行しても、Exited(1)で即死してしまう。
なお、エラーログはdocker logs (コンテナID)
で見れる
$ docker-compose up -d
Pulling locust-master (locustio/locust:)...
latest: Pulling from locustio/locust
cbdbe7a5bc2a: Pull complete
26ebcd19a4e3: Pull complete
a29d43ca1bb4: Pull complete
979dbbcf63e0: Pull complete
30beed04940c: Pull complete
d4954e7e8c67: Pull complete
45867b33845c: Pull complete
2be134228162: Pull complete
Creating locust_locust-slave_1 ... done
Creating locust_locust-master_1 ... done
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2fd911a1ee59 locustio/locust "locust" 5 minutes ago Exited (1) 5 minutes ago locust_locust-master_1
9c35f427c9eb locustio/locust "locust" 5 minutes ago Exited (1) 5 minutes ago locust_locust-slave_1
公式を見ると、パラメータを環境変数から取らなくなった様子。
LOCUSTFILE_PATH: /tests/basic.py
と書いても効果はなく、以下のエラーが出る。
Could not find any locustfile! Ensure file ends in '.py' and see --help for available options.
command
でlocust
コマンドへの引数を渡せるので、それでコントロールする。
entrypointが定義されたコンテナイメージでのcmdの扱いについては、こちらがわかりやすい。
なお、volumesは tests/
ではなく./tests
と書く。
でないとtestsという名前付きVolumeと見なされ、以下のエラーが発生する。
ERROR: Named volume "tests:/tests:rw" is used in service "locust-master" but no declaration was found in the volumes section.
結果、以下だと動く。
version: "3.4"
x-common: &common
image: locustio/locust
volumes:
- tests/:/tests
services:
locust-master:
<<: *common
ports:
- 8089:8089
command: -f /tests/basic.py --master -H http://example.com
locust-slave:
<<: *common
command: -f /tests/basic.py --worker --master-host locust-master
なお、workerの数を増やしたい場合、yamlを変更する必要はなく、docker-compose
を実行する際に--scale
を指定すれば良い。
$ docker-compose up -d --scale locust-slave=3
Creating network "locust_default" with the default driver
Creating locust_locust-slave_1 ... done
Creating locust_locust-slave_2 ... done
Creating locust_locust-slave_3 ... done
Creating locust_locust-master_1 ... done
これで気軽に分散負荷試験をできるようになった!
続編として、locustをECSで動かす記事も書いたので、ご参考まで。
https://qiita.com/sekikatsu/items/e6fad26fded58f92fb25