はじめに
サービスのパフォーマンスチェックをするために、1万リクエスト/秒以上の負荷を瞬間的(スラムテスト)や持続的(ドリップテスト)に生成する必要が出てきました。TerraformでGoogle Kubernetes Engine(GKE)クラスタをワンコマンドで展開、負荷環境(Locust)のクラスターを作成するツールを作成しました。
https://github.com/studio-design/distributed-load-testing-using-kubernetes-locust
環境
- Google Kubernetes Engine
- Google Cloud SDK 361.0.0
- Terraform 1.0.5
- Locust Cluster (Helm)
環境構築
Terraformファイルの構成は以下です。この記事を参考にさせていただきました。
技術とかボドゲとかそんな話をしたい
正解が未だに見つからないTerraformのディレクトリ構成を考えてみた
├── projects
│ └── studio-loadtesting
│ ├── 0-build-cluster
│ ├── 1-build-monitoring
│ │ └── values
│ └── 2-deploy-locust
│ └── values
└── usecases
└── gke_cluster
- GKE環境の構築
- Locust Cluster(Helm)の展開
- 負荷スクリプトの更新(ConfigMap)
をMakefile
を利用して操作できるようにします。
負荷をかけるターゲットのサーバーを立てる
GCP公式のLocustサーバーテストにある、サンプル アプリケーションのデプロイでターゲットのサーバーを建てました。
Makefileの作成
以下を参考にして、Makefile.example
からMakefile
を作成します。
値 | 説明 |
---|---|
PROJECT_ID | GCPプロジェクトID |
CLUSTER_NAME | クラスタの基本名。クラスタの削除には時間がかかるため、このツールではベースとなるクラスタ名の最後にランダムなテキストを追加しています。 |
REGION | GCPのRegion |
ZONE | GCPのゾーン名 |
MACHINE_TYPE | ローディングマシンのマシンタイプ。詳細はマシンタイプを参照してください |
CREDENTIALS | サービスアカウントのJSONファイルへのフルパスです。(相対パスは不可) |
SERVICE_ACCOUNT_EMAIL | サービスアカウントのEメール。例:[ユーザー名]@[プロジェクト名].iam.gserviceaccount.com
|
TARGET_HOST | 負荷を掛ける対象ホストのURLです。 |
TARGET_HOSTはご自身で管理されているサーバーを指定してください。それ以外のサーバーは指定しないようにしてください。
環境の展開
まずdeploy
フォルダに移動します。
make help
と打つと利用できるコマンドのヘルプが表示されます。
terraformの初期化
make init_all
でterraform init
を全サブプロジェクトで実行します。
GKEクラスターの作成
make build
を実行してGKEクラスターをセットアップし、作成したGKEクラスターを指すgcloud
コマンドを初期化します。構築までに5分程度かかります。
詳細は0-build-cluster
ディレクトリにありますが、以下を行っています。
-
gke_cluster
モジュールを利用してクラスタを展開。クラスタを作って潰してを繰り返している際に、GCP側で完全に環境を消しきれていない状況で、再構築時にエラーが発生することがあったので、ランダムな文字列をサフィックスとしてつけている。 - GKEクラスタ生成後、生成クラスタ情報を
kubeconfigfile
に取得。GKE絡みのterraformモジュールを利用する際に必要になる。 -
locust_connect.sh
という名でgcloud
コマンドを、作成したGKEに向けるスクリプトを生成。Makefile内部で直で実行してしまうと、その実行中のみ有効なだけになってしまうので、一旦シェルファイルに吐き出して実行させている。
deploy/projects/distributed-load-testing-using-kubernetes-locust/0-build-cluster 以下に、GKEの設定情報ファイルとしてkubeconfigが生成され、またgcloudコマンドのGKEクラスタへの初期化用に、gcloud_conf.shというファイルが生成されます。これは以後実行する他のmakeコマンドからも参照されます。
Locustスクリプトの編集
デフォルトでは、locust
ディレクトリ以下の
- main.py
- libディレクトリ以下のpyという拡張子を持つ複数ファイル
をConfigMap
に展開し、Locustクラスターから参照できるようにします。main.py
の中に負荷スクリプトを書いて展開してください。またサンプル用の負荷ファイルがmain.py
と同じディレクトリに配置してあるので、中身をコピーしてmain.py
に貼り付ければ利用できます。
Locustクラスターの作成
make a_locust
を実行して、パフォーマンステスト用の locust
と必要なコンフィグマップ(ロードテストスクリプトを格納する)をセットアップします。
詳細は2-deploy-locust
内部にありますが、
-
kubernetes
,kubectl
,helm
モジュールで先ほど生成したkubeconfig
を利用して、GKEに接続できるように準備 -
helm_updater
で、必要なhelm
リポジトリを取得できるように設定 -
kubernetes_config_map
リソースを使って、LocustスクリプトファイルとライブラリをConfigMap
に展開 -
helm_release
リソースを利用して、Locustのhelmファイルを展開。Locustのhelmのパラメータ設定はlocust/values.yaml
から取得する。
helmチャートはhttps://github.com/helm/charts 以下が廃止になったので、https://charts.deliveryhero.io/ のものを利用しています。
デフォルトではスクリプトはlocustディレクトリ以下のmain.pyを参照し、ライブラリはlib以下に書かれているpyという拡張子がつくファイルをConfigMapに展開します。
Locustマスターサーバーへの接続(port forwarding)
make locust
これでローカルへのポートフォワーディングが行われます。これで、localhost:8089
でLocust Master
にアクセスできるようになります。
負荷スクリプトを書き換えて、再度Locustクラスタに展開
make refresh
Scriptの書き換えにはConfigMap
を利用しています。そのため手順としては以下となります。
- Scriptを書き換え
- ConfigMapを更新
- Podを更新
これら作業をmake refresh
コマンド一回で実施します。terraform
による更新処理が終わり、Locustクラスタが再展開されたら、make locust
でLocust Master
に接続します。
ローカルで負荷テスト環境
とはいえ、負荷スクリプトが完成するまで、試行錯誤を繰り返すかと思います。そのたびにGKE上で毎回ConfigMap
を更新して、クラスターを再展開というのは時間がかかります。
意図する負荷パターンのスクリプトが完成するまでは、ローカルで色々試行錯誤できた方が効率がいいかと思います。そのため、docker-compose
を利用して1ワーカースレッドで起動する小規模クラスタをローカルで動かせるdocker-compose.yaml
も添付しました。
ローカルでスクリプトを動作させるには
docker-compose up --build --scale worker=1
でクラスター起動させ、localhost:8089
でLocust Master
にアクセスできます。
参照設定
今回、GKE上のLocust Cluster
で、spike_load.py
を使用して10000RPSを生成した設定は以下になります。
spike_load.py
は最初に全ユーザー展開し、すべてのユーザーが展開し終わるまでリクエストを投入するのを待ちます。(全ワーカーが同期しているわけでなく、各ワーカー毎でのユーザーになります。)
パラメータ | 説明 |
---|---|
Locustワーカーのマシンタイプ (Makefile 内のMACHINE_TYPE ) |
e2-standard-2 |
ワーカーのReplicas(values.yaml の66行目) |
15 |
ユーザー数(spike_load.ph の15行目、user_amount 変数) |
10000 |
この設定で
- 最初の1秒間のRPSは600前後
- 15~20秒後には10000RPSに達し、それ以上になります。
ほぼ10000RPSで負荷をしばらく保持する場合は、constant_pacing
関数でアクセスのペースを上げるとよいでしょう。
spike_load.py
では、以下の行でdwell load time
を設定しています。このコードは、user_amount
のユーザー数で120秒維持することを意味しています。維持時間は適宜変更してください。
target_with_times = Step(user_amount, 120)
ワーカーとユーザーのバランスを調整する方法
低コストで負荷を発生させるためには、できるだけワーカーの数を少なくしたいかと思います。ここでは自分が実際行った、ユーザー数とワーカー数を適切に調整するためのサンプルステップをご紹介します。
spike_load.py
スクリプトを利用して、10000RPSを発生させるのに、試した手順は以下の通りです。
最初のトライ
HPA
を有効にして、10台のワーカー、2000人のユーザーからスタートし、Locustクラスタがどのくらいの負荷を発生できるかを確認しました。この場合、Locustは3000RPSを生成し、そこで飽和しました。Cloud LoggingではCPUエラーが発生していないことから、CPUがまだ限界に達していないと考えられます。
2回目のトライ
3倍のユーザー数で10000RPSを生成すると仮定します。ユーザー数を6000に変更し、make refresh
を実行、ConfigMap
とLocustクラスタを再構築します。結果、ワーカーが自動的に15にスケールアップし、負荷が10000RPS以上になったことを確認しました。
3回目のトライ
ワーカーがスケールする時間を短縮するため、values.yaml
で初期ワーカー数を 15
に調整し、make refresh
で Locust ポッドを更新します。
まとめ
10000 RPS以上の負荷を生成するクラスタの構成には成功しましたが、1〜数秒で10000RPSに到達するような、スパイクを再現するようなスクリプトは上手くつくれませんでした。どなたか見識がある方がいらっしゃったら教えてください。
比較的容易に負荷生成サーバーは作成できたので、GKE上に負荷サーバーの構築を検討されている方の参考になれば幸いです。