Slide 1

Slide 1 text

Sansan株式会社 部署 名前 Kubernetes x k6で 負荷試験基盤を開発して 負荷試験を⺠主化した話 研究開発部 Architectグループ 藤岡 ⼤輝

Slide 2

Slide 2 text

藤岡 ⼤輝 Sansan株式会社 技術本部 研究開発部 Architectグループ 2022年 Sansan 新卒⼊社 / DevOps エンジニア / 兵庫県在住 負荷試験基盤の設計・推進やCI/CD共通化、EKSのコスト最適化 などを通して、開発⽣産性の向上やコスト削減などに取り組んで いる。 @fhiroki

Slide 3

Slide 3 text

会社概要 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⽇ 設 ⽴ ⽀店:⼤阪、名古屋、福岡 サテライトオフィス:徳島、京都、新潟 拠 点 寺⽥ 親弘 代表者

Slide 4

Slide 4 text

- 背景 - 負荷試験基盤の詳細 - 使ってもらうための取り組み - まとめ はなすこと

Slide 5

Slide 5 text

背景

Slide 6

Slide 6 text

働き⽅を変えるDXサービス 請求 ⼈や企業との出会いをビジネスチャンスにつなげる「働き⽅を変えるDXサービス」を提供 ビジネスフローにおけるさまざまな分野でサービスを展開 名刺管理 名刺DX 営業 営業DX 契約 契約DX 経理DX 個⼈向けDX 法⼈向けDX 必要な情報を すぐに⾒つけられる 情報の管理がしやすく すぐに共有できる 情報を分析・活⽤しやすく データに基づいた判断ができる SansanのDXサービスの活⽤で変わる働き⽅

Slide 7

Slide 7 text

所属部署について 3つの研究員グループと、1つのエンジニアグループで構成 研究開発部 Architect SocSci Data Analysis Automation 約60⼈ 約10⼈

Slide 8

Slide 8 text

あらゆるプロダクトで重要な役割を担う プロダクト横断なシステムに携わる

Slide 9

Slide 9 text

プラットフォームチームについて メンバ - 現在4名で10年⽬未満の若⼿が中⼼ 主な業務 インフラ/CICDなどの共通化や⾃動化を通じた⽣産性向上の⽀援しています。 - 部署内のプラットフォームCircuitの企画・開発・運⽤ - EKSを利⽤ - 部署内の共通CI/CDのワークフローの企画・開発・運⽤ - GitHub Actionsなど R&Dのアプリケーション基盤 “Circuit”について https://speakerdeck.com/sansan_randd/about-circuit-r-and-ds-application- platform

Slide 10

Slide 10 text

前提 - 複数のプロダクトに対してAPI形式で様々な機能を提供しており、各事業部と要 件を取り決めているため、負荷試験を実施する機会が多い。 課題 - 研究員側で負荷試験を実施するハードルがある - 基本的にエンジニアが負荷試験をやっている - 負荷をかける側の限界が、ローカルPCのスペックに依存する - 負荷試験に関する⽅針が定まっていない - 負荷試験ツールが統⼀されていない(k6, locust, vegeta …) - 結果をまとめる場所が散逸的(Notion, PR, Confluence …) 既存の課題

Slide 11

Slide 11 text

- 研究員側で負荷試験ができる状態にすることで、エンジニアへの依存を 減らしリードタイムを削減したい - k8s基盤側でシステム構成を変更した際に、動作確認とパフォーマンスに 変化がないかを確認したい - k8s基盤を持つチームとしての利点 - 負荷試験に関するノウハウを蓄積したい 負荷試験基盤を作ることで解決したいこと

Slide 12

Slide 12 text

- クラウド上(k8s)で負荷試験が実⾏できること - スケーラブルなので、攻撃側がボトルネックになり得ない - サービスを横断して利⽤できること - 開発者が容易に導⼊できること - 負荷試験のメトリクスがリアルタイムで確認できること - 負荷試験の結果が⻑期間保存されること 負荷試験基盤の要件

Slide 13

Slide 13 text

負荷試験基盤の詳細

Slide 14

Slide 14 text

- k8s基盤上で、k6 により任意のAPIに対して負荷をかけることができる。 - 簡単に導⼊し、簡単に実⾏できる。 - テスト結果のサマリを Slack から確認できる。 - メトリクスの詳細を Datadog から確認できる。 概要

Slide 15

Slide 15 text

- 導⼊が簡単 - テンプレートから⽣成するだけ - 実⾏が簡単 - GitHub Actions で1クリックで実⾏可能 - 定期実⾏, プルリクエストのマージ時に⾃動実⾏も可能 - ツールをインストールするなどの環境構築がほぼ不要 - 実⾏者の環境に依存しない - シナリオファイルがGitHub上に保存されるので、必ず後に残る。 なにがうれしいの?

Slide 16

Slide 16 text

- パフォーマンスが良い - クラウド上で実⾏するので、メモリ使⽤量が低いのはコストに効く。 - 単⼀プロセスで多くの負荷をかけることができる。 - シナリオ実装⾔語がJavaScript - 多くの開発者にとって馴染みがある - Datadogのインテグレーションがある - k8s の operator が Grafana 公式サポート - Gatling, Locust, vegeta なども operator はあるが、個⼈開発のもの なぜ k6 か

Slide 17

Slide 17 text

https://grafana.com/blog/2021/01/27/k6-vs-jmeter-comparison/

Slide 18

Slide 18 text

アーキテクチャ

Slide 19

Slide 19 text

アーキテクチャ ポイント2: シナリオはイメージに含める ポイント1: Operatorパターンを利⽤ ポイント3: 通知⽤のAPIを⽴てておく ポイント4: Datadogでメトリクスを⾒れる

Slide 20

Slide 20 text

- CRD(カスタムリソース定義)とコントローラを⽤いて、特定 アプリケーションの構成管理や運⽤タスクを⾃動化する仕組み Operator パターン https://grafana.com/blog/2022/06/23/running-distributed-load-tests-on-kubernetes/

Slide 21

Slide 21 text

- Grafana 公式サポート - helm でインストール可能 - TestRun(CRD)を適⽤すると k6-operator によりJobが作られる k6-operator

Slide 22

Slide 22 text

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: 試験終了後、リソースが削除される 起動するPod数 どのシナリオを使うか定義する

Slide 23

Slide 23 text

アーキテクチャ ポイント2: シナリオはイメージに含める ポイント1: Operatorパターンを利 ⽤ ポイント3: 通知⽤のAPIを⽴てておく ポイント4: Datadogでメトリクスを⾒れ る

Slide 24

Slide 24 text

シナリオをどう保存するか? - ConfigMap - VolumeClaim - LocalFile の3つの選択肢がある シナリオの管理⽅法 https://grafana.com/docs/k6/latest/set-up/set-up-distributed-k6/usage/executing-k6-scripts-with-testrun-crd/

Slide 25

Slide 25 text

シナリオをどう保存するか? - ConfigMap - VolumeClaim - LocalFile ← 消去法でこいつになった の3つの選択肢がある シナリオの管理⽅法 https://grafana.com/docs/k6/latest/set-up/set-up-distributed-k6/usage/executing-k6-scripts-with-testrun-crd/

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

アーキテクチャ ポイント2: シナリオはイメージに含める ポイント1: Operatorパターンを利 ⽤ ポイント3: 通知⽤のAPIを⽴てておく ポイント4: Datadogでメトリクスを⾒れる

Slide 33

Slide 33 text

- 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), }; }

Slide 34

Slide 34 text

- リリース後の変更がしやすいから - シナリオに通知ロジックを書いてしまうと変更時のコストが⾼い - Slackのトークンを External Secret で管理したいから - 各Pod⽤に Secret を⽤意したくない - Secret のスコープは狭い⽅が安全 なぜAPIに切り出したか

Slide 35

Slide 35 text

Slack通知の詳細

Slide 36

Slide 36 text

Slack通知の詳細 実験時の時間で固定さ れたDatadogダッシュ ボードへのリンク サマリ情報 メタデータ 閾値を満たして いるかどうか 実⾏時のコミットハ ッシュで固定された シナリオファイル

Slide 37

Slide 37 text

Slack通知の詳細 詳細はスレッドに通知(CLIで実⾏した結果と同等)

Slide 38

Slide 38 text

アーキテクチャ ポイント2: シナリオはイメージに含める ポイント1: Operatorパターンを利 ⽤ ポイント3: 通知⽤のAPIを⽴てておく ポイント4: Datadogでメトリクスを⾒れる

Slide 39

Slide 39 text

Datadog 設定⽅法 spec: arguments: -o output-statsd runner: 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 のインテグレーションをインストールする

Slide 40

Slide 40 text

Datadog ダッシュボード

Slide 41

Slide 41 text

問題点 - Datadog のメトリクス保存期間は 15ヶ⽉ 当初は S3 に詳細なレポートをはきだすことも考えていたが、 過去の詳細なメトリクスを⾒たいケースはあまりなく、 Slackのサマリで⼗分という結論に⾄った。

Slide 42

Slide 42 text

使ってもらうための取り組み

Slide 43

Slide 43 text

- Actions で1クリック実⾏ - reusable workflow による共通化 - cookiecutter によるテンプレート化 負荷試験に対するハードルを下げる

Slide 44

Slide 44 text

Actionsで1クリック実⾏ 実⾏するブランチとシナリオファイルを選んでぽちっ

Slide 45

Slide 45 text

- 複数リポジトリから利⽤する workflow を1箇所で管理できる - (2022/12〜)Private Repository の workflow を参照できるようになった reusable workflow https://victoronsoftware.com/posts/github-reusable-workflows-and-steps/

Slide 46

Slide 46 text

共通化することで、 - 各リポジトリに導⼊するコストが低くなる - 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: with: service_name: "sample" namespace: "samples" scenario_file: ${{ inputs.scenario_file }} working_dir: "load_testing"

Slide 47

Slide 47 text

- テンプレートからプロジェクトを作成してくれるコマンドラインツール - 部内のテンプレートは⼤体管理されている - 負荷試験⽤のテンプレート - 負荷試験を開始/停⽌するworkflow - ⼀般的なケースのシナリオ - 平均負荷テスト - 耐久テスト - ストレステスト - … cookiecutter によるテンプレート化

Slide 48

Slide 48 text

1. pip install cookiecutter 2. cookiecutter --directory=”load_testing” 3. シナリオを調整する 4. Actions から実⾏ 使い始めるのに必要なこと

Slide 49

Slide 49 text

- いくらハードルが下がっても、新しいものへの抵抗や、そもそもの認知が なければ使ってもらえない。 広めていく活動 普及活動が必要

Slide 50

Slide 50 text

- 部内で負荷試験が必要なシステムを洗い出して、全てに導⼊しにいった - エンジニア全体の会議で紹介することで、部外に対して宣伝し導⼊⽀援をした - ユーザからのフィードバックに地道に対応した 広めていく活動

Slide 51

Slide 51 text

- ケース1: APIキーを使いたい - ケース2: Cloud Run に対して負荷をかけたい - ケース3: 負荷試験実⾏時のみ ECS, SageMaker Endpoint を⽴ち上げたい ユーザからのリクエスト

Slide 52

Slide 52 text

- 機密情報なので、Secrets Manager で管理する必要がある。 - 当初は k6 の awsライブラリ を利⽤した - しかし、AWS_SECRET_ACCESS_KEY を直接渡す必要があり、 GitHub Actions側で特定のロールから⼀時クレデンシャルを発⾏し、key を環境変数経由で Pod に渡すようにした。 - ↑ ⾮常にいけてないが、初回リリースはこれでいった ケース1: APIキーを使いたい

Slide 53

Slide 53 text

Issue は挙がってる ケース1: APIキーを使いたい https://github.com/grafana/k6-jslib-aws/issues/63

Slide 54

Slide 54 text

k6 extension (xk6) で AWS SDK ラップすればいいじゃん - k6 を簡単にカスタムビルドして、拡張機能を組み込んだバイナリを作成 できる - xk6 build latest –with github.com/grafana/[email protected] - → xk6-sql を組み込んだ k6 のバイナリが⽣成される - go⾔語で書ける ケース1: APIキーを使いたい

Slide 55

Slide 55 text

ケース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 側

Slide 56

Slide 56 text

ケース1: APIキーを使いたい シナリオ側 import aws from "k6/x/aws"; export default function () { const secret = JSON.parse(aws.getSecretValue("path/to/secret")); }

Slide 57

Slide 57 text

設定⼿順 1. SecretsManager のアクセス権を持つ IAM Role を作成する 2. 1. の IAM を紐づけた ServiceAccount を作成する 3. TestRun の runner に 2. の ServiceAccount を設定する ケース1: APIキーを使いたい spec: runner: serviceAccountName: load-testing-sa

Slide 58

Slide 58 text

前提 - Cloud Run へのアクセスに認証が必要 解 - Workload Identity連携を使う ケース2: Cloud Run に対して負荷をかけたい

Slide 59

Slide 59 text

Workload Identity連携 https://www.youtube.com/watch?v=4vajaXzHN08 - サービスアカウントキーを使⽤せずに、外部環境からGoogle Cloudリソースへ の安全なアクセスを可能にする認証⽅式

Slide 60

Slide 60 text

認証の流れ 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

Slide 61

Slide 61 text

設定⽅法 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:

Slide 62

Slide 62 text

設定⽅法 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 に対して負荷をかけたい

Slide 63

Slide 63 text

背景 - 負荷試験の対象となるサーバが ECS - SageMaker Endpoint に依存している - コストの観点から負荷試験をする時のみ⽴ち上げたい 解決⽅法 - 2つの k6 extension を作る - ECS のタスク数を更新する extension - SageMaker Endpoint を作成, 削除する extension ケース3: 負荷試験実⾏時のみECS, SageMaker Endpointを⽴ち上げたい

Slide 64

Slide 64 text

ケース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 が必須なため)

Slide 65

Slide 65 text

k6 extension 万歳 要は...

Slide 66

Slide 66 text

まとめ

Slide 67

Slide 67 text

- 負荷試験のリードタイムを約75%削減 - 基盤を使う場合と使わない場合で⽐較実験を⾏った - 研究員側で負荷試験ができるようになったので、エンジニアへの依存を減らしリリース サイクルを改善できた - ユーザ間で負荷試験に関するナレッジが共有されるようになった - ⾃然と新しいシステムでも使われる状況がつくれた - 負荷試験に対するハードルを下げることができた - リリース前だけではなく、こまめに負荷試験を回す⼈も出てきた - 1⽇平均20回程度使われている - ⇨ 早い段階でテストを実施することで⼿戻りが少なくなる - k8s基盤に変更を加える際の、各アプリの動作検証がしやすくなった - 負荷試験を当たり前にする⽂化を醸成できた 負荷試験基盤により変わったこと

Slide 68

Slide 68 text

効果は、処理が捌けない系のインシデントを防げていること、確認の⼯数が⼩さくなっていることである 活⽤例 - 導⼊予定のアルゴリズムの処理速度が問題ないことを確認できた → 実際に問題なく稼働している - ユーザ増に伴う流⼊増時に、滞留が発⽣しそうなことがわかった → 先んじて対策を検討開始した 実際のユーザからのフィードバック 項⽬ 負荷試験基盤のないとき 負荷試験基盤のあるとき 属⼈性 属⼈的で個別の理解が必要である 基盤ユーザなら誰でも使える 記録 負荷試験の記録が残るかどうかは作業者 次第である 作業者に関係無く記録が残る 実⾏しやすさ ⼤変。設定や環境構築を全て⾏ってから 実⾏できる 10分程度。設定ファイルの⽤意とブラウザか らの操作で実⾏できる

Slide 69

Slide 69 text

- 負荷試験基盤を Kubernetes クラスタ上に構築した - 負荷試験に対するハードルを下げるのは⼤事 - k6 extension による拡張性は無限⼤ - k6 はいいぞ まとめ

Slide 70

Slide 70 text

- karpenter の disruption により Pod が突然死 - karpenter.sh/do-not-disrupt: "true" を設定する - 別環境の Internal な LB に対して負荷をかけたい - VPC ピアリングを設定する その他の⼩話

Slide 71

Slide 71 text

研究開発部の採⽤情報 各種募集ポジションや関連記事などを Notionにまとめています 研究開発部のメンバー紹介 所属するメンバーのバックグラウンドや これまでの発表掲載実績などをまとめています https://media.sansan-engineering.com/randd https://sansan- engineering.notion.site/0afd3852e1d84695aff717be048e5062

Slide 72

Slide 72 text

No content