k6 + Github Actions  で開発の信頼性向上を目指した話

# 背景と動機
本番環境へのリリースまでにおける開発の信頼性向上を目指した話を書きたいと思います。

まず、日々の開発において僅かなパフォーマンスの低下には気づきにくく、本番リリース後にトラフィックが増えて気づくこともあると思います。また、数日〜数ヶ月経ってから気づいた場合は、どのリリースが原因となったかを調査するのも大変です。

外形監視で確認する方法もありますが、大抵の場合、ある程度の負荷がなければ気づきにくいものも多いと思います。
そのため定期的に負荷試験を実施して測定したくなるのですが、負荷試験にかかる工数・手間を考えるとなかなか気が進まず、大きな機能・変更をリリースするときだけ負荷試験をするというプロダクトも多いのではないでしょうか。

そこで、 [k6](https://k6.io/) と Github Actions を使って、日頃から自動でパフォーマンス測定を行い、デグレを検知する仕組みを作成してみました。

https://k6.io/

# k6 + Github Actions を使って開発段階のデグレを検知する
今回、負荷試験を実行するツールとして k6 を選択しました。他のツールとの比較は省略しますが、k6 はシンプルに実行でき、ドキュメントも充実しているため非常に取っ付きやすいツールだと思います。 
また、[公式のドキュメント](https://k6.io/docs/testing-guides/running-large-tests/)にも記載されているように、k6 では一つのインスタンスでも大量のリクエストを実現できることも良い点です。
> Unless you need more than 100,000-300,000 requests per second (6-12M requests per minute), a single instance of k6 is likely sufficient for your needs. Read on to learn about how to get the most load from a single machine.

### 今回構築したデグレ検知の仕組みについて
開発したコードをリポジトリに push し、特定のブランチにマージされた際に、Github Actions の Workflow がトリガーされ、k6 を実行します。（トリガーするタイミングは、それぞれプロダクトの開発フローに合わせて設定するとよいと思います。本番リリース前のプルリクエストが作成されたタイミングでもよさそうです。）

アプリケーションと負荷試験用（シナリオ）のリポジトリは分けているため、アプリケーションリポジトリ側から Repository Dispatch を実行し、負荷試験用リポジトリの Workflow で受け取り負荷試験を実施する形をとっています。

#### アプリケーションリポジトリ
```
~~~
jobs:
  trigger:
    runs-on: ubuntu-latest

    steps:
    - name: Repository Dispatch
      uses: peter-evans/repository-dispatch@v1
      with:
        token: ${{ secrets.PAT_XXXX_LOAD_TEST }} # パーソナルアクセストークン
        repository: xxxx/load-test
        event-type: dev-update
```

#### 負荷試験用リポジトリ
```
name: Trigger on dev-update
on:
  repository_dispatch:
    types: [dev-update]
jobs:
  load:
    runs-on: self-hosted
    steps:
~~~
```

負荷試験は多くのコンピュートリソースと実行時間が必要なケースがあるため、Github Actions はセルフホストランナーで動かすことが望ましく、負荷試験をかける側の Workflow はセルフホストランナーで実行させています。
※ 負荷試験をかける側（セルフホストランナー）は、プロダクトに合わせてアプリケーションと近いところに置いています

最終ステップで、負荷試験を実施し、デグレを検知した場合は Slack に通知を飛ばします。k6 では [Thresholds](https://k6.io/docs/using-k6/thresholds/) を用いて、プロダクトの SLO を適用することでパフォーマンスを測定し、品質を担保することが可能です。
>Often, testers use thresholds to codify their SLOs. 
```
export const options = {
  thresholds: {
    http_req_failed: ['rate<0.01'], // http errors should be less than 1%
    http_req_duration: ['p(95)<200'], // 95% of requests should be below 200ms
  },
};
```


また、k6 は負荷試験のサマリーをさまざまな方法で出力することができ、json ファイルで出力したものを負荷試験用リポジトリに保存するようにしました。そうすることで、同じディレクトリ（階層）に考察や改善した内容をレポートで残したり、後で別のモニタリングツールと見合わせて改善に使うことができます。
※ 他にも Elasticsearch や Datadog、NewRelic、Prometheus 等にも出力が可能です。 
https://k6.io/docs/get-started/results-output/

以下に流れをまとめます。

## 負荷試験・デグレ検知の流れ
1. 開発・変更した機能を Application リポジトリへ Push
2. Application リポジトリで特定のブランチ（feature -> dev）にマージ
3. Load Test リポジトリに対して Repository Dispatch が実行される
4. Load Test リポジトリの workflow がトリガーされ負荷試験を実行（セルフホステッドランナーで実行）
    - 負荷試験が実行される前にデプロイが完了しているか（Podの起動）をチェックするステップも間に入れています
5. シナリオに設定した Thresholds により、デグレ検知（SLO違反）をした場合は Slack に通知を飛ばす
6. k6 のサマリーレポート（jsonファイル）を Load Test リポジトリの results/ ディレクトリへ保存（例: results/202312011500.json） 

![AdventCalendar2023.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621357/52ac086f-92e5-3e7a-ecba-29fca4d34501.png)
※ アプリケーションのデプロイや構成など一部簡略化しています


# 最後に
負荷試験を自動化し、手軽に開発中の機能のパフォーマンス測定を実施できる環境を構築してみました。
実現方法は様々あり改善の余地はあるとは思いますが、（仕組みがあることを忘れるくらい）意識することなく開発の信頼性を担保できることを目標に取り組んでみました。

ちなみに、負荷試験用リポジトリでは Workflow Dispatch でシナリオと負荷を選択し任意のタイミングで負荷試験を実施できるようにもしてあります。
![advent-calendar2023.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621357/f1440767-1162-d598-e28c-6f444298fadc.jpeg)

簡単ではありましたが、どなたかの参考になれば幸いです。