Locustの分散負荷テスト環境をAWS Fargateを使って簡単に用意してやる

# 概要
この記事では、負荷テストツール**Locust**で分散負荷テスト環境を構築するに当たって私が使っている方法をまとめます。
構築は**AWS Fargate**を使って、設定をできるだけ少なくしました。
AWSの操作には**Terraform**を使って、構築・破棄を繰り返しできるようにしています。

# 背景
## これまでやっていた負荷テスト
負荷テストはどのようにして実行しているでしょうか？
私はこれまで簡単なものはApacheBenchで行い、
ログインを含むシナリオが必要なものはシェルスクリプトでコードを書いて実行していました。

しかし、ApacheBenchは静的なWebサイトなどで使うには良いのですが、Webアプリのテストとなると機能が足りないと感じていました。
シェルスクリプトを使えば何でもできる反面、テストの作成に時間がかかりがちで、メンテナンスもしずらくなっていました。

## Locustを使った負荷テスト

そんなときに、Locustというツールを知りました。
まだ使い始めて一ヶ月ほどですが、満足できそうな予感がしています。
(ツールの特徴については公式をはじめ多数情報があるので、割愛します)

## 今回紹介する方法の良い点
Locustは気に入ったので、これから何度も使うつもりなのですが、毎回Locustサーバーを立てるのは面倒です。
使わないサーバーを停止状態で放置するのもリソースが無駄になって良くないと思います。
そこでFargateとTerraformも使い、使いたいときに立ち上げてすぐに破棄できるようにコード化しました。

- **Locustを使うことで**
    - テストスクリプトが見やすくなる(と思う)
    - イベントやタスクの重み付けで、リアルなユーザーを想定したシナリオでテストできる
    - 分散クラスターの構築に対応していて、テストのスケールアウトに対応できる

- **Fargateを使うことで**
    - 多数のコンテナをサーバーの台数やスペックを気にせずに立ち上げられる

- **Terraformを使うことで**
    - 使いたくなったときにコマンド一発で環境が立ち上がり、コマンド一発でネットワーク設定もろとも破棄できる

# 構築手順
実際に環境を構築する方法です。

## 攻撃スクリプトコンテナを用意する
攻撃スクリプトは事前に用意して、コンテナに入れてイメージリポジトリにpushしておきます。
コンテナ内には複数種類のスクリプトを入れておいて、[Terraformの変数](#接続情報と変数)でどのスクリプトを実行するか切り替えられるようにしています。

実業務では、攻撃スクリプトコンテナはプライベートなECRリポジトリに置いていますが、今回はgithubとDockerHubでサンプルを作っています。

github: https://github.com/neilli-sable/locust_scripts
dokcerhub: https://hub.docker.com/repository/docker/neilli/locust_scripts

## Terraformから環境を構築する
このリポジトリに置いたコードから、攻撃スクリプトコンテナを起動して構築します。
https://github.com/neilli-sable/locust_fargate

### 設定内容について
「ネットワーク設定する -> ECSのクラスタを作る -> クラスタの上でLocustコンテナを動かす」というような内容になっています。
細かい部分はコードをご確認いただきたいですが、いくつか設定のポイントになった箇所を書いておきます。

#### 接続情報と変数
variable.tf.defaultにプロバイダーと変数設定のサンプルがあります。
variable.tfとリネームして、設定値を変更することで構築するLocustクラスタのスペック変更などができます。

**接続情報**として、providerに名前付きプロファイル名と、regionを指定しておきます。

```
provider "aws" {
  profile = "my-profile"
  region  = "ap-northeast-1"
}
```

**変数**は以下の種類を用意しました。

- general_name
    - AWSのリソース名やタグ付けに使うための文字列を指定します
- slave_count
    - LocustのSlaveサーバーの数を指定します
- fargate_cpu
    - LocustコンテナのCPUスペックを指定します
- fargate_memory
    - Locustコンテナのメモリ量を指定します
- locust_container
    - Locustの攻撃スクリプトを入れたコンテナのURLを指定します
- locust_script_path
    - 使用する攻撃スクリプトのファイルパスを指定します

#### 開放するポート
**Masterサーバー**は3つのポートを開放します。

- 8089
    - WebブラウザからUIにアクセスするためのポート
- 5557/5558
    - Slaveサーバーからの通信を受けるためのポート

5557と5558はSlaveサーバー向けのセキュリティグループからのみアクセス可にしておきます。
8089は今回アクセス制限していませんが、必要に応じてIP制限などを設定すると良いと思います。

一方、**Slaveサーバー**はポート開放は必要ありません。
セキュリティグループからingressは削除し、egressだけ設定して、外部からの接続はできないようにしておきます。

#### MasterサーバーのプライベートDNS
SlaveはMasterに通信しないといけないですが、コンテナは起動するまでIPアドレスが割り当たっていないため、単純なIPアドレスの指定は使えません。
なのでECSのサービスディスカバリ機能を使って、プライベートDNSを設定します。

vpc.tfにある、この以下の部分です。


```
# Service Discovery
resource "aws_service_discovery_private_dns_namespace" "locust_internal" {
  name        = "locust.internal"
  description = var.general_name
  vpc         = aws_vpc.vpc.id
}

resource "aws_service_discovery_service" "master" {
  name = "master"

  dns_config {
    namespace_id = aws_service_discovery_private_dns_namespace.locust_internal.id

    dns_records {
      ttl  = 10
      type = "A"
    }

    routing_policy = "MULTIVALUE"
  }

  health_check_custom_config {
    failure_threshold = 1
  }
}
```

そうすることで、タスク定義で`master.locust.internal`というプライベートDNSアドレスを解決できます。

```
  container_definitions = <<DEFINITION
[
  {
    "cpu": ${var.fargate_cpu},
    "essential": true,
    "image": "${var.locust_container}",
    "memoryReservation": ${var.fargate_memory},
    "name": "locust",
    "command": ["locust", "-f", "${var.locust_script}", "--slave", "--master-host=master.locust.internal"]
  }
]
DEFINITION
```

### Terraformの実行
`terraform` ディレクトリが実行用のディレクトリなので、移動します。

```
cd ./locust_fargate/terraform
```

「[接続情報と変数](#接続情報と変数)」の項目でも書きましたが、variable.tfを作成して、接続情報などを設定します。


初回のみ、initコマンドを使います。

```
terraform init
```

一度 init すれば、以後はapplyコマンドで環境構築できます。

```
terraform apply
```

## 負荷テストを実行する
### UIへのアクセス

`terraform apply` の結果、成功すればURLを出力するようにしています。
こんな感じに。

```
Outputs:

endpoint = access to here (wait a minute): http://locust-fargate-application-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com
```

`terraform apply`が終わって**しばらくしてから**URLにアクセスすると、LocustのUIが表示されます。
変数で設定した数のSlave数になっていることが確認できるはずです。

しばらくしてからと書きましたが、`terraform apply`の完了からサーバーが実際に起動完了するまでにタイムラグがあります。起動する前にURLにアクセスすると503エラーとなるので数分程度待たないといけません。
いつまでも503が続く場合は、エラーなどが原因でタスク自体が停止している可能性があります。

### Locustを使う
UIに接続できれば、普通にLocustを使うことができます。
「Number of users to simulate」「Hatch rate」「Host」を入力して、テストを開始してください。
Hostに「http://」から入力しないといけないのをいつも間違えるのは私だけでしょうか。

## お片付け
テストを実行してデータの回収などを完了したら、AWS上に作成したリソースを破棄します。

```
terraform destroy
```

これでキレイサッパリなはずなので、気軽に破棄してお金が掛からない状態にしておきます。
またテストしたくなったら、`terraform apply`からやり直すだけです。

# 感想
これまでの負荷テストだと、始めるまでがちょっとした仕事量になってしまい、実行がおろそかになっていたように思います。
もっと気軽にテストをしていきたいものです。

# 参考文献など

- [AWS FargateとTerraformで最強＆簡単なインフラ環境を目指す](https://qiita.com/tarumzu/items/2d7ed918f230fea957e8)
    - TerraformからFargateを操作する際の導入
- [MQTTの負荷テストもバッチリ！！Locustを活用した分散負荷テスト環境の構築](https://dev.classmethod.jp/cloud/aws/locust-with-performance-test/)
    - Locust+ECS の組み合わせの事例
- [ECSのサービスディスカバリーが東京にやってきて、コンテナ間通信の実装が簡単になりました！](https://dev.classmethod.jp/cloud/aws/ecs-service-discovery/)
    - ECSのサービスディスカバリー概要
- [Amazon Web Services負荷試験入門](https://gihyo.jp/book/2017/978-4-7741-9262-8) (書籍)
    - 負荷試験全般の参考図書