はじめに
こんにちは、計測プラットフォーム開発本部SREブロックの渡辺です。普段はZOZOMATやZOZOGLASSなどの計測技術に関わるシステムの開発、運用に携わっています。
先日私達のチームでは、リリースフローにステージング環境での負荷試験を自動化する取り組みを行いました。今回説明する「負荷試験の自動化」が何を表すのかを定義すると、ここではステージング環境のアプリケーションバージョンを変更した際に、人の手を介さずに負荷試験が行われることを指します。
Kubernetes環境における負荷試験の自動化を検討している方は是非参考にしてください。
目次
負荷試験を自動化する前の課題
私達のチームではEKS環境でアプリケーションを管理しており、これまでArgo CDやArgo Rolloutsなどのツールを導入することで、リリース安定性を向上させてきました。
特にArgo Rolloutsを導入したカナリアリリースにより、アプリケーションエラーやレイテンシ悪化などによるユーザへの影響を最小限にできました。
しかしながら、ステージング環境での動作確認だけでは見落とされる問題については、カナリアリリース後にアラート通知がくるなど未然に防げませんでした。例えば、ライブラリ更新によるメモリリークの発生がリリース後に発覚する事象などが挙げられます。
負荷試験のシナリオ設計
そこで、上記の問題を解決すべく、ステージング環境で負荷試験を自動化する取り組みを行いました。
まずは、負荷試験のシナリオを設計するために行ったことを紹介します。
目的設定
負荷試験はあくまで手段であるため、目的を見失わないようにあらかじめ「アプリケーションの特性」「現在の課題」「負荷試験の目的」などをドキュメント化しました。
負荷試験と一口に言っても、目的によってシナリオが変わってくるので、多少面倒でも目的をまとめておくことをオススメします。
自分達が負荷試験で何を実現したいのかを検討した結果、「負荷試験を自動化する前の課題」でも説明しているようにリリース後の性能悪化を事前に検知することが一番の目的となりました。
ピーク時の性能テスト | ロングランテスト | |
---|---|---|
テスト目的 | 在庫数よりも需要が大きく上回る商品の発売やメディア露出によるリクエストピーク時に十分なパフォーマンスが得られるか確認する | 通常時に十分なパフォーマンスが得られるか確認する(メモリリークの発生など長時間テストしないと表面化しない問題の確認) |
「ピーク時の性能テスト」を基本として、「ロングランテスト」はオプションとして設定しています。メモリリークなどロングランテストを行うことで検知の精度が高まるものもありますが、リリース作業に数時間の負荷試験を実行することは現実的でないと判断しました。
なお、今回は見送りましたが、今後は以下の観点でも負荷試験を活用したいと考えています。
- システム限界点を見極める
- 予想外のスパイク発生を想定したテストを行い、短時間に処理が集中した場合に発生する現象を確認する
- 限界を超えた場合にどのような現象が発生するか確認する
現状調査
テスト目的から、ピーク時の本番トラフィックを基にシナリオを作成するため、各APIごとリクエストの実績を調査しました。時間帯や曜日ごとに数が変化しているため、現状調査を通じてサービスの理解やユーザ行動などを深く認識できました。
負荷試験とは直接関係ありませんが、サービスのドメイン知識が少ないエンジニアにとって、現状調査をすることで得られるものは大きいと実感しました。このため、入社間もないドメイン知識が少ない方こそ負荷試験のタスクに取り組むことをオススメします。
目標値設定
負荷試験の実行結果と比較するための目標値を設定しました。
「目標値をクリアできないとリリースしない」などの指標にするため、既にSLOやSLAを策定している場合はその数値を利用してもいいと思います。しかし、私たちのチームでは各APIごとのSLOを策定していないため、導入初期の段階として「現状のレイテンシやエラー率を上回らない」ことを目標とする値を設定しました。
今後、各APIごとのSLOを策定した際には、こちらの目標値も更新していくつもりです。サービスの成長と共に目標値は変わってくるため、一度設定して終わりではなく定期的に見直すことが大切です。
シナリオ設計
上記の調査で得た情報を基にシナリオを設計します。
計測プロダクトを利用するユーザの行動は、大きく2つに分けられます。ユーザ行動はとてもシンプルなので、シナリオもシンプルになります。
- 計測ユーザ(計測行動)
- 商品閲覧ユーザ(計測後の行動)
例えばZOZOGLASSの計測ユーザ行動は以下のようになります。
2パターンのユーザ行動のシナリオを設計し、目的設定や現状調査で積算したパラメータ(テスト時間やリクエスト毎秒など)を割り当てていきます。計測ユーザよりも商品閲覧ユーザの方が多いため、以下のようなイメージでシナリオを実行していきます。
なお、先ほどオプションとしてロングランテストを設定していると説明しましたが、パラメータを環境変数で設定することによりテスト目的に合わせて簡単に切り替えられるようにしています。
負荷試験を自動化する取り組み
それでは、負荷試験を自動化する取り組みについて紹介します。
私たちはKubernetesマニフェストをGitHubで管理しており、Argo CDによるGitOpsを実現しています。
Podのコンテナイメージのタグ更新PRをmainブランチにマージした時、Argo CDによりステージング環境のPodが入れ替わります。
リリース担当者はPRのマージを行うまでを担当し、Podの入れ替えから負荷試験の実行までを自動的に行うような仕組みを構築しました。
構成
負荷試験ツールとしては、社内でも利用実績のあるGatlingを採用しました。
GatlingはテストシナリオをScalaで記述でき、結果レポートをHTMLで自動生成してくれます。計測プロダクトではバックエンドにScalaを採用していることもあり、馴染みのある言語でシナリオを作成できました。
処理の流れ
負荷試験を実行するPodが行うことは以下になります。
- シナリオに沿ったリクエストを送る
- 実行結果をS3に保存してSlackに通知する
- 負荷試験で作成したデータを削除する
シナリオに沿ったリクエストを送る
負荷試験を実行するコンテナをアプリケーションのサイドカーに設置してリクエストを流すこともできます。
しかし、計測サービスではレイテンシを重要視しているため、より本番トラフィックに近いネットワークでリクエストを送信したいという要求がありました。そこで、負荷試験を実行するPodからNAT Gatewayを経由する設計にしました。
ステージング環境は、ALBのルールで限られたIPからのリクエストのみ受け付ける設定をしています。NAT Gatewayに紐付けたElastic IPを許可することでリクエストを受け付けるようにしました。
実行結果をS3に保存してSlackに通知する
リリース担当者が負荷試験を待っている間に別の作業をしていると、つい負荷試験の完了に気づくのが遅れてしまいます。結果的にリリース作業も遅れるため、負荷試験の完了時にSlackへ結果レポートのリンク先(S3)を通知します。リンクをクリックするだけで結果を確認できるので、リリース担当者の手間が省けます。
負荷試験で作成したデータを削除する
負荷試験ではダミーデータで計測するため、ダミーデータがDBやS3に蓄積されていきます。塵も積もれば山となるため、負荷試験の実行後に該当データを削除しコスト負担が大きくならないようにしています。
負荷試験の実行トリガー
私達のアプリケーションはJVM環境で動作しており、アプリケーションコンテナがデプロイされる際は、リクエストを受け付ける前に暖機運転を行っています。
また、起動中のPod数をコントロールするためのPodDisruptionBudget設定やカナリアリリースを採用していることもあり、すべてのPodが入れ替わるまでにタイムラグが生じます。
このため、アプリケーションのデプロイと同じトリガーで負荷試験用のPodを起動すると、全てのPodが新しいバージョンへ入れ替わる前に実行される場合があります。
これでは、正しい試験結果を得ることはできません。負荷試験の開始をsleepなどで調整することも考えられますが、確実にPodが入れ替わった保証にはなりません。
そこで、全てのPodが新バージョンに入れ替わったことを負荷試験のトリガーにするため、当初はKubernetesのカスタムリソースを作成することを検討しました。
しかし、コントローラのメンテナンスなどを考慮すると負担も大きく感じたので、他の方法がないかネットの海を探索していたところ、Argo CDのResource Hookが目に留まりました。
Jobリソースのannotationsにargocd.argoproj.io/hook: PostSync
を設定するだけでデプロイ後のヘルスチェックが成功した後でJobを起動できます。たった一行追加するだけで要件を満たすことができたので、Argo CDを導入した恩恵を受けました。
今後の改善ポイント
自動化の精度向上
Argo CDのResource Hookはとても便利なのですが、Pod関連以外のリソース変更時もトリガーになるところがコントロールできません。
このため、Kubernetesリソースの変更があった場合(例えばServiceAccountを作成するなど)、アプリケーションは変更していないのですが負荷試験が実行されてしまいます。
現状、Kubernetesリソースの変更や追加は頻繁に発生していないので、意味のない負荷試験が実行される回数は少ないです。しかし、開発が盛んなプロダクトへ導入する際は、この辺を工夫する必要がありそうです。
リリース判断の自動化
今回、負荷試験の自動化は実現できましたが、リリースの判断の自動化までは実現できていません。
現状は、Slackに通知された結果レポートと目標値設定で策定した値を比べて、リリース担当者がリリースの可否を判断しています。
ヒューマンエラーを防ぐことはできていないため、レイテンシの悪化に気づかずリリース作業を進める可能性もゼロではありません。
また、負荷試験の結果を待たずとも本番リリースはできてしまうので、今後は負荷試験の結果でリリース判断を機械的に判断する仕組みを構築していきたいです。
まとめ
今回は計測システムのリリース業務の安定性向上の一例として、負荷試験を導入する方法についてご紹介しました。ステージング環境のデプロイ時に自動で負荷試験を実行する仕組みを構築したことで、より安全に定期リリースを行えるようになりました。
負荷試験は導入して終わりではないので、今後はさらなるサービスの安定性に役立てるため積極的に活用していきたいと思います。
終わりに
計測プラットフォーム開発本部では、ZOZOMATやZOZOGLASS、ZOZOFITなど様々なサービスを展開しています。今後も新しいサービスや機能を開発する予定であるため、サービスを一緒に盛り上げていける方を募集しています。少しでもご興味のある方は以下のリンクからぜひご応募ください。