Upgrade to Pro — share decks privately, control downloads, hide ads and more …

スタブサーバ自動生成ツール 〜負荷試験をもっと楽に〜

teru0x1
December 13, 2022

スタブサーバ自動生成ツール 〜負荷試験をもっと楽に〜

teru0x1

December 13, 2022
Tweet

More Decks by teru0x1

Other Decks in Technology

Transcript

  1. © DMM
    © DMM
    スタブサーバ自動生成ツール
    〜負荷試験をもっと楽に〜
    DMM.go #5

    View Slide

  2. © DMM
    2
    小野 輝也
    合同会社DMM.com SRE
    2021年新卒入社
    Twitter: @teru0x1
    https://cha-shu00.hatenablog.com/

    View Slide

  3. 今日のテーマ
    負荷試験

    View Slide

  4. © DMM
    想定する負荷試験対象
    4
    ● ベンチマークサーバ
    ○ リクエストをたくさん投げる
    ● 試験対象システム
    (SUT: System Under Test)
    ● 外部API
    ○ 別の組織が管理してるAPI
    ○ 勝手に変更できない
    Amazon Web Services、“Powered by AWS”ロゴ、[およびかかる資料で使用されるその他のAWS商標] は、米国その他の諸国における、Amazon.com Inc.またはその関連会社の商標です。

    View Slide

  5. © DMM
    想定する負荷試験対象
    5
    ● ベンチマークサーバ
    ○ リクエストをたくさん投げる
    ● 試験対象システム
    (SUT: System Under Test)
    ● 外部API
    ○ 別の組織が管理してるAPI
    ○ 勝手に変更できない
    このまま負荷試験をするとどうなるか?
    → 外部APIに猛烈な負荷がかかる
    → 外部APIの死、課金増 などが発生

    View Slide

  6. © DMM
    負荷試験アーキテクチャ
    6

    View Slide

  7. © DMM
    7
    外部APIと同等の振る舞いを
    するサーバ
    →スタブサーバ
    負荷試験アーキテクチャ

    View Slide

  8. © DMM
    スタブサーバ
    • 外部のAPIと同等の振る舞いをするサーバ
    • 外部のAPI数が少なくて単純、OpenAPIなどの定義がある場合
    • →普通に手作業で作ってもそこまで大変でない
    • 外部のAPI数が多い(10以上)、複雑、ドキュメントも充実してない場合
    • 試験対象のコード(つまり外部APIに対するクライアント側)を見て実装する
    • →つらい
    • 外部APIに対するリクエスト/レスポンスをプロキシでキャプチャし、そこから生成
    • →業務ではmitmproxyのキャプチャからスクリプトで生成した
    • https://mitmproxy.org/
    • →行き当たりばったり
    8

    View Slide

  9. gstbgen

    View Slide

  10. © DMM
    10
    gstbgen := “go stub server generator”
    スタブサーバのGoコードをいい感じに生成
    負荷試験の面倒な作業を大幅短縮!!!
    https://github.com/teru01/gstbgen
    で公開中

    View Slide

  11. © DMM
    gstbgenがやること
    11
    ● SUTと外部APIの間に挟まるプロキシとなる
    ● SUTと外部APIとのリクエスト・レスポンスを記録する
    ● 記録が終わると、スタブサーバのGoコードを出力する

    View Slide

  12. © DMM
    使い方(1/4)
    12
    1. 適当なサーバにgstbgenを起動し、
    SUTのフォワードプロキシとして
    指定する
    $ export http_proxy=http://xx.xx.xx.xx:yyyy
    $ export https_proxy=http://xx.xx.xx.xx:yyyy

    View Slide

  13. © DMM
    使い方(2/4)
    13
    2. ベンチマークサーバで負荷試験
    シナリオを負荷にならない程度に
    流す
    gstbgenにリクエスト/レスポンス
    の記録が溜まり始める

    View Slide

  14. © DMM
    使い方(3/4)
    14
    3. gstbgenを終了させると、記録
    したリクエスト・レスポンスから
    スタブサーバのコードが出力される

    View Slide

  15. © DMM
    使い方(4/4)
    15
    4. 生成されたコードでスタブサーバ
    を起動し、SUTで外部APIとして
    使う接続先をスタブサーバに向ける
    あとは負荷試験するだけ

    View Slide

  16. © DMM
    ポイント1: いい感じのコード生成
    • スタブサーバを生成するのではなく、スタブサーバのGoコードを生成
    する
    • 自由に編集できる
    • 記録したHTTPのフローをそのまま再現するだけでは不適切な場合も
    あるため
    • コード生成にはdave/jenniferを利用
    16
    If(Id("i").Op("==").Id("j")).Block(
    Return(Id("i")),
    )
    if i == j {
    return i
    }

    View Slide

  17. © DMM
    コード生成
    17
    GET http://external.com/foo
    mux.HandleFunc("/foo", func(rw
    http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
    rw.WriteHeader(200)
    fmt.Fprint(rw, "{\"contents\":\"hello\"}")
    return
    }
    })
    リクエスト
    レスポンス
    200 OK
    {“contents”: “hello”}

    View Slide

  18. © DMM
    全体のコード生成イメージ
    host path method
    query
    param
    reqbody response
    host path method
    query
    param
    reqbody response
    host path method
    query
    param
    reqbody response
    host
    path method
    query
    param
    reqbody response
    method
    query
    param
    reqbody response
    path method
    query
    param
    reqbody response
    mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
    body, _ := stringify(r.Body)
    if r.Method == "GET" {
    if q, _ := stringifyUrlValues(r.URL.Query()); q == "{}" {
    if body == "{\"token\":\"abc\"}" {
    rw.WriteHeader(200)
    fmt.Fprint(rw, "{\"foo\":\"bar\"}")
    return
    }
    }
    }
    if r.Method == "POST" {
    if q, _ := stringifyUrlValues(r.URL.Query()); q == "{}" {
    if body == "{\"token\":\"abc\"}" {
    rw.WriteHeader(200)
    fmt.Fprint(rw, "{\"foo\":\"bar\"}")
    return
    }
    }
    }
    })
    mux.HandleFunc("/hoge", func(rw http.ResponseWriter, r *http.Request) {
    body, _ := stringify(r.Body)
    if r.Method == "GET" {
    if q, _ := stringifyUrlValues(r.URL.Query()); q == "{}" {
    if body == "{\"token\":\"abc\"}" {
    rw.WriteHeader(200)
    fmt.Fprint(rw, "{\"foo\":\"bar\"}")
    return
    }
    }
    }
    })
    溜まっていく
    深さ優先でツリーを巡回して生成

    View Slide

  19. © DMM
    ポイント2: HTTPS対応
    • 普通、プロキシでHTTPS通信の中身は取得できない
    • 通信はE2Eで暗号化されている
    • 暗号化された通信内容は取得できる
    • どうするか: サーバー証明書を偽造する
    • TLSをgstbgenで終端させて、SUTには偽の証明書を返す
    • 偽の証明書をSUTが信頼すれば良い
    • gstbgen側に持たせたCA証明書を信頼させる
    19

    View Slide

  20. © DMM
    20
    1 2 3
    4 5

    View Slide

  21. © DMM
    その他話してないこと
    • コマンドラインツール製作にはurfave/cli/v2を使用
    • 引数の処理、自動help生成などが便利
    • プロキシのベースとしてはelazarl/goproxyを使用
    • TLS、 証明書周りは標準ライブラリに入っている
    • crypto/tlsやcrypto/x509など
    • GoではOpenSSLに依存せず、自前実装している
    • そのためGoのスクリプトだけで鍵ペアを生成できる
    21

    View Slide

  22. © DMM
    おわりに
    • 負荷試験をはじめとするシステムの解析に使えるツールをGoで作成
    • Google SREのプラクティス: 50%ルール
    • 業務時間の50%はソフトウェアエンジニアリングに使おう
    • パワーではなくてツールで解決することも重要
    22

    View Slide

  23. © DMM
    We are hiring!
    大規模なサービスや新事業の立ち上げなど規模の大小、ジャンル・フェーズ
    が存在するDMM.comでSREを実践してみませんか?
    https://dmm-corp.com/recruit/engineer/8/

    View Slide

  24. Thank You !

    View Slide