k6でbasic認証をかけているサイトに負荷テストを行う

大きめの機能のリリース前やインフラ構成を変更する時に、負荷テストを行うことは多いでしょう。
その際、staging環境やリリース前のプロダクトに行う場合、basic認証がかけられているサイトに対して負荷テストを行う必要がある場合もあります。

[JMeter](https://github.com/apache/jmeter)、[Locust](https://github.com/locustio/locust)をはじめ様々なオープンソースの負荷テストツールが存在しますが、今回は[k6](https://github.com/grafana/k6)を利用してbasic認証のサイトに負荷テストをしてみようと思います。

## k6のセットアップ

今回はMac OS(Apple M1 Pro)でセットアップを行います。

### k6のインストール

Macの場合、brewを利用して一行でインストールできます。

```sh
brew install k6
```

インストールされたバージョンは`k6 --version`で確認できます。

```sh
k6 --version
k6 v0.55.0 (go1.23.3, darwin/arm64)
```

### k6の作業フォルダの作成

適当なフォルダを作成し、`k6 new`を実行し`script.js`を作成しましょう。
既にscript.jsが存在する場合はそれを持ってくるでも構いません。

## script.jsを編集する

初期に作成されるscript.jsはコメントアウトを除くと以下のようになります。

```javascript:script.js
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  vus: 10,
  duration: '30s',
)

export default function() {
  http.get('https://test.k6.io');
  sleep(1);
}
```

10人のユーザーが、30秒の間1秒間隔で`https://test.k6.io`にアクセスするという記述がされている、ということが書かれています。
このコードを拡張していきます。

### basic認証を通過できるようにする

認証用のヘッダーを作成し、それを`http.get`の第二引数に渡すことで認証を通過できるようにします。
今回はユーザー名を`hoge`、パスワードを`fuga`と仮置きして、以下の値を与えると良いです。

```
{{Authorization: `Basic (hoge:fugaをbase64でエンコードした値)`}}
```

base64のエンコーディングは`k6/encoding`から`encoding.b64encode`関数を利用します。
ここまで踏まえると、以下のコードになります。


```diff_javascript:script.js
import http from 'k6/http';
import { sleep } from 'k6';
+import encoding from 'k6/encoding';

export const options = {
  vus: 10,
  duration: '30s',
};

export default function() {
+ const credentials = `hoge:fuga`;
+ const encodedCredentials = encoding.b64encode(credentials);

+ const headers = {
+   Authorization: `Basic ${encodedCredentials}`,
+ };

- http.get('https://test.k6.io');
+ http.get('https://test.k6.io'), { headers };

  sleep(1);
}
```

### .env管理にする

コード管理する場合、basic認証の情報をコードに直書きするのは避けたい場合が大半だと思います。
.env管理にしてしまいましょう。
sleepの引数、vus、duration、テストをするURLもこのタイミングで.env管理にします。

```diff_javascript:script.js
import http from 'k6/http';
import { sleep } from 'k6';
import encoding from 'k6/encoding';

+const {
+ BASIC_USERNAME: username,
+ BASIC_PASSWORD: password,
+ SLEEP_TIME: sleepTime,
+ VUS: vus,
+ TARGET_URL: targetUrl,
+ DURATION: duration
+} = __ENV;

export const options = {
- vus: 10,
- duration: '30s',
+ vus,
+ duration,
};

export default function() {
- const credentials = `hoge:fuga`;
+ const credentials = `${username}:${password}`;
  const encodedCredentials = encoding.b64encode(credentials);

  const headers = {
    Authorization: `Basic ${encodedCredentials}`,
  };

- http.get('https://test.k6.io', { headers });
+ http.get(targetUrl, { headers });

- sleep(1);
+ sleep(sleepTime);
}
```

.envは以下のように記述します（値は初期値をそのまま入れているので、各々必要な値にしてください）。

```sh:.env
BASIC_USERNAME='hoge'
BASIC_PASSWORD='fuga'
SLEEP_TIME=1
VUS=10
TARGET_URL='https://test.k6.io'
DURATION='30s'
```

実行時には.envの値をk6に渡す必要があるため、exportとxargsコマンドを活用して以下のように記述すると良いです。
コマンドにしてしまってもいいでしょう。

```sh
export $(cat .env | xargs) && k6 run script.js
```

### エラー処理を追加する

リクエストの状況を見て途中終了しやすいようにエラーが出たらメッセージを出すようにします。
期待するステータスコードは200とした場合、それ以外のステータスコードが返却された場合メッセージを出して欲しいので200以外の時は`console.error`でステータスコードを取得するようにします。

```diff_javascript:script.js
// 前略

export default function() {
  const credentials = `${username}:${password}`;
  const encodedCredentials = encoding.b64encode(credentials);

  const headers = {
    Authorization: `Basic ${encodedCredentials}`,
  };

- http.get(targetUrl, { headers });
+ const response = http.get(targetUrl, { headers });

+ if (response.status !== 200) {
+   console.error(`Request to ${targetUrl} failed with status ${response.status}`);
+ }

  sleep(sleepTime);
}
```

## まとめ

簡単なjavascriptファイルに少々変更を加えるだけで、k6を利用した負荷テストの設定を拡張しBasic認証を扱えるようにしました。
また、.envに設定を逃すことで、スクリプトを変えずに柔軟な設定変更をできるようにしました。
k6は非常に拡張性の高い負荷テストツールなので、是非必要なテストケースに応じたスクリプトを作成してみてください。