docker compose ECS integrationでlocustクラスタをさくっと起動する

OSSのパフォーマンステストツールlocustのクラスタをdocker compose ECS integrationでECS on Fargate上に起動してみました。
2021.06.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

locustはOSSのパフォーマンステストツールです。ワーカープロセスをスケールアウトさせることで分散実行が行えます。locustをクラウド上で実行させた例や手順は既にいくつか紹介されています。

今回はあまり頑張らないで使い慣れたdocker composeのECS integrationを使ってECS上でクラスタを起動してみました。

ローカルでクラスタを起動

開発時にはローカルでクラスタを起動します。—scaleオプションでワーカープロセスの数を指定できます。テストスクリプトはローカルのファイルシステムをマウントすることでコンテナ側に共有しています。

version: '3'

services:
  master:
    image: locustio/locust
    ports:
     - "8089:8089"
    volumes:
      - ./:/mnt/locust
    command: -f /mnt/locust/locustfile.py --master
  
  worker:
    image: locustio/locust
    volumes:
      - ./:/mnt/locust
    command: -f /mnt/locust/locustfile.py --worker --master-host master
# デフォルトコンテキストに切り替え(optional)
> docker context use default
# ワーカー数2で起動
> docker-compose -f docker-comose-local.yml up --scale worker=2

ECS上でクラスタを起動

ECS上で起動する時もそれほど多くの手順は必要ないです。ローカル起動との違いは以下の通りです。

  • テストスクリプトを含むコンテナイメージを使用する
    • スクリプトをEFSに置いてマウントしたり、S3に置いて動的に取得する方法も検討しましたが、これが一番手間が少ないと思っています
  • aws用のdocker contextを使用する
  • docker-compose.ymlにALB用の設定を追記する

上記を含む詳細な手順を以下に示します。

イメージのビルドとPUSH

今回使用するDockerfileは下記の通りです。テストスクリプトおよびテストに必要なファイルをコンテナイメージに含めます。

FROM locustio/locust
WORKDIR /mnt/locust
COPY *.py /mnt/locust/

ECRリポジトリの作成、イメージのビルド、プッシュを行います。今回の例ではリポジトリURIを環境変数に設定してdocker-compose.ymlから参照しています。

#!/bin/bash
set -ue -o pipefail

# リポジトリ名
repository="changeme"

# リポジトリの存否を確認してなければ作る
aws ecr describe-repositories --repository-names $repository || aws ecr create-repository --repository-name $repository

# リポジトリURIを取得
repositoryUri=`aws ecr describe-repositories --repository-names $repository | jq -r '.repositories[0].repositoryUri'`

# イメージビルド
docker build . -t $repository
docker tag $repository:latest $repositoryUri:latest

# push
aws ecr get-login-password | docker login --username AWS --password-stdin $repositoryUri
docker push ${repositoryUri}:latest

echo ""
echo ""
echo "export LOCUST_IMAGE=${repositoryUri}"

ECS用docker contextの作成

create-aws-context を参照

docker-compose.yml

docker-compose.ymlは以下のようになります。LOCUST_IMAGE でコンテナイメージのリポジトリを指定します。またx-aws-protocolプロパティでALBで使用するプロトコルを指定しています。このオプションを含むECS向けのオプションはECS integration Compose features を参照。

version: '3'

services:
  master:
    image: ${LOCUST_IMAGE}
    ports:
     - target: 8089
       x-aws-protocol: http
    command: -f /mnt/locust/locustfile.py --master
  
  worker:
    image: ${LOCUST_IMAGE}
    command: -f /mnt/locust/locustfile.py --worker --master-host master
    ulimits: 
      nofile: #同時接続数が多くなる場合に備えて上限を設定しておく
        soft: 65535
        hard: 65535

クラスタ起動

ECS上でのクラスタ起動もdocker composeで行えます。upサブコマンドを実行するとCloudFormationによって一連のリソースが生成されます。

# コンテキスト切り替え
docker context use your_ecs_context
# クラスタ起動
LOCUST_IMAGE=xxxx docker compose up --scale worker=3

スタック作成完了後にhttp://ALBのDNS名:8089にアクセスするとlocustのUIが表示されます。

デフォルトではアクセス元が無制限になっているので必要に応じて制限をしたり、HTTPSリスナーを設定してください。

以下のワンライナーでDNS名を表示できます。

aws elbv2  describe-load-balancers \
--load-balancer-arns (aws cloudformation describe-stack-resource \
--stack-name YOUR_STACK_NAME \
--logical-resource-id LoadBalancer | jq .StackResourceDetail.PhysicalResourceId) | \
jq .LoadBalancers[].DNSName

カスタマイズ

今回紹介した例は必要最小限な手順なので実際に使用する際には用途に応じた追加の設定が必要だと思います。以下に私のユースケースにフィットしなかった点を挙げておきます。

docker compose stopでタスクを停止できない

利用料金を考えると使っていないときはタスクを停止したいところですが停止するにはdocker compose以外のインターフェースから操作する必要があります。またdownサブコマンドではスタックが削除されるので意図しない削除が行われないよう注意が必要です。

テストスクリプトの変更、切り替えが容易にできない

今回の例ではdocker-composeファイルに実行するテストスクリプトファイル名をハードコードしているのでテストスクリプトの変更が容易に行えません。以下のような対応案を検討し、2つ目の方法を採用しました。

  1. コンテナには外部ストレージ(パラメータストアなど)から実行するスクリプトファイル名を取得するブートストラップスクリプトを含め、テストスクリプトはS3から取得する
  2. 任意のテストスクリプトを実行するタスク定義を作成、サービスの更新を行うことで更新する (スクリプト化)

テストスクリプト自体の更新はローカルで十分にテスト実行ができる前提があり頻繁に更新することはないだろうということで対応はしていません。

まとめ

docker-compose ECS integrationを使ってなるべく少ない設定でローカルとAWS環境上にlocustクラスタを構築してみました。最後に書いたように実際はここで紹介した例よりもカスタマイズや追加のコーディングをしましたが最初のクラスタ作成までの時間がとても短かったので見通しがよく効率よく作業できました。