Gatlingによる分散負荷試験を自動化するKubernetesオペレーターGatling Operatorの紹介

こんにちは。SRE部の川崎(@yokawasa)、巣立(@tmrekk_)です。私たちは、ZOZOTOWNのサイト信頼性を高めるべく日々さまざまな施策に取り組んでおり、その中の1つに負荷試験やその効率化・自動化があります。本記事では、私たちが負荷試験で抱えていた課題解決のために開発、公開したOSSツール、Gatling Operatorを紹介します。

github.com

はじめに

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(後述)をクラスターにデプロイすると、カスタムコントローラーにより、次のような一連のタスクが自動実行されます。

  1. Gatling Runner Jobの作成
    • Gatling Runner Jobは、指定された並列実行数(Parallelism)分のGatling Runner Podを作成します
    • 各Gatling Runner Podでは、Gatlingテストシナリオを実行して、出力された結果レポート用ログファイル(simulation.log)をクラウドストレージにアップロードします。次の「Gatling Runner Podのマルチコンテナー構成」でGatling Runner Podについてさらに詳しく解説します
  2. Gatling Reporter Jobの作成(オプショナル)
    • Gatling Runner Jobが完了すると、Gatling Reporter Jobを作成し、そのJobがGatling Reporter Podを作成します
    • Gatling Reporter Podはすでにクラウドストレージにアップロードされた全Pod分の結果レポート用ログファイルをローカルファイルシステムにダウンロードします。そして、すべてのログファイルを元に集約したHTML結果レポートを生成し、それをクラウドストレージにアップロードします
  3. 試験結果をメッセージ通知プロバイダーに送信(オプショナル)
    • 前のすべてのステップが完了すると、試験の実行結果をメッセージ通知プロバイダー用Webhookに送信します
  4. 関連リソースのクリーンアップ(オプショナル)
    • すべてのステップが完了すると、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のデプロイ手順を説明します。

事前準備

クラスターの構築

今回使用するクラスターは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のマニフェストを使用しています。必要に応じてバージョンを変更してください。なお、バージョンはリリース一覧ページより確認できます。

github.com

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.generateReporttrueの場合、後述する.spec.cloudStorageSpecの設定も必要になります。

.spec.generateLocalReportではGatlingの実行結果レポートをPod毎に生成するかどうかを指定できます。

.spec.notifyReportではGatlingの実行結果を通知するかどうかを指定できます。.spec.notifyReporttrueの場合、後述する.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を指定しています。

他にも、resourcesaffinityなど標準の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ユーザーガイドをご覧ください。

github.com

続いて、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-1gatling-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では、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

https://hrmos.co/pages/zozo/jobs/0000010hrmos.co

カテゴリー