k6のスクリプトからAWSのAPIを利用する

こんばんは、最近ヴァンパイアサバイバーズにハマって睡眠時間がゴリゴリ減ってます。

最近、k6で負荷テストをする機会があり、AWSのAPIをk6のスクリプトから呼び出した方法について記録しておきます。

k6のスクリプトからAWSのAPIを利用する

k6とは、オープンソースの負荷テストツールです。テストスクリプトをjavascriptで記述することができ、dockerやCLIから負荷テストを実行することができます。

k6には、テストスクリプトの中で呼び出せるjavascriptのモジュールがいくつも用意されています。
AWSのAPIを呼び出せるawsというライブラリもあり、S3であれば S3Client、SecretsManagerであればSecretsManagerClientなどいくつかあります。
ただし、用意されているAWSサービスの数は非常に少ないです。(そもそも多分AWSのAPIを大量に呼び出したい場面が世間的にそんなにないのでしょう)

僕は最近、DynamoDBのスロットリングエラーについて調べていて、k6からDynamoDBに対してデータの書き込みを大量に行いたいと考えていたのですが、DynamoDBも同様に用意されていませんでした。
そこで、自前でAWSへのHTTPリクエストを作り上げてリクエストを送信するようにしました。
k6のスクリプトの中でHTTPリクエストを送るためにはk6/httpというモジュールが用意されているのでこれが使えます。
また、HTTPリクエストをAWSに正しく送るためには署名周りの設定がとても面倒なのですが、k6ではSignatureV4というドンピシャなライブラリが用意されていたのでこいつを使ってみました。

const signer = new SignatureV4({
    service: 'dynamodb',
    region: "ap-northeast-3",
    credentials: {
        accessKeyId: "AKIXXXXXXXXX",
        secretAccessKey: "XXXXXXXXXXXXX",
    },
})

const signedRequest = signer.sign(
    {
        method: 'POST',
        protocol: 'https',

        hostname: 'dynamodb.ap-northeast-3.amazonaws.com',

        path: '/',
        headers: {
            "Content-Type": "application/x-amz-json-1.0",
            "X-Amz-Target": "DynamoDB_20120810.PutItem"
        },
        body: JSON.stringify(data)
    },
)

const res = http.post(signedRequest.url, JSON.stringify(data),{
    headers: signedRequest.headers
})

ポイントとしては、signer.sign()の際に、リクエストのプロトコル/メソッド/ボディ/ヘッダーを全て含めなければなりません。http.post()の際の引数のheadersに追加で設定してもAWS側でリクエストを受け付けてもらえません。

また、DynamoDBの場合は、独自にContent-Typeヘッダーにapplication/x-amz-json-1.0を付与したり、どういうような操作をするかをX-Amz-Targetヘッダーに付与したりする必要がありました。
後者になかなか気づけず時間を溶かしてしまいがちですよね

https://docs.aws.amazon.com/amazondynamodb/latest/APIReference//API_PutItem.html#API_PutItem_Examples

あと、もしかしたら、SignatureV4の初期化は一度のみで良かったかもしれません

ソースコード

import http from 'k6/http'
import { check } from 'k6';
import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';

import { SignatureV4 } from 'https://jslib.k6.io/aws/0.9.0/aws.js'

export default function () {

    const data = {
        "TableName": "books",
        "Item": {
            "book_id": {
                "S": "a"
            },
            "user_id": {
                "S": randomString(20)
            },
            "id": {
                "S": randomString(20)
            }
        }
    }

    const signer = new SignatureV4({
        service: 'dynamodb',
        region: "ap-northeast-3",
        credentials: {
            accessKeyId: "AKIXXXXXXXXX",
            secretAccessKey: "XXXXXXXXXXXXX",
        },
    })

    const signedRequest = signer.sign(
        {
            method: 'POST',
            protocol: 'https',

            hostname: 'dynamodb.ap-northeast-3.amazonaws.com',

            path: '/',
            headers: {
                "Content-Type": "application/x-amz-json-1.0",
                "X-Amz-Target": "DynamoDB_20120810.PutItem"
            },
            body: JSON.stringify(data)
        },
    )

    const res = http.post(signedRequest.url, JSON.stringify(data),{
        headers: signedRequest.headers
    })

    check(res, {
        'response code was 200': (res) => res.status == 200,
    });
}

ご利用は自己責任で。

tech  k6  AWS 

See also