NASEBANAL Quickstarts − 開発の背景と、用意したシナリオのご紹介

目次
  1. NASEBANAL Quickstartsの開発の背景
  2. NASEBANAL Quickstartsの構成と特徴
  3. 用意しているシナリオ
  4. テスト結果のレポート
  5. まとめ

NASEBANALでは、コンサルティングサービスの中でのトレーニングに活用することを想定して NASEBANAL Quickstarts を開発し、OSSとして公開しています。本ブログでは、開発の背景と全体像、そして用意している7つのシナリオを、使っているOSSツールの主要機能と処理の流れ、実際に動かして得られた結果とあわせてご紹介します。具体的な手順はアプリ内のドキュメントとリポジトリのREADMEに任せ、ここでは「何が試せて、何が確かめられたのか」に絞ります。


NASEBANAL Quickstartsの開発の背景

AIコーディングエージェントが急速に普及し、要件を伝えて実装そのものを任せる場面が増えてきました。ただ、大きすぎる要件を一度に渡すと、AIも人間と同様に迷いや取りこぼしが生じやすくなります。要件を小分けにして、境界を明確にしたまま管理しやすくするという観点からも、マイクロサービスのようなアーキテクチャが改めて期待されているように感じます。

一方で、モノリスの開発では意識する必要のなかった概念が、マイクロサービスでは次々に登場します。APIゲートウェイ、イベントストリーミング、サービスディスカバリ、契約テスト、APIモック、認証基盤、シークレット管理、オブザーバビリティ。それぞれに適切なツールを導入すること自体は重要ですが、ツールが増えるほど、インストール方法も起動・停止の手順も管理画面へのアクセス方法もツールごとにバラバラで、いちいち覚えていられません。

ソフトウェアの品質管理・アーキテクチャの方針は、ベンダーごとに、さらにはプロジェクトごとにまちまちで、属人化しているケースが多いように感じています。だからこそ、テスト方針・利用ツール・テスト結果の管理を標準化していくことが、最終的にソフトウェアの品質管理全体の底上げにつながると考えています。この「ツールごとのやり方の違い」を吸収するために作ったのが、NASEBANALがOSSとして公開している NASEBANAL Quickstarts(リポジトリ名はnb-quickstarts)です。

NASEBANAL Quickstartsの構成と特徴

どのモジュールもmake <module>:up / make <module>:downというDocker Composeの呼び出しに統一されていて、管理画面を持つツールはmake <module>:openでそのままブラウザが開きます。使い終わったらmake <module>:downで片付くので、ゴミを残す心配もありません。NASEBANAL Stackを構成する技術要素を、1モジュールずつ切り出しているのが特徴です。

全体像は、デモアプリ内ドキュメントの概要ページにある、次の2つの図で紹介します。

全体図 — apps-networkと周辺モジュール

1つ目は全体図です。テスト対象のapps(frontend・backend・MySQL)を中心に、その前段のKong、非同期連携のKafka、認証のKeycloak、シークレットのVault、監視のObservabilityなどが同じネットワーク(apps-network)上に並び、必要なものだけを自由に組み合わせられます。矢印は呼び出す側から呼び出される側へ向かい、点線は既定ではオフの連携です。

NASEBANAL Quickstartsの全体アーキテクチャ図。apps-network内のfrontend・backend・MySQLを中心に、frontendの前段に置けるKong(向き先をSpecmaticやMicrocksのモックに切り替え可能)、Locustからの負荷をkafka-bridge経由でbackendへ流すKafka、JWTの公開鍵を提供するKeycloakとDBユーザーを発行するVault、OTLPでトレース・メトリクス・ログを受け取るObservabilityが並ぶ構成NASEBANAL Quickstartsの全体アーキテクチャ図。apps-network内のfrontend・backend・MySQLを中心に、frontendの前段に置けるKong(向き先をSpecmaticやMicrocksのモックに切り替え可能)、Locustからの負荷をkafka-bridge経由でbackendへ流すKafka、JWTの公開鍵を提供するKeycloakとDBユーザーを発行するVault、OTLPでトレース・メトリクス・ログを受け取るObservabilityが並ぶ構成

MCPアクセス経路の図 — 内蔵の/mcpとagentgateway

2つ目は、AIエージェントなどのMCPクライアントがbackendに到達する経路の図です(シナリオ5〜7に関わる部分)。MCPクライアントはFrontendを経由せず、backend内蔵の/mcpで、または任意でagentgateway(Kongと同じ位置づけの点線)経由で到達します。agentgatewayは、OpenAPIの契約からMCPツールを作ってREST APIを呼びます。Keycloakでログインして得たJWTはbackendで検証され、backendのDB接続情報はVaultから取得します。

MCPアクセス経路の図。MCPクライアントは、Frontendを経由せずbackend内蔵の/mcpへ直接、または任意でagentgatewayを経由してbackendに到達する。agentgatewayはOpenAPIからMCPツールを作ってbackendのREST APIを呼ぶ。MCPクライアントはKeycloakでログインしてJWTを得て、backendはKeycloakから公開鍵を取得して検証し、VaultからDBの認証情報を取得してMySQLに接続するMCPアクセス経路の図。MCPクライアントは、Frontendを経由せずbackend内蔵の/mcpへ直接、または任意でagentgatewayを経由してbackendに到達する。agentgatewayはOpenAPIからMCPツールを作ってbackendのREST APIを呼ぶ。MCPクライアントはKeycloakでログインしてJWTを得て、backendはKeycloakから公開鍵を取得して検証し、VaultからDBの認証情報を取得してMySQLに接続する

テスト対象のデモアプリと、アプリ内ドキュメント

テスト対象のappsモジュールは、REST・GraphQL・MCPを提供するFastAPIのbackendと、Next.jsのfrontendからなる、シンプルな会計台帳アプリです。データはイベントソーシングの考え方で持っており、勘定科目の残高は、1件ずつ追記された取引の合計として導出されます。PlaywrightやLocustといったテストツールを、すぐに手元のappsに対して試せます。

NASEBANAL Quickstartsのデモアプリのトップページ。make apps:upとmake playwright:testの実行例と、起動後のエンドポイント一覧が並ぶ

make apps:upで起動すると、このデモアプリ自身にドキュメント(/docs)が付いてきます。各シナリオの手順は、実際に動いているアプリの隣で、コマンドと「何が起きれば成功か」を添えて読めます。

用意しているシナリオ

シナリオは7つあり、番号はアプリ内ドキュメントと同じです。シナリオ1はテストツールでデモアプリ自体を検証し、シナリオ2〜7は、それぞれ上の構成図のモジュールを1つずつ取り上げ、同じ稼働中のappsに組み込んで動きを確かめる形になっています。以下では、シナリオごとに「何をするか」「使っているOSSの主要機能」「実際に確かめられたこと」を紹介します。

シナリオ1: デモアプリの動作検証

デモアプリ本体、つまり他のすべてのシナリオの土台になる稼働中のappsの動作を、同梱のテストツールで検証します。どのツールも専用のmakeコマンドを持ち、結果はHTMLレポートとして手元に残ります。レポートの画面は、下の「テスト結果のレポート」に載せています。

  • pytest・Vitest — ブラウザなしで動くユニットテストです。pytestはMySQLをインメモリDBに差し替えるため、backendのテストはほかのサービスを必要としません。Vitestはfrontendのロジック(APIクライアント、OIDCのPKCEフロー、backend resolver)を対象にします
  • Playwright — 実際のfrontendをブラウザで動かし、実際のbackendとMySQLに対して操作するE2Eテストです
  • Specmatic — 手で書いたopenapi.yamlを読み、稼働中のbackendへリクエストを送って、パス・メソッド・レスポンスコードごとのカバレッジを報告する契約テストです。エラーレスポンスも対象にします
  • Microcks — openapi.yamlに書かれたexampleを実際のbackendへ送り、レスポンスをexampleとスキーマに照らして確認する、2つ目のプロバイダー確認です
  • Locust・OWASP ZAP — 負荷テストとセキュリティスキャンです

確かめられたこと: pytestの22件、Vitestの20件、Playwrightの9件、Specmaticの18シナリオ(APIカバレッジ100%)は、すべて成功しました。何かを見つけたのはMicrocksです。Microcksが実行した9つのexampleのうち6つが成功し、失敗した3つは、テストの不備ではなく発見事項でした。1つ目はcreatedAtです。契約ではformat: date-timeと宣言されていますが、backendは2026-09-23T07:00:26とタイムゾーンのオフセットなしで返し、これはRFC 3339として有効ではありません。同じbackendに対して、Specmaticは18シナリオすべてに合格し、Microcksはformatを検知しました。契約と実装の、実際の食い違いです。2つ目はPOST /auth/loginで、契約にbad_credentialsというレスポンスのexampleはあるものの、同じキーのリクエストexampleがないため、Microcksは空のbodyを送り、exampleが期待する401ではなく422が返りました。こちらはbackendの不具合ではなく、exampleの不足です。

シナリオ2: Kong経由への切り替え

frontendの接続先をAPIゲートウェイのKong経由にし、Kongのapps_backendというGateway Serviceの向き先を、実backendからSpecmaticのスタブ・Microcksのモックへ差し替えます。frontendのコードは一切変えずに、同じ契約から作られたモックへ切り替わるのを確かめます。

  • Kong — 認証・レート制限・ルーティングをbackendの前段のゲートウェイに集約でき、向き先の変更もアプリのコードを触らず実行時に行えます。管理画面のKong Managerから、Host / Port / Pathを編集して保存するだけで切り替わります。このシナリオでは、Kongは設定をPostgreSQLに持つDBモードで動かしています
  • Specmatic — OpenAPIの契約からそのままモックサーバー(スタブ)を生成できます。同じ契約で実backendも検証できるため、モックと実装が知らないうちにずれることを防げます
  • Microcks — 契約を一度取り込むだけで、example付きのモックが立ち上がります。実APIの完成前でも、frontendなどの利用側が開発を進められます
Kong ManagerのGateway Serviceの編集画面。apps_backendのHostがbackend、Portが8080、Pathが空になっている

確かめられたこと: 向き先の切り替え前後で、まったく同じcurl http://localhost:8000/api/accounts/balancesを実行しました。切り替え前は台帳の実データ(Cashの残高120,000など)が返り、Kongの向き先をSpecmaticのスタブに変えた後は、同じURLから、スキーマには合致しているものの乱数で生成された別の値が返りました。frontendに手を入れることなく、実行時の設定変更だけで向き先が入れ替わったことが、この差で確認できます。なお、MicrocksはPOSTに必要な実bearerトークンをOpenAPIのexampleで表現できないため、POST /accountsはモックできません(読み取り系は問題なく動きます)。

シナリオ3: Kafka経由への切り替え

書き込みをKafkaに一度溜め、kafka-bridge(Kafkaのトピックを読んでbackendへ1件ずつPOST /accountsする小さなconsumer)がbackendへ流す構成にして、同じ負荷をREST直叩きとKafka経由で比べます。

  • Kafka — 書き込み側は永続化されたログに自分のペースで書き込み、消費側が一定のペースで処理します。そのため、バーストを吸収でき、メッセージは消費されるまで保持されるのでbackendが止まっても処理が遅れるだけでデータは失われません。送る側と受ける側が互いを知らなくてよい疎結合も、大きな特徴です
  • Locust — 負荷シナリオをPythonで書けて、Web UIでも、make locust:testのようにコマンドからも実行できます。Locust masterが複数のworkerにユーザーを割り当てる分散実行にも対応し、結果はHTMLレポートとして残ります

実測して面白かったこと: 同じ書き込み(POST /accounts)を、600ユーザー・60秒間、think timeゼロで流し続けました。backendは1台のラップトップ上で、あえてチューニング前の非力な設定のままです。

  • REST直叩き: 58件中46件が失敗(79%)。過負荷で接続プールが枯渇し、応答は秒単位まで悪化しました
  • Kafka経由: 124万1,297件を、失敗0件で受け切りました。応答は終始ミリ秒単位で安定しています
REST直叩きとKafka経由、それぞれ600ユーザー・60秒間の累積リクエスト数を対数目盛で比較した折れ線グラフ。REST直叩きは58件中46件失敗(失敗率79%)で序盤から頭打ち、Kafka経由は124万1,297件を失敗0件で送り切り、ほぼ一定の傾きで伸び続けている

ただし、Kafkaが負荷そのものをなくしたわけではありません。kafka-bridgeが自分のペースでbackendへ流し込むので、バーストを吸収してbackendに届く形を変えている、というのが実態です。実際、300ユーザー・40秒の実行の後にconsumer groupを見ると、書き込まれた約166万件のうち、約92万件がまだbackendに届いていない(lag)状態でした。「Kafkaに書けた」ことと「backendに反映された」ことは別で、反映までには時間がかかります。この結果整合の性質や運用コストを含めて、書き込みの受け口と処理能力を分離すると、瞬間的な負荷の受け止め方がまるごと変わる、ということを数字で確認できました。

シナリオ4: オブザーバビリティ

backendのトレース・メトリクス・ログをOpenTelemetryで送り、Prometheus・Tempo・Loki・Grafanaで見ます。負荷をかけたときの様子をGrafanaでライブに観察し、Alertmanager経由でアラートが発火するところまで確かめます。

  • OpenTelemetry — トレースやメトリクスのベンダー中立な標準です。一度計装すれば、受け取る側(ここではローカルのスタック、本番なら例えばNew Relic)をアプリのコードを触らずに切り替えられます。このシナリオでは、FastAPIのリクエストとSQLAlchemyのクエリ、HTTPサーバーのメトリクス、アプリケーションログを、OTLPでCollectorに送ります
  • Prometheus / Tempo / Loki — それぞれメトリクス・トレース・ログの専用ストアで、いずれもOSSでライセンス費用がかかりません。Prometheusはアラートルールの評価も担います
  • Grafana — メトリクス・トレース・ログを1画面で見られます。ログの行にはtrace_idが付いていて、ログからトレースへ、トレースからそのスパンのログへと、IDを手でコピーせずに行き来できます
  • Alertmanager — Prometheusのアラートを、グルーピング・重複排除・ルーティングして通知します。より重大なアラートが出ているときに、同じサービスの軽微なアラートを抑止する(inhibit)こともできます

このシナリオでは、Prometheusに3つのアラートルールを入れています。5xxが5%超でcriticalのBackendHighErrorRatio、p95レイテンシが1秒超でwarningのBackendHighLatencyP95、DB接続プール15本使用でwarningのBackendDbPoolSaturatedです。いずれも一瞬のノイズで発火しないよう、30秒続いたときだけ発火します。

確かめられたこと: シナリオ3で失敗したREST直叩きの負荷(300ユーザー・40秒)を、今度はGrafanaを開いたまま流しました。

  • レイテンシ(p95/p99)は、DBプールのタイムアウトである30秒付近まで跳ね上がり、使用中のDB接続数も上限付近に張り付いたまま推移しました。Kafka経由の場合は、同じパネルがすべて平坦なままです
  • アラートは、まず2つのwarningが発火し、1分ほど遅れてcriticalの5xxアラートが発火しました。criticalが出たあとはinhibitルールが働き、当番の人に届くのは「3件」ではなく「criticalの1件」に整理されました
  • Lokiでは、5xxの原因がQueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00というDB接続プールの枯渇だったことが、スタックトレースごと確認できました
Grafanaのダッシュボード。5xxの割合がほぼ100%に達し、発火中のアラートのパネルにBackendHighLatencyP95・BackendDbPoolSaturated・BackendHighErrorRatioが並び、Lokiのログパネルにはbackendの例外が並んでいる

さらに、KongとagentgatewayのトレースもCollectorに送れます。ゲートウェイを通ったリクエストは、ゲートウェイのスパンの下にbackendのスパン(SQLクエリを含む)がぶら下がる、1本のトレースとして見えます。

シナリオ5: Keycloakの利用

ログイン画面に本物のOIDCログインを加えます。Keycloakが発行したJWTを、backendが公開鍵で署名検証して受け入れるので、backendはパスワードを一切扱いません。

  • Keycloak — 標準のOIDC/OAuth 2.0に対応したIDプロバイダーで、ログイン・トークン発行・SSOを、認証コードを自前で書かずに任せられます。ユーザー・ロール・外部/企業IDとの連携を1か所で管理でき、同じIdPを複数のアプリで共用できます。このシナリオでは、起動のたびに固定のレルム(nasebanal)をインポートし、デモユーザーとサインアップ(ユーザー登録)を有効にしています

処理のシーケンスは、「Googleでログイン」と同じ考え方です(PKCE付きの認可コードフロー)。

  1. Frontend → Keycloak: ユーザーをKeycloakのログイン(またはサインアップ)画面へリダイレクトする(client_idとcode_challengeを付ける)
  2. Keycloak: ユーザーがここでサインインする。アプリはパスワードを目にしない
  3. Keycloak → Frontend: 使い捨ての認可コードを付けて/auth/callbackへ戻す
  4. Frontend → Keycloak: コードとcode_verifierをPOST /tokenに送り、トークンに交換する
  5. Keycloak → Frontend: アクセストークン(JWT)を返す
  6. Frontend → Backend: Authorization: Bearer <JWT>を付けてPOST /accountsなどを呼ぶ
  7. Backend → Keycloak: 初回(または鍵のローテーション後)だけ、公開鍵(GET /certs、JWKS)を取得してキャッシュする
  8. Backend: 署名・issuer・有効期限を検証し、通れば201 Created、失敗すれば401を返す

Backendはリクエストごとの問い合わせをせず、公開鍵での署名検証だけで判断します。FrontendのOIDC処理は、OIDCライブラリを使わずに手書き(リダイレクトで出て、コードで戻り、fetchを1回)しています。

Keycloak自身のサインインページ。アプリではなくKeycloakのURLでパスワードを入力する。New user? Registerのリンクからサインアップできる

確かめられたこと: ブラウザを使わずに確認するmake keycloak:verify-appsで、Keycloakから実際にトークンを取得してPOST /accountsを呼びました。

$ make keycloak:verify-apps
1. Getting a real access token from Keycloak (realm nasebanal, user keycloak-demo)...
2. Calling the real backend's protected POST /accounts with it...
   HTTP 201

$ curl -X POST localhost:8080/accounts ...            # トークンなし
401
$ curl ... -H "Authorization: Bearer <最後の1文字を書き換えたトークン>"
{"detail":"invalid or missing token"}  401
$ curl ... -H "Authorization: Bearer <正規のトークン>"
201

正規のトークンは通り、トークンなしと、最後の1文字を書き換えて署名が合わなくなったトークンは401で拒否されました。backendが信頼しているのはトークンの中身ではなく、Keycloakの公開鍵と一致する署名だ、ということが分かります。ブラウザでは、Keycloak側でサインアップした新しいユーザーが、backendに事前登録することなくそのまま取引を記帳できることも確かめられます。

シナリオ6: Vaultの利用

backendの設定からMySQLのパスワードを取り除き、起動時にVaultへ要求する形にします。Vaultがその場で短命のMySQLユーザーを作って渡し、リースが切れれば削除します。

  • Vault — シークレットを.envやイメージ、CI設定に散らさず、監査可能な1か所のストアに集約します。アクセスはポリシーとトークンで制御され、読み取りごとにログを残せます。そして、このシナリオで使っているデータベースシークレットエンジンは、認証情報を動的に発行する機能です。固定のパスワードを配る代わりに、要求のたびに新しい認証情報を作り、リース(貸出期間)が切れると自動で失効させます

処理のシーケンスは次のとおりです。Vaultには、権限の高いMySQL接続(root)を一度だけ設定しておき、backendが持つのは「認証情報を要求してよいVaultトークン」だけです。

  1. Backend → Vault: GET /v1/database/creds/apps-backendでデータベース認証情報を要求する(X-Vault-Token付き)
  2. Vault → MySQL: Vaultだけが持つrootで、ランダムな名前とパスワードを持ち、demoデータベースに限定した新しいユーザーをCREATE USERする
  3. MySQL → Vault: OKを返す
  4. Vault → Backend: ユーザー名・パスワード・リース(1時間)を返す
  5. Backend → MySQL: そのユーザーとして接続し、SQLを実行する(backendはリースをTTLの半分の時点で更新し、ロールの上限24時間まで延ばします)
  6. Vault → MySQL: リースが満了または失効したら、DROP USERでそのユーザーを削除する

確かめられたこと: 「Vaultなしでは動かない」ことと「Vaultありで動く」ことを、順に確かめます。

まず、backendのBACKEND_MYSQL_PASSWORDを空にしてVaultも渡さないと、backendは起動できず、次のようにログインを再試行し続けます(make vault:prove-needs-vault)。

[db] attempt 1/30 failed: (pymysql.err.OperationalError) (1045, "Access denied for user 'demo'@'172.20.0.3' (using password: NO)")
[db] attempt 2/30 failed: ... Access denied for user 'demo' ... (using password: NO)
  GET /accounts/balances -> HTTP 000
  (no answer - the backend never came up)

次に、Vaultを渡すと、backend自身の起動ログに、Vaultが動的に発行したユーザーが現れます(make vault:verify-apps)。

Password in the backend's environment: '' (empty on purpose)

[vault] issued a dynamic MySQL user v-token-apps-backe-c9wybUj5oXeKe (lease 3600s) from http://vault:8200/v1/database/creds/apps-backend

[{"name":"","balance":7,"eventCount":1},{"name":"Cash","balance":120034,"eventCount":15}]

環境変数のパスワードは意図的に空のまま、Vaultが発行したv-token-apps-backe-...というユーザーでMySQLに接続し、実際の残高が返っています。backendをもう一度再起動すると、2つ目の別のユーザーが作られます(make vault:db-usersで、MySQL自身のmysql.userに並ぶのが見えます)。その名前もパスワードも、誰も入力しておらず、Vaultが生成したものです。なお、このシナリオで使うVaultはインメモリのdevサーバーで、vault:downで記録ごと消える、あくまで手元での検証用の構成です。

シナリオ7: agentgateway経由でのMCPアクセス

backend側にMCPのコードを書く代わりに、OpenAPIの契約からMCPツールを組み立てるゲートウェイを立て、AIエージェントから呼べるようにします。

  • agentgateway — 既存のOpenAPI契約から、設定だけでMCPツールを作れます。MCP(およびA2A)の通信を1つのゲートウェイに通すことで、AIエージェントが呼べるものへのアクセス制御・可観測性・ルーティングを一元化できます。ツールは他のすべてと同じ契約から作られるので、APIの変更にも自動で追従します。管理用のダッシュボードUIも付いていて、どのルートがどのbackendに配線されているかを確認できます

処理の流れは、MCPクライアント(Claude Desktopやmcp-inspectorなど)が、Streamable HTTPでagentgatewayの/mcpに接続し、tools/listでツールを取得、tools/callで呼び出すと、agentgatewayが対応するREST APIをbackendに対して呼ぶ、というものです。

agentgatewayのダッシュボード。Traffic > Routesに、appsのbackendに配線されたRoute 1が表示されている

確かめられたこと: make agentgateway:toolsでMCPのハンドシェイクを手で行い、提供中のツールを一覧しました。openapi.yamlの1オペレーションにつき1ツールで、名前も説明も契約からそのまま取られた6個です(health・login・list_accounts・create_account・list_balances・get_account)。list_balancesをゲートウェイ経由で呼ぶと、GET /accounts/balances自体と同じライブなデータが返り、静的な説明ではなく、稼働中のbackendへの本物のプロキシになっていることが分かります。create_accountは、REST直叩きと同じく本物のbearerトークンが必要で、先にloginを呼んで得たトークンを渡さないと401になります。

また、シナリオ4のGrafana/Tempoでは、tools/callとその先のbackendのRESTリクエスト・SQLクエリが1本のトレースにつながって見えました。MCPの呼び出しとRESTの呼び出しを、同じ場所で追えます。

テスト結果のレポート

これらは、シナリオ1が手元に残すレポートです。どのテストツールもmake <module>:testという同じ操作感で、結果はHTMLレポートとして残り、同じ稼働中のappsを、ユニット・E2E・コントラクト・負荷・セキュリティのあらゆる角度からテストしています。最新の結果は次のとおりです。

レイヤーツール確認すること結果
ユニット(backend)pytest口座と残高、パスワードログイン、プロフィール、KeycloakのJWT検証(偽造・改ざんトークンを含む)22件
ユニット(frontend)VitestAPIクライアント、OIDCのPKCEフロー、転送先のbackendを選ぶresolver(ラウンドロビンと次のインスタンスへのフォールバック)20件
E2EPlaywright実ブラウザでログイン・ログアウト、プロフィール、誤ったパスワード、resolver API9件
コントラクトSpecmatic実backendがopenapi.yamlどおりに応答するか18シナリオ、APIカバレッジ100%
コントラクト(2つ目の確認)Microcksopenapi.yamlのexampleを実backendに送り、スキーマと照合9つのexampleのうち6件が成功、3件が発見事項
負荷LocustHTTP・GraphQL・MySQL、過負荷、Kafka経由シナリオ3・4の結果
セキュリティOWASP ZAPfrontendのパッシブスキャン、backend全ルートのOpenAPI駆動スキャンHigh/Mediumなし

以下は、これらのコマンドで実際に生成されたレポート画面です。

ユニットテスト: pytestとVitest

pytestはMySQLをインメモリDBに差し替えるので、どこでも1秒未満で動きます(22件で570ms)。VitestはブラウザなしでfrontendのAPIクライアントやログインのPKCEの扱い、転送先のbackendを選ぶサーバー側のresolverを確かめます。どちらもappsの起動なしで試せるので、最初に触るのに最適です。

pytestのHTMLレポート。環境情報の表と、すべてPassedになったbackendのテスト一覧
VitestのHTMLレポート。20件Pass・0件Failで、左にテストファイルごとのテストツリーが表示されている

E2Eとコントラクト: PlaywrightとSpecmatic

Playwrightは、実際のfrontendをブラウザで動かし、実際のbackendとMySQLに対して操作します。失敗したときは、スクリーンショット・ネットワーク・コンソールのトレースにワンクリックで入れます。

Specmaticは、openapi.yamlを読み、稼働中のbackendへ実際のリクエストを送って、パス・メソッド・レスポンスコードごとのカバレッジを報告します。正常系では届かない、エラーレスポンス(401・404・422)も含まれます。このopenapi.yamlはコードから生成せず手で書いているので、実装と食い違う可能性が本当にあり、それが確認する意味を生みます。

PlaywrightのHTMLレポート。9件のE2Eテストがpassedで、所要時間とともに一覧されている
Specmaticのコントラクトテスト結果。APIカバレッジ100%、成功18件で、全パス・メソッド・レスポンスコードがCoveredの表

コントラクトの2つ目の確認: Microcks

make microcks:testは、backendが/openapi.jsonで配信している契約(openapi.yamlそのもの)を取り込み、Microcksに適合テストを実行させます。契約にある名前つきexampleのそれぞれについて、Microcksがリクエストを組み立てて実際のbackendへ送り、ステータスコードとbodyを、exampleのレスポンスとオペレーションのスキーマに照らして確認します。何も生成しないため、テストされるのは契約のexampleが指定している内容そのものです。発見事項は、上のシナリオ1に記載しています。

Microcksの実行結果のページ。9つのexampleのうち6つが成功し、失敗した3つはPOST /auth/login、GET /accounts、GET /accounts/{account_id}にある
MicrocksのUIでGET /accounts/{account_id}を開いた画面: Microcksが送ったリクエストと、createdAtが2026-09-23T07:00:26のbackendの200レスポンス。有効なRFC 3339のdate-timeではないと指摘されている

SpecmaticとMicrocksは、同じopenapi.yamlを読み、どちらも実際のbackendを契約に照らしてテストできます。今回の検証で見えた違いは次のとおりです。

SpecmaticMicrocks
実装Kotlin。MIT(オープンソース版)。画面のStudio・Insightsは有償製品Java。Apache-2.0
実行形態コマンドとして実行(CLIまたはDockerイメージ)サーバーとして実行(Docker ComposeまたはKubernetes)
テストの元契約の定義と、別途与えたexample契約に書かれた名前つきexample。exampleのない操作は実行されない
タイムゾーンのオフセットがないcreatedAt検知されない検知
モックこの構成では契約ごとに1つのモックサーバー。生成データを返し、オープンソース版にUIはない稼働中のサーバーへ契約を取り込み、契約自身のexampleを返す。UIとAPIカタログあり

負荷とセキュリティ: LocustとZAP

Locustは、エンドポイントごとの応答時間のパーセンタイルと失敗数をレポートします(下は10ユーザー・20秒の短い実行で、97リクエスト・失敗0件)。シナリオ3・4の比較は、このレポートと統計の履歴を元にしています。

ZAPは、脆弱性をスキャンします。OpenAPI駆動のスキャン(api-scan)は、backendの全ルートへ実際の攻撃ペイロードを送るため、対象は常にこのデモのappsだけです。今回のbackendのスキャンでは、27エンドポイントでHighとMediumのアラートはなく(Lowが2件、Informationalが5件)、116のルールに合格して、失敗は0件でした(警告は2件)。

Locustのテストレポート。20秒間の実行で97リクエスト・失敗0件と、エンドポイントごとの応答時間パーセンタイル
ZAPのスキャンレポート。backendに対してHighとMediumのアラートはなく、Lowが2件、Informationalが5件、エンドポイント数は27

なお、強化されていないNext.jsの開発サーバーであるfrontendに対するZAPのベースラインスキャンは、52のルールに合格し、15件で警告(多くはセキュリティヘッダーの欠如)、失敗は0件でした。これらの結果はあくまでツールの実演であり、セキュリティ監査ではありません。

いずれのレポートも、ファイルをブラウザで直接開くだけで見られ(サーバーは不要です)、実行のたびに再生成されます。GitHubのCIが実行するのは、PythonのlintやDockerfileのlintとビルド、frontendの型チェックなどの静的チェックで、上記のテストは稼働中のスタックが必要なため、ローカルで実行します。

まとめ

NASEBANAL Quickstartsは、特定の実験結果を報告するためのものではなく、マイクロサービスならではの構成要素とその検証ツールを、手元のサンドボックスで気軽に試すための入口です。シンプルなコマンドだけでコンテナの操作を一通り試せること、そしてサービス・ツール間の連携を、7つのシナリオで実際に動かして確かめられることが、一番の特徴だと考えています。そして、その結果がテストレポートとして手元に残ることで、「動いた」ことを後から見返して共有できます。

今後も、NASEBANAL Stackの変化に合わせてモジュールとシナリオを増やしていく予定です。そこで得られるテスト結果を統合的に管理する仕組みは、NASEBANAL Evolutionの開発へとつなげていきたいと思っています。

シナリオの手順は、NASEBANAL Quickstarts のREADMEと、make apps:upで立ち上がるデモアプリ内のドキュメントに載せています。興味があれば、まずはmake apps:upから触ってみてください。