https://qiita.com/sekikatsu/items/992e82671aa505c5a652 の続きです。
ローカル環境でlocustを実行できるようになったので、クラウドの豊富なマシンパワーを使って負荷掛けができるようにします。
Terraformを使って本格的にやる場合は https://qiita.com/neilli-sable/items/b17dfba5eabfcafccaf8 など既存記事があるので、ご参考ください。
この記事は、ECSを初めて使うくらいの人が、とりあえずAWS Webコンソールを使ってlocustを動かしてみる、という内容です。
序章
「ローカルで動いてるんだからAWSに移行するのも簡単やろ」と思ってたら、全然簡単じゃなかった。ECSのハードル高い。
とりあえず、スタート時点での知識レベルは以下くらい
- ECS: AWSでコンテナ使うためのサービス。EKSとかいう玄人向けサービスは無視。
- 「ECSって?」という状態なら https://xn--o9j8h1c9hb5756dt0ua226amc1a.com/?p=2025 が参考になった
- Fargate: ECSを使うためのインフラ。FargateかEC2を選べるが、IaaSよりPaaSの方が楽に違いないとこちらを選択。
- locust: https://qiita.com/sekikatsu/items/992e82671aa505c5a652 これ書いた。
流れ
以下のフローで実行できるようにしようとした
- master 1台 + slave N台を起動する
- コンテナが起動すると、指定したURLからlocustfile.pyをgit clone
- cloneしたlocustfile.pyを使ってlocustをmaster/slaveで起動
- WebUIから負荷掛け
- WebUIから結果をダウンロード。locustの場合、結果の回収方法を考えなくていいので楽(JmeterだとS3に転送したりしないといけない)
方針
- locustio/locustイメージはgitが入っていないので、自作する
- Dockerイメージ内にmaster/slaveそれぞれ用のスクリプトを用意して、それを叩くことでmaster/slaveの切り替えと複数コマンド実行を実現
- masterとslaveでDockerイメージを分けるのは面倒だったので、横着して一つにまとめる
- ECSのCMDで複数コマンドを実行する方法がわからなかったので、スクリプトを置いてコマンドを一つにする
- 今回はここに一番時間を取られた
- CMDに
/bin/bash,-c,'git clone ** && locust **'
を設定すると、No such file or directory
と心ないことを言われる - CMDに
/bin/bash,-c,git,clone,(略)
を設定しても、動かず、かつログも出力されず、心が折れた
- 今思えば、イメージ内に入れるのではなく、これもGitHubに入れてgit cloneで取得すれば良かった
- 本当はMasterへの接続先を自動で取ってきてSlaveを起動したかったんだけど、別のところでハマりすぎて心が折れたので、目で確認+手で入力、という最強の手段を用いる
- 変数は全て環境変数経由で渡す
Dockerイメージを用意する
使う環境変数
- LOCUST_FILE_GIT_URL: locustfile.pyを取得するための git clone URL
- LOCUST_TARGET_URL: 負荷掛け対象のURL。あとでWebから上書きできるので、適当でもいい
- LOCUST_MASTER: slave用。Masterのprivate IPアドレスを入れる。
- カレントディレクトリ
- Dockerfile
- master
- slave
master用のエントリポイント。
Slaveの起動時に必要なMasterのIPアドレスを一応出力しておく(実際にはAWSコンソール上で見る方が早いから使わないけど)。
#!/bin/bash
echo "=== IP Address ===================="
ip address show
echo "=== Git clone ====================="
git clone ${LOCUST_FILE_GIT_URL} /locust_src
echo "=== Start LOCUST as master mode ==="
locust -f /locust_src/locustfile.py --master -H ${LOCUST_TARGET_URL}
slave用のエントリポイント
#!/bin/bash
echo "=== Confirm to connect master ===="
ping -c 3 ${LOCUST_MASTER}
echo "=== Git clone ===================="
git clone ${LOCUST_FILE_GIT_URL} /locust_src
echo "=== Start LOCUST as slave mode ==="
locust -f /locust_src/locustfile.py --worker --master-host ${LOCUST_MASTER}
Dockerfile
FROM python:3.6
RUN pip install locust
RUN mkdir /locust
ADD ./master /locust/master
ADD ./slave /locust/slave
RUN chmod +x /locust/master
RUN chmod +x /locust/slave
CMD ["/locust/master"]
この状態でdocker build + push。
docker build -t sekikatsu36/locust:0.0.1 .
docker push sekikatsu36/locust:0.0.1
できたのがこれ
https://hub.docker.com/repository/docker/sekikatsu36/locust/general
locustfile.pyの用意
githubリポジトリに作ってpushするだけ。
今回はPOSTを使った負荷掛けをしたかったので、Bodyに入れるパラメータも環境変数から取るようにした。
使う環境変数
- LOCUST_TARGET_PATH: 負荷掛けの対象パス。上の
LOCUST_TARGET_URL
と組み合わせることでフルパスになる。 - LOCUST_PARAM: パラメータを
key1=value;key2=value2
の形式で入れる
from locust import HttpUser, TaskSet, task, between, constant
import os
class UserBehavior(TaskSet):
@task(1)
def login(self):
l = os.environ['LOCUST_PARAM'].split(';')
tmp = map(lambda s:s.split('='), l)
param = dict(tmp)
self.client.post(os.environ['LOCUST_TARGET_PATH'], param)
class WebsiteUser(HttpUser):
tasks = {UserBehavior:1}
wait_time = constant(1)
できたのがこれ
https://github.com/sekikatsu36/locust-py
AWSでECSクラスタを作る
やっとAWSの出番。
- クラスターテンプレートは「ネットワーキングのみ AWS Fargateを使用」を選択。
- クラスタ名は適当につける
- VPCはお好みで。
- 負荷掛けにしか使わないので、Container Insightsは要らない
タスク定義を作る
どんなタスク(コンテナ)を実行するのか、定義する。
あくまでテンプレート的なもので、タスクの実行時に定義は上書きできるので、横着してタスク定義もmaster/slaveで使い回す。
-
「起動タイプの互換性の選択」は
FARGATE
で -
タスクサイズは、実行する処理によるけど、試しに動かすなら
メモリ=1GB、vCPU=0.5
で十分 -
「コンテナの追加」ボタンから、作ったDockerイメージを登録する
- コンテナ名は適当に
- イメージはさっき作ったやつ。今回は
sekikatsu36/locust:0.0.1
- ポートマッピングは 8089(WebUI用)と5557/5558(Slave=>Master通信)を開ける
- 環境はコマンドだけ
/locust/master
を入れておく。なくてもデフォルトこれなので動くが、あとでSlaveを変更するときに入ってると個人的に変更しやすかった - 環境変数は必要な値を適宜設定。LOCUST_MASTERはあとで毎回上書きする必要があるので、適当に入れる。
- ログ設定は特にいじらなければ、CloudWatch Logsの
/ecs/{コンテナ名}
に出力されるようになる。今回はそのまま。
-
記載のないものはデフォルトで
セキュリティグループを作る
master/slaveに適用されるセキュリティグループを作る。
この操作自体はタスクの実行時にもできそうなUIをしているが、そちらはUIがダメダメすぎて使えないので、事前に作っておく。(このUIで無駄にてこずってしまった)
- まず、ルールを何も買えない状態でセキュリティグループを作る。そうするとインバウンドルールが空になっているはず。
- そのあとで、以下のインバウンドルールを追加する
- WebUIにつなぐために、手元から8089ポートへの通信を開ける
- Slave=>Masterの通信を許可するために、「ソース」にこのセキュリティグループ自体、「ポート範囲」に
5557-5558
を指定。
masterを実行
- 作ったクラスターの「タスク」タブから、「新しいタスクの実行」を選ぶ
- 「キャパシティープロバイダーの戦略」 でエラーが出る。が、今は使わないので「起動タイプへ切り替える」を選択して、FARGATEを選ぶ
- VPCとサブネットを適当に選択
- セキュリティグループはさっき作ったやつ
- 記載のないものはデフォルトで
実行できたら、タスクのプライベートIPを確認する。
http://(プライベートIP):8089 にアクセスすると、locustの画面が出てくるはず
slaveを実行
masterと同じ手順でタスクを実行。違いは以下。
- コンテナを以下のように上書き
- コマンドを
/locust/slave
- 環境変数の
LOCUST_MASTER
を、MasterのプライベートIPアドレスに変更
- コマンドを
- セキュリティグループは同じのも使いまわせるが、8089も5557/5558も開ける必要はないので、気になるなら閉じたものを作成&設定
- タスクの数を用途に合わせて適宜変更
- サブネットは複数指定できるので、Slaveのサブネットを分散させたい場合、配置予定のサブネットを全て登録
slaveが立ち上がったあと、locustのWebUIでWorkerの数が増えていれば成功。
あとはローカルと同じように負荷掛けを実行するだけ
おまけ
masterとslaveの実行が若干面倒くさいので、CLIで叩きたいという欲が出る。
masterのタスク実行をCLIで行う
masterはタスク定義をそのまま実行するだけなので、簡単。
aws ecs run-task \
--cluster locust-test \
--count 1 \
--launch-type FARGATE \
--task-definition locust-base \
--network-configuration "awsvpcConfiguration={subnets=['サブネット1','サブネット2',(略)],securityGroups=['セキュリティグループID'],assignPublicIp=ENABLED}"
- サブネットは
subnet-***
というサブネットIDをひたすら列挙するだけ - セキュリティグループIDも
sg-***
を設定する
蛇足だけど、AWS WebコンソールでこのID群を確認するのが面倒臭いのは私だけだろうか。UIの改善を求めたい。
slaveのタスク実行をCLIで行う。
slaveはコンテナの設定を上書きする都合で、ちょっと面倒くさい。
コンテナの設定値は差分をJsonで表現して与える必要がある。
$ #差分のJsonファイルの雛形を作る
$ echo "{\"containerOverrides\":[{\"name\":\"locust\",\"command\":[\"/locust/slave\"],\"environment\":[{\"name\":\"LOCUST_MASTER\",\"value\":\"MASTER_IP\"}]}]}" > override-for-slave-base.json
$ cat override-for-slave-base.json | jq
{
"containerOverrides": [
{
"name": "locust",
"command": [
"/locust/slave"
],
"environment": [
{
"name": "LOCUST_MASTER",
"value": "MASTER_IP"
}
]
}
]
}
$ #masterのIPアドレスを置換
$ sed "s/MASTER_IP/確認したマスターのIPアドレス/g" override-for-slave-base.json > override-for-slave.json
$ #slaveを立ち上げる
$ aws ecs run-task \
--cluster locust-test \
--count 10 \ #slaveの必要個数を入力
--launch-type FARGATE \
--task-definition locust-base \
--network-configuration "awsvpcConfiguration={subnets=['サブネット1','サブネット2',(略)],securityGroups=['セキュリティグループID'],assignPublicIp=DISABLED}" \
--overrides file://./override-for-slave.json
terraformで一発、とまではいかないけれど、まぁまぁ楽に起動できるようにはなった。