こんにちは。SRE部の川崎(@yokawasa)、巣立(@tmrekk_)です。私たちは、ZOZOTOWNのサイト信頼性を高めるべく日々さまざまな施策に取り組んでおり、その中の1つに負荷試験やその効率化・自動化があります。本記事では、私たちが負荷試験で抱えていた課題解決のために開発、公開したOSSツール、Gatling Operatorを紹介します。
はじめに
ZOZOTOWNは非常にピーク性のあるECシステムであることから、常にそのシステムが受けうる負荷の最大値を意識しております。想定しうる最大規模の負荷を受けてもユーザー体験を損なうことなくサービス継続できることをプロダクションリリースの必須条件としています。したがって、新規リリースやアップデート、大規模セールなどのシステム負荷に影響を与えうるイベント前など、比較的頻繁に負荷試験を実施しています。そして、社内でもっとも利用実績のある負荷試験ツールがGatlingになります。
Gatlingとは、Webアプリケーション向けのOSS負荷試験フレームワークです。テストシナリオをScala(Gatling 3.7からはJavaやKotlinもサポート)のDSLで記述でき、結果レポートをHTMLで自動生成してくれます。
本記事で紹介するGatling Operatorは、このGatlingをベースとした分散負荷試験のライフサイクルを自動化するKubernetes Operatorです。
Kubernetes Operatorとは、カスタムリソース(以下、CR)とそのCRにリンクされたカスタムコントローラーによりKubernetesを拡張するための仕組みであり、Kubernetes上で稼働するワークロードのライフサイクル管理の自動化を可能にします。ワークロードの目的の状態を定義したCRをクラスターにデプロイすると、カスタムコントローラーが制御ループを通じてその目的の状態に近づくように制御します。
Gatling Operatorの場合は、分散負荷試験の内容を定義したGatling CRがクラスターにデプロイされると、Gatling CRにリンクされたコントローラーが目的の状態に近づくように制御することで一連の分散負荷試験のタスクが自動化されます。
なぜ開発したのか?
開発の発端は、ZOZOTOWN冬セール対策の負荷試験における課題感からでした。
冬セールはZOZOTOWNにおいて一年でもっともユーザーアクセスが多いイベントです。これを安定的に乗り越えるべく、2021年冬セールから事前にオンプレ・クラウドを横断した大規模な負荷試験を本番相当の環境を使って実施しております。この負荷試験は、機能ごとの単体の負荷試験ではなくユーザー導線に合わせてZOZOTOWNにセール同等のトラフィックを再現し、ボトルネックとなりうる箇所を事前に潰すことを目的としています。なお、今年の2022年冬セール向け負荷試験の詳細については別記事にて紹介される予定です。
さて、この冬セール向けの負荷試験ですが、当然ながら目標スループットを再現するためにはGatling実行用ノードを大量に並べて並列実行させる必要があります。これが単一システムの負荷試験であれば、試験用にチューニングされた一台の仮想マシンからの実行で事足りることが多く、多くても数台並べてタイミングを合わせて実行することで目標スループットを再現できます。ただし、冬セール規模となればそうも行かず、大量のGatling実行用ノードの準備、大量ノードからの実行タイミング調整やレポート生成などさまざまな運用面での課題感がありました。
そこで、2021年冬セール向けの負荷試験では、運用面での課題感を解決すべくAmazon ECSからAWS Fargateをデータプレーンとして利用する方式を採用しました。そこに大量のGatling実行用ノードを並べて分散負荷試験の実行やレポート生成などを自動化しました。これにより当初感じていた運用面での課題はある程度解消されました。ただし、逆にFargateの制約から生ずる課題に直面しました。
Fargateはオンデマンドでコンピューティングリソースを提供する仕組みであり、タスク実行毎にホストリソース確保と準備処理が行われるため、EC2と比べPod起動までの待ち時間が長くなりがちでした。
タスク用に予約可能なvCPUとメモリの選択の幅が狭く、したがって目標スループットを再現するためには必要ノード数が多くなりがちになりました。これによりFargateの同時に実行可能なタスク数の上限に達しやすくなり、目標スループットを安定的に再現できないという課題がありました。なお、当時と比べるとFargateのインスタンスあたりの性能は向上し、同時に実行可能なタスク数の上限も上がっていることから問題は緩和されているといえます。
これらの課題を解消すべく、2022年冬セール対策負荷試験に向けてGatling Operatorを開発することになりました。これにより、分散負荷試験の自動化はもとより、Gatling用Podに柔軟にノードリソースの配分ができるようになりました。また、分散負荷試験がマニフェストで宣言的に定義できるようになったことも大きなメリットといえます。
Gatling Operatorの処理概要
Gatling Operatorの処理概要を簡単に説明します。利用者が分散負荷試験の内容を定義したGatling CR(後述)をクラスターにデプロイすると、カスタムコントローラーにより、次のような一連のタスクが自動実行されます。
- Gatling Runner Jobの作成
- Gatling Runner Jobは、指定された並列実行数(Parallelism)分のGatling Runner Podを作成します
- 各Gatling Runner Podでは、Gatlingテストシナリオを実行して、出力された結果レポート用ログファイル(simulation.log)をクラウドストレージにアップロードします。次の「Gatling Runner Podのマルチコンテナー構成」でGatling Runner Podについてさらに詳しく解説します
- Gatling Reporter Jobの作成(オプショナル)
- Gatling Runner Jobが完了すると、Gatling Reporter Jobを作成し、そのJobがGatling Reporter Podを作成します
- Gatling Reporter Podはすでにクラウドストレージにアップロードされた全Pod分の結果レポート用ログファイルをローカルファイルシステムにダウンロードします。そして、すべてのログファイルを元に集約したHTML結果レポートを生成し、それをクラウドストレージにアップロードします
- 試験結果をメッセージ通知プロバイダーに送信(オプショナル)
- 前のすべてのステップが完了すると、試験の実行結果をメッセージ通知プロバイダー用Webhookに送信します
- 関連リソースのクリーンアップ(オプショナル)
- すべてのステップが完了すると、Gatling CRとその関連リソースであるJobやPodを削除します
Gatling Runner Podのマルチコンテナー構成
分散負荷試験のメインワークロードであるGatling Runner Podのコンテナー構成について解説します。
上図のようにGatling Runner Podはマルチコンテナーで構成されています。gatling-runnerによるGatling負荷試験の実行以外に、gatling-waiterとgatling-result-transfererでそれぞれ次のような前処理と後処理が実行されます。
- gatling-waiterコンテナー
- Gatling Runner Jobにより作成される並列実行数(Parallelism)分のすべてのGatling Runner Podが開始されるまで待機します
- Gatling Runner Podが複数作成される場合、すべてのPodが同じタイミングでスケジューリングされる保証がないため、待機処理によりgatling-runner実行のタイミングを同期させます
- gatling-runnerコンテナー
- Gatlingテストシナリオを実行します
- 生成された結果レポート用ログファイルは共有Volume(emptyDir)に出力します
- gatling-result-transfererコンテナー
- gatling-runnerで生成された結果レポート用ログファイルを共有Volumeより読み込み、クラウドストレージにアップロードします
gatling-waiterとgatling-runnerはinitコンテナーとして、gatling-result-transferはメインコンテナーとして作成していることが特徴として挙げられます。initコンテナーはPod内でメインコンテナーの前に実行されます。また、initには1つ以上のコンテナーを定義でき、それらは1つずつ順番に実行されます。 なお、結果レポート生成を選択しない場合はgatling-result-transfererによる処理は不要であるため、gatling-waiterがinitコンテナーとして、gatling-runnerがメインコンテナーとして作成されます。
使い方(Quickstart)
ここでは、Gatling OperatorのインストールとGatling CRのデプロイ手順を説明します。
事前準備
- kubectlとkindのインストール
- gatling-operatorリポジトリのクローン
クラスターの構築
今回使用するクラスターはkindを使って構築します。まずは、kindを使ってクラスターを構築します。
なお、kindで構築するクラスターは、1.18以上を推奨します。また、kindで使用するNodeのImageバージョンはリリースノートから確認できます。
$ kind create cluster $ kubectl config current-context kind-kind
Gatling Operatorのインストール
kindで構築したクラスターにGatling Operatorをインストールします。
$ kubectl apply -f https://github.com/st-tech/gatling-operator/releases/download/v0.5.0/gatling-operator.yaml namespace/gatling-system created customresourcedefinition.apiextensions.k8s.io/gatlings.gatling-operator.tech.zozo.com created serviceaccount/gatling-operator-controller-manager created role.rbac.authorization.k8s.io/gatling-operator-leader-election-role created clusterrole.rbac.authorization.k8s.io/gatling-operator-manager-role created rolebinding.rbac.authorization.k8s.io/gatling-operator-leader-election-rolebinding created clusterrolebinding.rbac.authorization.k8s.io/gatling-operator-manager-rolebinding created deployment.apps/gatling-operator-controller-manager created
以上を実行することにより、CRDやManagerなどのリソースがデプロイされGatling CRを実行する準備ができます。
$ kubectl get crd NAME CREATED AT gatlings.gatling-operator.tech.zozo.com 2022-02-02T06:00:25Z $ kubectl get deploy -n gatling-system NAME READY UP-TO-DATE AVAILABLE AGE gatling-operator-controller-manager 1/1 1 1 44s
今回はv0.5.0のマニフェストを使用しています。必要に応じてバージョンを変更してください。なお、バージョンはリリース一覧ページより確認できます。
Gatling CRのデプロイ
続いて、Gatling CRをデプロイします。 ここでは、gatling-operatorリポジトリのサンプルを使用します。
$ git clone https://github.com/st-tech/gatling-operator.git $ cd gatling-operator $ kustomize build config/samples | kubectl apply -f - serviceaccount/gatling-operator-worker unchanged role.rbac.authorization.k8s.io/pod-reader unchanged rolebinding.rbac.authorization.k8s.io/read-pods configured secret/gatling-notification-slack-secrets unchanged gatling.gatling-operator.tech.zozo.com/gatling-sample01 created
上記を実行することでGatling Runner Podの実行に必要なServiceAccountやGatling CRがデプロイされます。
Gatling CRのデプロイ後、Gatling CR、Gatling Runner Job、Gatling Runner Podが生成され、Gatlingテストシナリオが実行されます。
$ kubectl get gatling NAME AGE gatling-sample01 16s $ kubectl get job NAME COMPLETIONS DURATION AGE gatling-sample01-runner 0/3 19s 19s $ kubectl get pod NAME READY STATUS RESTARTS AGE gatling-sample01-runner-4dk6z 0/1 PodInitializing 0 22s gatling-sample01-runner-nlxcm 0/1 PodInitializing 0 22s gatling-sample01-runner-zdqgq 0/1 PodInitializing 0 22s
PodのログからもGatlingが実行されていることが確認できます。
$ kubectl logs gatling-sample01-runner-4dk6z -c gatling-runner -f Wait until 2022-02-03 09:00:12 GATLING_HOME is set to /opt/gatling Simulation MyBasicSimulation started... ================================================================================ 2022-02-03 09:01:42 5s elapsed ---- Requests ------------------------------------------------------------------ > Global (OK=2 KO=0 ) > request_1 (OK=1 KO=0 ) > request_1 Redirect 1 (OK=1 KO=0 ) ---- Scenario Name ------------------------------------------------------------- [--------------------------------------------------------------------------] 0% waiting: 0 / active: 1 / done: 0 ================================================================================ ================================================================================ 2022-02-03 09:01:47 10s elapsed ---- Requests ------------------------------------------------------------------ > Global (OK=3 KO=0 ) > request_1 (OK=1 KO=0 ) > request_1 Redirect 1 (OK=1 KO=0 ) > request_2 (OK=1 KO=0 ) ---- Scenario Name ------------------------------------------------------------- [--------------------------------------------------------------------------] 0% waiting: 0 / active: 1 / done: 0 ================================================================================ ================================================================================ 2022-02-03 09:01:51 14s elapsed ---- Requests ------------------------------------------------------------------ > Global (OK=6 KO=0 ) > request_1 (OK=1 KO=0 ) > request_1 Redirect 1 (OK=1 KO=0 ) > request_2 (OK=1 KO=0 ) > request_3 (OK=1 KO=0 ) > request_4 (OK=1 KO=0 ) > request_4 Redirect 1 (OK=1 KO=0 ) ---- Scenario Name ------------------------------------------------------------- [##########################################################################]100% waiting: 0 / active: 0 / done: 1 ================================================================================ Simulation MyBasicSimulation completed in 14 seconds
このサンプルではGatlingの結果レポートの通知やクラウドプロバイダーへの結果レポートの保存は行われません。
以降の章で説明する.spec.cloudStorageSpec
や.spec.notificationServiceSpec
を設定することで可能になります。
設定例の紹介
Gatling CRの設定項目についてサンプルを用いて説明します。なお、Gatlingカスタムリソースの定義についてはこちらのCRDリファレンスページを参照ください。
Gatling CRについて
Gatling CRでは大きく次の5つを定義します。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: ## 実行フラグ generateReport: true notifyReport: true cleanupAfterJobDone: true ## Gatling Runner PodのSpec定義 podSpec: ## 結果レポート格納用のクラウドストレージの定義 cloudStorageSpec: ## 結果レポート通知先の定義 notificationServiceSpec: ## Gatlingテストシナリオと実行方法の定義 testScenarioSpec:
5つの定義について詳しく説明していきます。
実行フラグの設定
Gatling CRでは、Gatlingの実行に関する設定やGatling CRの挙動の設定が可能です。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: generateReport: true generateLocalReport: true notifyReport: true
.spec.generateReport
ではGatlingの実行結果レポートを生成するかどうかを指定できます。.spec.generateReport
がtrue
の場合、後述する.spec.cloudStorageSpec
の設定も必要になります。
.spec.generateLocalReport
ではGatlingの実行結果レポートをPod毎に生成するかどうかを指定できます。
.spec.notifyReport
ではGatlingの実行結果を通知するかどうかを指定できます。.spec.notifyReport
がtrue
の場合、後述する.spec.notificationServiceSpec
の設定も必要になります。
他にも、.spec.cleanupAfterJobDone
ではGatling Operatorが生成するJobの実行完了後の挙動を設定できます。
true
の場合、Runner Jobの実行が完了するとGatling CRは削除されます。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: cleanupAfterJobDone: true
Gatling Runner Podをカスタマイズする
podSpecでは、Runnner Jobが生成するPodの設定が可能です。
podSpecでは、.spec.serviceAccountName
が必須項目となっています。
このサービスアカウントはgatling-waiterコンテナーがGatling実行タイミングの同期目的で他のPodの状態を取得するために必要となります。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: testScenarioSpec: serviceAccountName: "gatling-operator-sa-sample" --- apiVersion: v1 kind: ServiceAccount metadata: name: gatling-operator-sa-sample --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: read-pods subjects: - kind: ServiceAccount name: gatling-operator-sa-sample apiGroup: "" roleRef: kind: Role name: pod-reader apiGroup: ""
以下が、.spec.podSpec
の例になります。
.spec.podSpec.serviceAccountName
にてさきほどのServiceAccountを指定しています。
他にも、resources
やaffinity
など標準のPodと同様の設定が可能です。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample01 spec: podSpec: serviceAccountName: "gatling-operator-sa-sample" gatlingImage: ghcr.io/st-tech/gatling:latest rcloneImage: rclone/rclone resources: limits: cpu: "500m" memory: "500Mi" affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/os operator: In values: - linux tolerations: - key: "node-type" operator: "Equal" value: "non-kube-system" effect: "NoSchedule"
Gatlingテストシナリオと実行方法を設定する
testScenatioSpec
では、Gatlingを実行する上で必要になるリソースの配置場所や定義方法などの設定が可能です。
.spec.testScenarioSpec.startTime
ではGatlingの実行開始時間の設定が可能です。
フォーマットは%Y-%m-%d %H:%M:%S
となっており、UTCで設定します。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: testScenarioSpec: startTime: 2022-01-01 12:00:00
.spec.testScenarioSpec.parallelism
ではGatlingの同時実行数、すなわちRunner Jobが生成するPod数の設定が可能です。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: testScenarioSpec: parallelism: 4
続いて、Gatlingのテストシナリオ、テストデータ、gatling.confの設定方法について説明します。 以下の2種類のデプロイ方法が用意されています。
- Gatlingコンテナーにまとめてパッケージ化してデプロイ
- ConfigMapとしてデプロイ
まず、Gatlingコンテナーにまとめてパッケージ化してデプロイする方法を説明します。
.spec.testScenarioSpec.simulationsDirectoryPath
では、Gatlingのテストシナリオのファイルパスの設定が可能です。
設定されていない場合は、デフォルトで/opt/gatling/user-files/simulations
が使用されます。
.spec.testScenarioSpec.resourcesDirectoryPath
では、テストに使用するデータのファイルパスの設定が可能です。
設定されていない場合は、デフォルトで/opt/gatling/user-files/resources
が使用されます。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: testScenarioSpec: simulationsDirectoryPath: "dir-path-to-simulation" resourcesDirectoryPath: "dir-path-to-resources"
上記で設定したデータをGatlingコンテナーにまとめてパッケージ化する方法についてはこちらのGatling Operatorユーザーガイドをご覧ください。
続いて、GatlingテストシナリオをConfigMapとしてデプロイする方法を説明します。Gatlingが使用するテストシナリオやテストデータ、gatling.confなどをGatling CRのマニフェストに直接記述できます。
.spec.testScenarioSpec.simulationData
では、シナリオファイルを記述できます。
.spec.testScenarioSpec.resourceData
では、テストデータを記述できます。
.spec.testScenarioSpec.gatlingConf
では、gatling.confを記述できます。
ここで記述されたものは、ConfigMapへと変換されControllerによって処理されます。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: testScenarioSpec: simulationData: MyBasicSimulation.scala: | # scalaファイルをここに書く resourceData: sample.csv: | # テストデータをここに書く gatlingConf: gatling.conf: | # gatling.confをここに書く
Gatling OperatorリポジトリにGatling CRマニフェストへ直接記述するサンプルも用意されています。
結果レポート格納用クラウドストレージプロバイダーを設定する
Gatling OperatorではGatligが生成したレポートを任意のクラウドプロバイダーへ格納できます。
執筆時点では、AWS(S3)・GCP(GCS)に対応しています。
cloudStorageSpec
では、格納するクラウドプロバイダーの情報を設定します。
以下の例では、Amazon S3にてap-northeast-1
のgatling-operator-reports
という名前のバケットにレポートを格納します。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: cloudStorageSpec: provider: "aws" bucket: "gatling-operator-reports" region: "ap-northeast-1"
なお、レポートの格納には他にもPodからAmazon S3にアクセスできるようにAWSクレデンシャルの設定が必要になります。詳しくは、ユーザーガイドを参照ください。
通知用メッセージプロバイダーを設定する
Gatlingの実行終了後にレポートのリンクと共に通知サービスプロバイダーに通知できます。
notificationServiceSpec
にて通知先の設定が可能です。
以下の例では、Slackに完了通知を送信します。
apiVersion: gatling-operator.tech.zozo.com/v1alpha1 kind: Gatling metadata: name: gatling-sample spec: notificationServiceSpec: provider: "slack" secretName: "gatling-notification-slack-secrets"
.spec.notificationServiceSpec.secretName
では、通知先であるSlackのWebhook URLが登録されたSecret名を指定します。
apiVersion: v1 data: incoming-webhook-url: # base64-encoded webhook URL for slack kind: Secret metadata: name: gatling-notification-slack-secrets type: Opaque
base64暗号化するとはいえWebhook URLをマニフェストに直接記載したくない場合もあります。そのような場合は、AWS Secrets Managerのような外部の秘匿情報を管理できるサービスに保存することを検討ください。
外部サービスに登録した秘匿情報からKubernetesのSecretリソースを生成するツールはいくつかあります。その中の1つのExternal Secretsの利用例を紹介します。
以下の例では、AWS Secrets Managerにnotification-slack-gatling-noticeという名前でシークレットを作成し、Webhook URLを保存しています。そのSecretをExternal Secretsを経由して取得するようにしています。
apiVersion: "kubernetes-client.io/v1" kind: ExternalSecret metadata: name: gatling-notification-slack-secrets spec: backendType: secretsManager data: - key: notification-slack-gatling-notice name: incoming-webhook-url property: incoming-webhook-url
実際に送られたメッセージがこちらです。
Report URLへアクセスするとGatlingが生成した結果レポートを確認できます。
まとめ
本記事では、Gatlingをベースとした分散負荷試験のライフサイクルを自動化するGatling Operatorを紹介しました。 Gatling Operatorにより、分散負荷試験の自動化を始め、Gatling用ノード選択、マニフェストによる分散負荷試験の宣言的管理が実現可能になりました。
今後は、AWSやGCP以外のクラウドプロバイダーへのレポート送信や、S3などの外部リソースのクリーンアップなどの機能を追加予定です。詳しくは、Issueをご確認ください。また、Gatling OperatorではIssueやPull Requestを歓迎しています。興味を持った方は、ぜひ使ってみてください。
さいごに
ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。