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

Kubernetes x k6 で負荷試験基盤を開発して 負荷試験を民主化した話 / Kube...

Sansan R&D
February 05, 2025

Kubernetes x k6 で負荷試験基盤を開発して 負荷試験を民主化した話 / Kubernetes x k6

■ イベント
Kubernetes Meetup Tokyo #69
https://k8sjp.connpass.com/event/341934/

■ 発表者
技術本部 研究開発部 Architectグループ 藤岡 大輝

■ 研究開発部 採用情報
https://media.sansan-engineering.com/randd

■ Sansan Tech Blog
https://buildersbox.corp-sansan.com/

Sansan R&D

February 05, 2025
Tweet

More Decks by Sansan R&D

Other Decks in Technology

Transcript

  1. 藤岡 ⼤輝 Sansan株式会社 技術本部 研究開発部 Architectグループ 2022年 Sansan 新卒⼊社 /

    DevOps エンジニア / 兵庫県在住 負荷試験基盤の設計・推進やCI/CD共通化、EKSのコスト最適化 などを通して、開発⽣産性の向上やコスト削減などに取り組んで いる。 @fhiroki
  2. 会社概要 2 本社 神⼭ラボ Sansan Innovation Lab 社 名 Sansan株式会社

    所在地 本社 東京都渋⾕区桜丘町1-1 渋⾕サクラステージ 28F グループ 会社 Sansan Global Pte. Ltd.(シンガポール) Sansan Global Development Center, Inc.(フィリピン) Sansan Global (Thailand) Co., Ltd.(タイ) ログミー株式会社 株式会社ダイヤモンド企業情報編集社 クリエイティブサーベイ株式会社 株式会社⾔語理解研究所 従業員数 1,789名(2024年11⽉30⽇時点) 2007年6⽉11⽇ 設 ⽴ ⽀店:⼤阪、名古屋、福岡 サテライトオフィス:徳島、京都、新潟 拠 点 寺⽥ 親弘 代表者
  3. 働き⽅を変えるDXサービス 請求 ⼈や企業との出会いをビジネスチャンスにつなげる「働き⽅を変えるDXサービス」を提供 ビジネスフローにおけるさまざまな分野でサービスを展開 名刺管理 名刺DX 営業 営業DX 契約 契約DX

    経理DX 個⼈向けDX 法⼈向けDX 必要な情報を すぐに⾒つけられる 情報の管理がしやすく すぐに共有できる 情報を分析・活⽤しやすく データに基づいた判断ができる SansanのDXサービスの活⽤で変わる働き⽅
  4. プラットフォームチームについて メンバ - 現在4名で10年⽬未満の若⼿が中⼼ 主な業務 インフラ/CICDなどの共通化や⾃動化を通じた⽣産性向上の⽀援しています。 - 部署内のプラットフォームCircuitの企画・開発・運⽤ - EKSを利⽤

    - 部署内の共通CI/CDのワークフローの企画・開発・運⽤ - GitHub Actionsなど R&Dのアプリケーション基盤 “Circuit”について https://speakerdeck.com/sansan_randd/about-circuit-r-and-ds-application- platform
  5. 前提 - 複数のプロダクトに対してAPI形式で様々な機能を提供しており、各事業部と要 件を取り決めているため、負荷試験を実施する機会が多い。 課題 - 研究員側で負荷試験を実施するハードルがある - 基本的にエンジニアが負荷試験をやっている -

    負荷をかける側の限界が、ローカルPCのスペックに依存する - 負荷試験に関する⽅針が定まっていない - 負荷試験ツールが統⼀されていない(k6, locust, vegeta …) - 結果をまとめる場所が散逸的(Notion, PR, Confluence …) 既存の課題
  6. - 導⼊が簡単 - テンプレートから⽣成するだけ - 実⾏が簡単 - GitHub Actions で1クリックで実⾏可能

    - 定期実⾏, プルリクエストのマージ時に⾃動実⾏も可能 - ツールをインストールするなどの環境構築がほぼ不要 - 実⾏者の環境に依存しない - シナリオファイルがGitHub上に保存されるので、必ず後に残る。 なにがうれしいの?
  7. - パフォーマンスが良い - クラウド上で実⾏するので、メモリ使⽤量が低いのはコストに効く。 - 単⼀プロセスで多くの負荷をかけることができる。 - シナリオ実装⾔語がJavaScript - 多くの開発者にとって馴染みがある

    - Datadogのインテグレーションがある - k8s の operator が Grafana 公式サポート - Gatling, Locust, vegeta なども operator はあるが、個⼈開発のもの なぜ k6 か
  8. TestRun apiVersion: k6.io/v1alpha1 kind: TestRun metadata: name: k6-sample spec: cleanup:

    post parallelism: 4 script: configMap: name: k6-test file: test.js runner: image: <custom-image> 試験終了後、リソースが削除される 起動するPod数 どのシナリオを使うか定義する
  9. シナリオをどう保存するか? - ConfigMap - VolumeClaim - LocalFile ← 消去法でこいつになった の3つの選択肢がある

    シナリオの管理⽅法 https://grafana.com/docs/k6/latest/set-up/set-up-distributed-k6/usage/executing-k6-scripts-with-testrun-crd/
  10. ConfigMap - Good - ⼀番シンプル - Bad - 1MB以上⼊れられない -

    git lfs で巨⼤なデータを管理している場合に対応できない - 複数ファイルからなる ConfigMap は作れない、と勘違いしていた - フラットな構造であれば以下で作れる(ネストは不可) シナリオの管理⽅法 $ kubectl create configmap scenarios-test --from-file ./test spec: script: configMap: name: k6-test file: test.js
  11. ConfigMap - Good - ⼀番シンプル - Bad - 1MB以上⼊れられない -

    git lfs で巨⼤なデータを管理している場合に対応できない - 複数ファイルからなる ConfigMap は作れない、と勘違いしていた - フラットな構造であれば以下で作れる(ネストは不可) シナリオの管理⽅法 $ kubectl create configmap scenarios-test --from-file ./test spec: script: configMap: name: k6-test file: test.js
  12. VolumeClaim - Good - ファイルサイズ、ディレクトリ構造に制限がない - Bad - システムが複雑になってしまう -

    init container でシナリオをPVに書き込む必要がある - クラスタ上で Private Repository からシナリオを clone する必要がある - 負荷試験終了時にPVを削除する必要がある シナリオの管理⽅法 spec: script: volumeClaim: name: stress-test-volumeClaim file: test.js
  13. VolumeClaim - Good - ファイルサイズ、ディレクトリ構造に制限がない - Bad - システムが複雑になってしまう -

    init container でシナリオをPVに書き込む必要がある - クラスタ上で Private Repository からシナリオを clone する必要がある - 負荷試験終了時にPVを削除する必要がある シナリオの管理⽅法 spec: script: volumeClaim: name: stress-test-volumeClaim file: test.js
  14. LocalFile - Good - ファイルサイズ、ディレクトリ構造に制限がない - VolumeClaim に⽐べたらシンプル - 負荷試験終了後の掃除をする必要がない

    - Bad - イメージを管理する必要がある - 公式には VolumeClaim を推奨されている... シナリオの管理⽅法 spec: script: localFile: /test/test.js runner: image: <custom-image>
  15. LocalFile - Good - ファイルサイズ、ディレクトリ構造に制限がない - VolumeClaim に⽐べたらシンプル - 負荷試験終了後の掃除をする必要がない

    - Bad - イメージを管理する必要がある - 公式には VolumeClaim を推奨されている... シナリオの管理⽅法 spec: script: localFile: /test/test.js runner: image: <custom-image>
  16. - k6 は handleSummary() でサマリデータを受け取れる(参考) - それをいい感じに加⼯して、Slack に通知したい 通知⽤APIを⽴てる import

    http from 'k6/http'; export default function () { http.get('https://test.k6.io'); } export function handleSummary(data) { return { 'summary.json': JSON.stringify(data), }; }
  17. Datadog 設定⽅法 spec: arguments: -o output-statsd runner: image: <custom-image> env:

    - name: K6_STATSD_ENABLE_TAGS value: "true" - name: K6_STATSD_ADDR value: "datadog.kube-system:8125" 前提 Datadog agent が各ノードに⼊っている 設定⽅法 1. StatsD の k6 extension を⼊れる a. (元々は本体に⼊っていたが、メンテナンス性の観点から削除された) 2. TestRun の runner の環境変数を設定する 3. Datadog で k6 のインテグレーションをインストールする
  18. 共通化することで、 - 各リポジトリに導⼊するコストが低くなる - workflowの重複を避けられる - リリース後の変更がしやすい - バグ修正やバージョン更新が⼀括でできる -

    共通のナレッジがたまる reusable workflow name: Run Load Testing on: workflow_dispatch: inputs: scenario_file: type: choice description: "Scenario file to run" required: true default: "test01.js" options: - "test01.js" - "test02.js" jobs: run-load-test: uses: <path/to/reusable_workflow> with: service_name: "sample" namespace: "samples" scenario_file: ${{ inputs.scenario_file }} working_dir: "load_testing"
  19. - ケース1: APIキーを使いたい - ケース2: Cloud Run に対して負荷をかけたい - ケース3:

    負荷試験実⾏時のみ ECS, SageMaker Endpoint を⽴ち上げたい ユーザからのリクエスト
  20. - 機密情報なので、Secrets Manager で管理する必要がある。 - 当初は k6 の awsライブラリ を利⽤した

    - しかし、AWS_SECRET_ACCESS_KEY を直接渡す必要があり、 GitHub Actions側で特定のロールから⼀時クレデンシャルを発⾏し、key を環境変数経由で Pod に渡すようにした。 - ↑ ⾮常にいけてないが、初回リリースはこれでいった ケース1: APIキーを使いたい
  21. k6 extension (xk6) で AWS SDK ラップすればいいじゃん - k6 を簡単にカスタムビルドして、拡張機能を組み込んだバイナリを作成

    できる - xk6 build latest –with github.com/grafana/[email protected] - → xk6-sql を組み込んだ k6 のバイナリが⽣成される - go⾔語で書ける ケース1: APIキーを使いたい
  22. ケース1: APIキーを使いたい package aws import ( "context" "log" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/aws/aws-sdk-go/aws"

    ) func (a *AWS) GetSecretValue(secretName) string { ctx := context.Background() cfg := a.getConfig(ctx) svc := secretsmanager.NewFromConfig(cfg) input := &secretsmanager.GetSecretValueInput{ SecretId: aws.String(secretName), VersionStage: aws.String("AWSCURRENT"), } result, err := svc.GetSecretValue(ctx, input) if err != nil { log.Fatal(err.Error()) } return *result.SecretString } package aws import ( "go.k6.io/k6/js/modules" ) func init() { modules.Register("k6/x/aws", new(AWS)) } type AWS struct {} extension 側
  23. ケース1: APIキーを使いたい シナリオ側 import aws from "k6/x/aws"; export default function

    () { const secret = JSON.parse(aws.getSecretValue("path/to/secret")); }
  24. 設定⼿順 1. SecretsManager のアクセス権を持つ IAM Role を作成する 2. 1. の

    IAM を紐づけた ServiceAccount を作成する 3. TestRun の runner に 2. の ServiceAccount を設定する ケース1: APIキーを使いたい spec: runner: serviceAccountName: load-testing-sa
  25. 認証の流れ 1. ⾃環境のIdプロバイダ(IdP)に対して 認証しクレデンシャルを取得 2. アプリケーションは Google Cloud の Security

    Token Service (STS) に 1. のクレ デンシャルを渡し、問題なければアクセストークンが発⾏される。 3. 2. のアクセストークンを使って、Google Cloud の Service Accout になりすまし てリソースにアクセスする。 Workload Identity連携 1 2 3 参考: https://zenn.dev/ohsawa0515/articles/gcp-workload-identity-federation
  26. 設定⽅法 1. Google Cloud側で必要な権限を持った Service Account を設定する 2. Google Cloud側で

    Workload Identity Pool/Provider を設定する 3. 認証構成ファイルを取得し、ConfigMap に設定する 4. 3. のConfigMap を runner に volumeMounts する ケース2: Cloud Run に対して負荷をかけたい spec: runner: env: - name: GOOGLE_APPLICATION_CREDENTIALS value: "/var/tmp/config-aws-eks-provider.json" volumeMounts: - name: config mountPath: /var/tmp volumes: - name: config configMap: name: <config_map_name>
  27. 設定⽅法 5. k6 extension で Google Cloud の Id Token

    を⽣成できるようにする。 - 参考: https://cloud.google.com/docs/authentication/get-id-token?hl=ja#go 6. 5. により⽣成した token を header に⼊れて Cloud Run にリクエストする。 ケース2: Cloud Run に対して負荷をかけたい
  28. 背景 - 負荷試験の対象となるサーバが ECS - SageMaker Endpoint に依存している - コストの観点から負荷試験をする時のみ⽴ち上げたい

    解決⽅法 - 2つの k6 extension を作る - ECS のタスク数を更新する extension - SageMaker Endpoint を作成, 削除する extension ケース3: 負荷試験実⾏時のみECS, SageMaker Endpointを⽴ち上げたい
  29. ケース3: 負荷試験実⾏時のみECS, SageMaker Endpointを⽴ち上げたい import aws from "k6/x/aws"; export function

    setup() { aws.scaleECS("some-ecs-cluster", "some-ecs-service", 10); aws.createEndpointConfig("some-endpoint-config", "some-model", 5); aws.createEndpoint("some-endpoint", "some-endpoint-config"); } export default function () { // 負荷をかける処理 } export function teardown() { aws.scaleECS("some-ecs-cluster", "some-ecs-service", 0); aws.deleteEndpointConfig("some-endpoint-config"); aws.deleteEndpoint("some-endpoint"); } - setup stage で ECS を⽴ち上げ、Endpoint を作成する - teardown stage でそれらを削除する - (Endpoint config を作っているのは Endpoint を作成するのに config が必須なため)
  30. - 負荷試験のリードタイムを約75%削減 - 基盤を使う場合と使わない場合で⽐較実験を⾏った - 研究員側で負荷試験ができるようになったので、エンジニアへの依存を減らしリリース サイクルを改善できた - ユーザ間で負荷試験に関するナレッジが共有されるようになった -

    ⾃然と新しいシステムでも使われる状況がつくれた - 負荷試験に対するハードルを下げることができた - リリース前だけではなく、こまめに負荷試験を回す⼈も出てきた - 1⽇平均20回程度使われている - ⇨ 早い段階でテストを実施することで⼿戻りが少なくなる - k8s基盤に変更を加える際の、各アプリの動作検証がしやすくなった - 負荷試験を当たり前にする⽂化を醸成できた 負荷試験基盤により変わったこと
  31. 効果は、処理が捌けない系のインシデントを防げていること、確認の⼯数が⼩さくなっていることである 活⽤例 - 導⼊予定のアルゴリズムの処理速度が問題ないことを確認できた → 実際に問題なく稼働している - ユーザ増に伴う流⼊増時に、滞留が発⽣しそうなことがわかった → 先んじて対策を検討開始した

    実際のユーザからのフィードバック 項⽬ 負荷試験基盤のないとき 負荷試験基盤のあるとき 属⼈性 属⼈的で個別の理解が必要である 基盤ユーザなら誰でも使える 記録 負荷試験の記録が残るかどうかは作業者 次第である 作業者に関係無く記録が残る 実⾏しやすさ ⼤変。設定や環境構築を全て⾏ってから 実⾏できる 10分程度。設定ファイルの⽤意とブラウザか らの操作で実⾏できる
  32. - karpenter の disruption により Pod が突然死 - karpenter.sh/do-not-disrupt: "true"

    を設定する - 別環境の Internal な LB に対して負荷をかけたい - VPC ピアリングを設定する その他の⼩話