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

