写真共有サービスで、ログインした利用者だけにアルバムを見せたい。さらに、家族への共有を解除したら、その人からの閲覧を止めたい。
ログインだけなら認証サービスに任せられますが、「このアルバムを誰に見せるか」は別に実装しなければなりません。さらに本番で使うなら、共有解除、サーバの停止、バックアップからの復元でも、権限が正しく扱われるかが気になります。
そこで、Keycloakでログイン、OpenFGAで共有関係の判定、業務APIでアクセス制御を行う構成を作り、Mac上で試しました。 一般消費者向けサービスのバックエンド開発を想定し、次の3点をまとめました。
- どう作るか:各サービスの役割、通信方式、共有データの持ち方。
- 本番前に何を確かめるか:負荷・共有解除・障害・復元を試して分かったこと。
- 自分で運用するか、任せるか:GCP・AWS・Azureの配置と費用、IDaaSとの比較。
通常負荷では目標の応答時間を満たしました。ただし、OpenFGAの停止中は正当な利用者も閲覧できず、バックアップ復元後には解除済みの共有が復活しました。停止中にどの操作を止めるか、復旧後に何を確認するかまで扱います。クラウドの構成と費用は参考案で、実験はすべてローカルで行っています。
まず全体像:何を作って、どこを測ったか
Keycloakは本人確認とトークン発行を担当する認証サーバ、OpenFGAは「誰が、何を、どう操作できるか」を関係データから判定する認可サーバです。 どちらも業務APIとは別プロセスです。OpenFGAが許可を返した後、実際にデータを返すのは業務APIです。Keycloak、OpenFGAの概念
まず①〜③を追うと、ログインはKeycloak、閲覧・編集は業務APIという別の経路になっています。OpenFGAが返すのは許可・拒否の判定で、アルバムのデータを返すのはAPIです。下側のワーカーは共有を変更するときに使います。
図のk6は、利用者の代わりにHTTP要求を発生させる負荷試験ツールです。Outboxワーカーは、DBに保存した権限変更をOpenFGAへ反映する処理で、後で流れを説明します。
実験ではアルバムのメタデータと共有権限を扱います。写真ファイルの転送性能は測っていません。
| 対象 | 実施したこと |
|---|---|
| Keycloak・OpenFGA・業務API・PostgreSQL・ワーカー | ローカル実装と機能・負荷・障害・復元試験 |
| Mac上のDocker Compose | 全実験の実行環境。クラウドは使用していない |
| GCP・AWS・Azure | 後半に配置案を掲載。構築・負荷測定はしていない |
実装・実験結果のリポジトリを公開しています。この記事が参照する実験成果物は固定コミットです。個別試験の実行SHAは、後述のrun台帳に記録されています。
OpenFGAを足す理由は、速度よりルールの共通化
写真共有程度なら、APIが業務DBで所有者と共有先を調べるだけでも作れます。OpenFGAを導入すると、サーバ、DB、通信、監視、復旧手順が増えます。
| 必要な認可 | 最初に検討する構成 |
|---|---|
| 一般利用者と管理者を区別する | Keycloakのロール+API側の検証 |
| 自分のデータだけ操作する | APIで所有者IDを照合 |
| 対象ごとの共有ルールを少数のAPIで扱う | 業務DBで所有者・共有先を判定 |
| 複数APIで同じ関係モデル・権限ルールを使う | OpenFGAへの集約を検討 |
OpenFGAを使うのは、同じ共有ルールをAPIごとに実装せずに済むようにするためです。Keycloakにもロールや認可機能がありますが、今回は本人確認と対象単位の共有関係を別々に管理しました。
OpenFGAを呼ぶと応答時間がどれくらい変わるのか、業務DBで直接判定する場合と比べます。 ただし今回の直接DBモードも、権限変更時には同じOutbox・OpenFGA反映経路を使います。これは閲覧・編集要求に対する認可の判定経路を比較する実験で、OpenFGAを完全に除いたシステム同士の費用・可用性比較ではありません。
実装では比較のためにmode=directとmode=fgaを切り替えます。本番では利用者のリクエストに認可方式の選択を委ねず、サーバ側の設定として固定する前提です。
通信方式:業務APIから別サーバのOpenFGAを呼ぶ
業務APIはHTTPクライアントでOpenFGAへJSONを送ります。SDKを使う場合も、APIに組み込むのは呼び出し用ライブラリです。OpenFGAサーバとDBは別に動かします。
| 経路 | ローカル実装の通信 |
|---|---|
| k6 → Keycloak | HTTPS上でOIDC Authorization Code+PKCE、Refresh Tokenによる更新 |
| k6 → 業務API | HTTPS。アクセストークンを付けて閲覧・編集 |
| 業務API → Keycloak | HTTPSで署名検証用の公開鍵集合JWKSを取得・保持 |
| 業務API/ワーカー → OpenFGA | HTTPS+JSON。Checkと関係の追加・削除 |
| 各サービス → PostgreSQL | PostgreSQLプロトコル。今回の閉じた実験ネットワークではDBのTLSは無効 |
HTTP経路ではローカルCAを信頼させ、証明書を検証しています。本番案ではDB接続もTLSと証明書検証を設定します。内部ネットワークに配置しただけでは、通信が暗号化されるわけではありません。
OpenFGAにはHTTP APIとgRPCがありますが、今回は要求を確認しやすいHTTP+JSONを選びました。OpenFGAへのサーバ間認証と、利用者がAPIへ提示するトークンは別物です。OpenFGAの本番設定
ログイン済みでも、アルバムを見られるとは限らない
次の図では、ログイン後の閲覧要求を追います。APIはトークンで本人を確認した後、共有の変更中か、その人に閲覧権限があるかを調べます。
APIは、リクエスト本文で指定された本人IDを信用せず、検証済みトークンのsubを使います。ID Tokenではなく、APIを宛先とするアクセストークンを検証します。今回の本人IDは単一issuerを前提とし、複数issuerを受け入れるならissuer + subから業務上の利用者IDへの対応が必要です。JWTの検証指針
なお、JWTをローカル検証する構成では、ログアウトやアカウント停止だけで発行済みトークンが直ちに使えなくなるとは限りません。共有解除とアカウント停止は別の要件として設計します。
データ設計:共有設定の正本と、判定用の関係を分ける
認可モデルは、所有者が閲覧・編集でき、閲覧者は閲覧だけできる最小構成です。
model
schema 1.1
type user
type album
relations
define owner: [user]
define viewer: [user]
define can_view: owner or viewer
define can_edit: owner
例えばuser:aliceをalbum:summerのowner、user:bobをviewerとして登録します。APIはcan_viewやcan_editを問い合わせます。モデルIDを固定し、変更時は期待する許可・拒否をテストしてから切り替えます。モデルの管理
業務DBにアルバム・共有設定の正本を持たせ、OpenFGAに判定用の関係を反映します。次は実装の主要項目を抜き出した図です。KeycloakとOpenFGAの内部テーブルを表すものではありません。ALBUMがアルバム、ALBUM_SHAREが共有先、AUTHZ_EVENTがOpenFGAへ反映する変更です。共有設定と反映イベントを同じ業務DBに置く点に注目してください。
ALBUM_SHAREの主キーはalbum_id・user_subject・permissionの組です。同じ共有関係の重複を防ぎます。versionは権限変更の世代、イベントのstatusは未反映・反映済み・古いため破棄した状態を表します。
owner_subjectとuser_subjectは検証した利用者の識別子です。認証DBへの外部キーではありません。Keycloak・OpenFGAの内部DBは、それぞれのサービスを通して操作します。
共有解除は「DB更新に成功した」で終わらない
業務DBの更新とOpenFGAへの反映は、ひとつのDBトランザクションにはできません。片方だけ成功する場合に備え、業務DBの共有変更と同じトランザクションでイベントを保存するOutbox方式を使いました。
非同期反映の間に古い権限を使わせないため、APIはchangingを確認して対象アルバムへの保護操作を一時拒否します。反映後に操作を受け付けます。整合性を優先した分、変更中は正当な利用者も一時的に操作できなくなる設計です。
また、これはOpenFGA単体の性質ではなく、業務DBの変更中フラグ・APIの拒否・ワーカーの反映確認を組み合わせた設計です。キャッシュを有効にする場合も、どの状態を見て変更完了と判定するかを再確認する必要があります。
解除前に始まった要求まで取り消す保証はありません。完了応答の前後をどの時点で区別するかは、サービス側で定義する必要があります。
測定条件:毎秒何件のログイン・更新・API要求を送るか
「100万人が登録できるか」と「毎秒何件処理できるか」は別です。今回は架空ユーザー1,000人、アルバム1,000件を使い、API・ログイン・トークン更新の到着率を別々に指定しました。
| 項目 | 条件 |
|---|---|
| ホスト | Apple SiliconのMac、メモリ24GB。Docker VMに4 CPU・約12GBを割り当て |
| バージョン | Keycloak 26.7.3、OpenFGA 1.18.1、PostgreSQL 17.6、k6 1.8.1 |
| CPU/メモリ上限 | Keycloak 1.5 CPU/3 GiB、DB 1 CPU/2 GiB、OpenFGA 0.5 CPU/1 GiB |
| その他の上限 | API 0.4 CPU/768 MiB、ワーカー0.1 CPU/256 MiB、k6 0.5 CPU/1 GiB |
| 共有数 | アルバムごとに5人または50人 |
| APIの期待結果 | 許可80%・拒否20%。所有者の閲覧・編集、共有先の閲覧、禁止操作を混ぜる |
| OpenFGA | キャッシュ無効、CheckはHIGHER_CONSISTENCY |
| 通常比較 | ウォームアップ60秒+測定180秒、各条件3回 |
| 資源観測 | CPU・メモリ・DB接続・Outbox滞留を約10秒間隔で取得 |
API単独比較と混合負荷の保護APIは、事前取得したアクセストークンを使います。通常のアクセストークンの有効期間は実験用に7,200秒としており、1時間の試験中に保護API用トークンを更新する経路は測っていません。混合負荷のログイン・更新は、別のシナリオとして同時に実行しています。
準備用のトークン取得には、実験上の便宜でpassword grantを使っています。OAuthの現行セキュリティ指針では使用が禁止されているため、本番には使いません。測定対象のログインにはAuthorization Code+S256 PKCEを使います。PKCEは認可コードの横取り対策に用いる仕組みです。ブラウザ描画や人の入力時間は含みません。OAuthセキュリティ指針 §2.4
以降のAPI p99は、接続確立を除くHTTP要求の送信・待機・受信時間です。 使用したk6のresponse.timings.durationには、DNS・TCP接続確立・TLSハンドシェイクの時間が含まれません。JWT検証・DBアクセス・OpenFGA Checkは、API内の処理区間を別に計測しています。ログインと更新の遅延は、それぞれの処理の開始から終了までです。k6の測定境界
p99は99%の観測が収まる時間です。表の「p99の中央値」は、3試行それぞれで求めたp99の中央値で、全要求をまとめたp99ではありません。
負荷試験の「期待どおりの判定」は、許可予定ならHTTP 200、拒否予定なら403というステータスの一致率です。「誤許可」は拒否予定の要求に200が返った件数を数えます。応答内容まで全件検証する指標ではなく、基本機能テストでの応答確認とは区別しています。
非機能要件と、今回確認できる範囲
| 観点 | 試験で置いた目標・確認方法 | 今回の限界 |
|---|---|---|
| 認可の正しさ | 誤許可0、通常負荷の期待どおりの判定99.9%以上 | 有限の入力での結果。すべての経路の安全性証明ではない |
| 通常時の遅延 | API p99 300ms、ログイン2秒、更新500msを閾値に設定 | APIは上記の測定境界。利用者が感じる全体時間とは異なる |
| 負荷生成 | 通常の正式比較でdropped iterations 0 | サーバと生成器が同じMacの資源を共有 |
| 権限変更 | 完了後の新規閲覧を拒否、古いイベントで復活しない | 直列試験。更新と連続閲覧の競合は未検証 |
| 障害時 | 誤許可0、再開後60秒以内に10秒窓3つ連続で基準達成 | コンテナ再起動。HA・ゾーン障害の試験ではない |
| 復元 | 別ボリュームへ復元し、認可状態を照合 | ローカルの論理バックアップ。RPO・災害復旧は未検証 |
| 継続運転 | 1時間、失敗・メモリ・接続・滞留を観測 | 月間可用性や長期安定性は評価できない |
実験結果の読み方
気になる点から読めるよう、疑問と試験の対応をまとめました。
| 知りたいこと | 対応する結果 |
|---|---|
| 権限確認の通信が増えても遅くならない? ログインが重なっても動く? | 結果1:通常負荷、結果2:混合負荷 |
| 共有を解除したら、本当に見られなくなる? | 結果3:共有解除 |
| サーバが落ちても使える? 復元後に権限は戻りすぎない? | 結果4:障害、結果5:バックアップ復元 |
| そのまま動かし続けても大丈夫? | 結果6:1時間の継続試験 |
結果1:権限の確認を別サーバに任せても、目標の応答時間を満たせた
OpenFGAを導入すると、閲覧や編集のたびに、APIから別サーバへ「この人に許可してよいか」を問い合わせます。ここで確かめたかったのは、この問い合わせが増えても、APIが目標の300ms以内で応答できるかです。
毎秒100件の要求を送ると、APIのp99は3回の試行の中央値で約5.3〜5.4msでした。p99は、応答時間を短い順に並べたときの99%の位置を表し、一部の遅い応答も含めて速さを見る指標です。この共有モデルと負荷では、OpenFGAへ問い合わせても目標の300msに収まり、誤許可もありませんでした。
測ったのは接続確立を除くAPIの応答時間です。画面表示が5msで終わるという意味ではなく、複雑な権限ルールやクラウド上でも同じ時間になるとは限りません。
負荷をかける前の基本テストは29/29で期待どおりでした。所有者・共有先・無関係な利用者、改ざん署名、期限切れ、issuerやaudienceの不一致などを確認してから負荷をかけました。
同じAPI・データ・負荷条件で、判定経路を直接DBとOpenFGAに切り替えた結果です。
| API要求/秒・共有数 | 直接DB:p99の中央値(最小–最大) | OpenFGA:p99の中央値(最小–最大) |
|---|---|---|
| 25 req/s・共有5人 | 5.640(5.634–5.808)ms | 8.971(8.586–9.298)ms |
| 100 req/s・共有5人 | 4.421(4.289–4.539)ms | 5.273(5.175–5.482)ms |
| 100 req/s・共有50人 | 4.507(4.401–4.604)ms | 5.381(5.238–5.532)ms |
各条件3回、すべて期待どおりの判定100%、誤許可0、dropped iterations 0でした。
共有50人の試験でも、要求側は決められた共有先を選びます。50人全員を均等にアクセスさせた実験ではなく、主に保存する共有関係数を増やした比較です。
表の差は「通信1回のコスト」ではありません。DBの問い合わせ方や接続・実行状態も異なるためです。深いグループ階層、一覧検索、大量の関係データ、別ノードへの通信は未検証です。
200 req/sの探索では、直接DBで70件、OpenFGAで170件のdropped iterationsが出ました。これは予定した処理をk6が開始できなかった件数です。VUは要求を実行する仮想ユーザーです。報告ではVU不足と分類していますが、VUを増やした再試験でサーバの飽和点を確定したわけではありません。100 req/sは確認できた水準であり、この構成の処理限界ではありません。
結果2:API・ログイン・トークン更新を同時に動かしても、失敗はなかった
閲覧・編集だけの試験では、ログインが重なったときの負荷が分かりません。同じマシンでログインとトークン更新も動かし、APIの応答や認証処理に失敗が出ないかを確かめました。
API 25 req/s・ログイン5 req/s・更新20 req/sを同時に送っても、APIの判定はすべて期待どおりで、ログイン・更新もすべて成功しました。 ただし、さらにログインが集中した場合や、一部のサーバが止まった場合にどこまで処理できるかは分かっていません。
次はOpenFGAモードの結果です。直接DBモードも含め、各条件を3回ずつ試しました。
| API/ログイン/更新の毎秒件数 | API p99の中央値 | ログインp99の中央値 | 更新p99の中央値 |
|---|---|---|---|
| 25/1/5 | 8.026 ms | 97.44 ms | 12 ms |
| 25/5/20 | 3.764 ms | 86 ms | 5 ms |
両モードの正式比較で、APIの期待どおりの判定とログイン・更新の成功率は100%、誤許可・dropped iterationsは0でした。
追加負荷の条件で遅延が下がっていますが、「負荷を増やすと速くなる」という結論にはできません。接続の再利用やJITによる最適化、ホストの状態など、どの要因が効いたのかは調べきれていません。別の認証単独試験でも、同じ20 login/sでp99が72/27/74msと分かれ、原因は未解明です。そのうち1試行には資源観測エラーも1件ありました。
API要求を25から50 req/sへ60秒間増やしても、処理が追いつかなくなる状態にはなりませんでした。必要台数を決めるには、実サービスの操作比率・ピーク・再試行と、障害時に必要な余力を条件に加えて測ります。
結果3:共有解除は反映できたが、変更中は操作を止める
共有解除では、DBの書き換えに成功するだけでなく、解除した相手が実際に閲覧できなくなることが重要です。解除完了までの時間と、その前後の閲覧結果を確認しました。
共有解除の完了後、確認した閲覧要求はすべて拒否されました。 ただし、反映を待つ間は、そのアルバムへの操作を所有者も含めて止めています。画面にも「反映中」と表示して、完了まで待ってもらうことになります。
各モードで共有の追加・解除を100サイクルずつ実行しました。
| 指標 | 直接DB | OpenFGA |
|---|---|---|
| 解除完了までのp99 | 135.97 ms | 125.58 ms |
| 完了後の確認要求での誤許可 | 0 | 0 |
| 変更直後に観測した安全側の拒否 | 200件 | 200件 |
ここでの時間は、変更要求開始から完了をポーリングで観測するまでです。OpenFGAの内部反映時間だけを表す値ではありません。
手順は「変更→直後の閲覧1回→完了待ち→閲覧3回」の直列実行です。閲覧を並行して続ける試験ではないため、解除完了前後の競合は未検証です。
ワーカーを30秒止めた試験では、保留中の30回の要求をすべて拒否し、再開後約4.18秒で反映完了、その後は解除された利用者に403を返しました。重複する要求、古いイベント、順番に実行した追加→解除→追加の最終状態も照合しました。
HTTP 202は受付であり、「解除済み」ではありません。どの操作を何秒まで待たせてよいかを決め、閲覧と共有解除が同時に起きる場合も確かめてから本番へ持ち込みます。
結果4:権限を確認できないと、正当な利用者もアルバムを使えなくなる
OpenFGAへ接続できないときは、権限を確認できないままデータを返さないよう、閲覧・編集を拒否します。実際にサーバを止め、誤って許可しないか、どの機能が使えなくなるかを確かめました。
OpenFGAを止めると、ログインはできても、アルバムの閲覧・編集を受け付けるAPIはほぼ使えなくなりました。APIプロセスが落ちたのではなく、権限を確認できずに要求を拒否したためです。さらに、3つの論理DBを置いたPostgreSQLを止めると、ログインも含めて影響が広がりました。障害中の誤許可は観測しませんでした。
誤った許可を避ける代わりに、正当な利用者の操作も止まります。 停止中も使えるようにするには、OpenFGAやDBを冗長化し、1台が落ちても別のインスタンスで処理できるかを試します。DBを用途ごとに分けるのは、ひとつのDB障害ですべての機能が止まるのを避けるためです。
OpenFGAモードの混合負荷中に、依存サービスをひとつずつ60秒停止しました。各ケース3回、再開後は180秒観測しています。
| 止めたもの | 観測した影響 | 本番で考えること |
|---|---|---|
| OpenFGA | 保護APIがほぼ処理できなくなった。ログイン・更新は継続 | 認可を外部化すると、保護APIの依存先が増える |
| Keycloak | 新規ログイン・更新がほぼ停止。保護APIの継続率には試行差 | JWTがあっても鍵取得を含む依存を確認する |
| 業務API | 保護操作がほぼ停止。ログイン・更新は継続 | ログインに加え、保護APIが使えるかも監視する |
| 共有PostgreSQL | 認証・認可・業務処理に同時に影響 | 論理DBの分離だけでは障害範囲を分けられない |
全12試行で誤許可0、終了時の整合性を確認しました。再開から40秒後に、10秒ごとの集計で3回連続して回復基準を満たしたことを確認しました。
主な回復基準は、各フローの開始件数が予定の80%以上、APIの判定一致率99.9%以上、ログイン・更新成功率99%以上、各p99が設定閾値内であることです。通常負荷試験の「dropped iterations 0」と同じ条件ではありません。40秒は回復基準を満たしたことを確認するまでの時間で、最初の成功応答までの時間ではありません。 意図的な停止中のエラーは、通常時の成功率と分けて評価しています。
Keycloak停止時の試行差については、トークン期限だけが原因とは断定できません。実装では、JWKS取得から300秒を超えるとキャッシュを更新します。取得に失敗すると、既知の鍵があっても検証を止める経路があります。トークンの残り有効時間とキャッシュ更新時刻の影響は、まだ切り分けていません。
本番では、鍵更新失敗時にどの範囲・期間まで保持した鍵を使えるか、未知の鍵への要求をどう制限するか、停止・鍵失効要件と合わせて決める必要があります。認証サーバを呼ばない設計でも、依存が完全になくなるわけではありません。
結果5:バックアップを戻すと、解除した権限も戻る
バックアップには、取得した時点の共有設定も入っています。その後に共有を解除していた場合、復元すると誰が閲覧できるようになるのかを確かめました。
復元すると、共有を解除した相手が再び閲覧できてしまいました。 DBが起動しただけでサービスを再開すると、この状態を見逃します。
今回のバックアップは、共有状態を整えてから3論理DBを順番にpg_dumpしたものです。各DBのバックアップは、それぞれの取得時点に対して整合しますが、3つを同一時点のスナップショットにはしていません。書き込みが継続する環境でのDB間整合性は別の課題です。pg_dumpの仕様
3論理DBと必要な設定を別ボリュームへ復元し、サービスがreadyになるまで約32.65秒でした。バックアップ時点の状態に対する基本テストは29/29で期待どおりでしたが、バックアップ後に解除した共有は復元環境で再び許可されました。
共有設定も過去に戻るのは、バックアップの動作として自然です。注意したいのは、基本テスト29件がすべて通っても、この問題を見逃すことです。テストが照合していたのはバックアップ時点の状態で、利用者がその後に行った共有解除ではありません。
復旧時には、バックアップ後の権限変更も復元し、「解除した相手が閲覧できないか」を確認します。この工程はまだ自動化していません。最新の変更履歴も失っている場合は、同じ古いバックアップ同士を照合するだけでは直せません。変更履歴を保全し、権限を確認できない対象へのアクセスは再開しない方針が必要です。
結果6:1時間の試験だけでは、何日も動かし続けられるかは分からない
短い負荷試験で速く応答できても、動かし続けるうちにメモリやDB接続を使い切ることがあります。そこで、同じ負荷をかけ続けても、失敗や応答の遅れが出ないかを1時間調べました。
成功率・遅延の基準は満たし、メモリ不足による停止もありませんでした。ただし、試験の後半もメモリ使用量は増えていました。増加が続けばいずれ不足しますが、キャッシュなどが一定量まで増えた後に落ち着く場合もあります。今回の測定だけでは、どちらかは分かりません。
1時間は処理できましたが、このメモリ量で何日も動かせるかは未確認です。
OpenFGAモードでAPI 25 req/s、ログイン1 req/s、更新5 req/sを、ウォームアップ後1時間継続しました。これは100 req/sの短時間比較とは別条件です。
| 指標 | 結果 |
|---|---|
| 保護APIの要求数 | 90,001件 |
| 期待どおりの判定/誤許可/dropped iterations | 100%/0/0 |
| API p99/最大 | 8.409 ms/179.807 ms |
| ログイン | 3,600件、成功100%、p99 99ms |
| 更新 | 18,001件、成功100%、p99 14ms |
許可予定の要求だけのAPI p99は8.494ms、拒否予定だけでは7.737msでした。許可要求だけでも、全体と同程度のp99でした。
要求数90,001件には、測定区間のカウンター不足をほかの記録で補った箇所があります。
要求数と完了数の集計方法
到着率の区間境界で予定よりAPIが1件多く開始され、90,001件を分母にしています。測定区間の完了カウンターが直接出力されていなかったため、完了数は全区間のカウンター一致と測定区間の成功件数から補完された値です。
メモリ不足による強制終了(OOM)、接続枯渇、Outbox滞留は観測しませんでした。ただし後半のメモリ使用量の傾きは、DBが+12.17 MiB/h、APIが+8.41 MiB/h、k6が+30.05 MiB/hでした。1時間の傾きから将来の増加量を外挿したり、メモリリークの有無を断定したりはできません。
次は数時間〜日単位で動かし、何がメモリを使っているか、使用量がどこかで落ち着くかを調べます。また、この1時間試験は閲覧・編集・ログイン・更新が中心で、権限変更が大量に発生する状態のOutbox処理能力を測ったものではありません。
本番へ移すなら:GCP・AWS・Azureの配置案
ここからは未実装の参考設計です。3クラウドとも単一リージョン内の複数ゾーンを想定し、リージョン全体の障害に備える構成ではありません。HAは一部の障害が起きても稼働を続けるための冗長構成を指します。実験では1台のPostgreSQLに3論理DBを置きました。本番案では、認証・認可・業務のDBインスタンスを分け、負荷と保守・障害の影響を分離します。
DBを分ける分、費用は増えます。小さく始めるならDB共有も選択肢ですが、DBが止まると認証・認可・業務処理もまとめて止まります。OpenFGAも本番向けにDBの専用利用を推奨しています。OpenFGAのDB推奨
Kubernetesは通信のために必須ではない
Composeでも、VMと内部DNSでも、APIからOpenFGAへHTTPSで接続できます。以下でKubernetesを使うのは、複数インスタンスの配置、入れ替え、名前解決、ヘルスチェックを共通の仕組みで運用するためです。
Serviceは接続先を提供しますが、証明書・認証・アクセス制限は別途設定します。Pod間でもノードやゾーンをまたげばネットワークを通ります。「Pod間だから速い」ではなく、その配置でCheckの時間を測ります。 接続再利用で通信先が偏ることもあるため、Pod数を増やせば要求が均等に分かれるとは限りません。Kubernetes Service
次の3図は、同じ役割を各クラウドのサービスに置き換えたものです。共通するのは、入口でKeycloakとAPIへ振り分け、OpenFGAは内部に置き、認証・業務・認可のDBを分ける構成です。クラウドごとの差は、入口、Kubernetes、DBに使うサービス名を見比べてください。
GCP
GKEのregional構成に加えてノードとPodも分散させ、Cloud SQLはHAと非公開の接続経路を設定する案です。GKE regional、Cloud SQL HA
AWS
AWS Load Balancer Controllerなどで入口を接続し、ノード・PodをAZに分散します。RDSは非公開とし、必要な通信だけを許可します。ここで想定する単一standbyのMulti-AZ DB instanceは、読み取り分散用の構成ではありません。EKSの信頼性設計、RDS Multi-AZ
Azure
Application Gateway Ingress Controllerで入口を接続し、AKSとDBをゾーン障害に備えた配置にする案です。リージョン・SKUでの提供条件は構築時に確認します。AKSの信頼性設計、PostgreSQL HA
全案でNetworkPolicy等により必要な通信だけを許可し、readinessやゾーン分散、縮退時の容量を設定します。Kubernetesの導入だけでこれらが整うわけではありません。
図では見通しのため省略していますが、全案でAPIからKeycloakへのJWKS取得経路、内部DNS、証明書管理、DBへのTLS接続、秘密情報、監査ログ、監視を用意します。OpenFGAの管理・Write APIを利用者へ公開しません。
また、今回の単一ワーカーをそのまま複数起動してよいとは限りません。イベントの排他取得、順序、重複処理、停止後の再処理を設計します。KeycloakもPodを増やすだけで完了ではなく、クラスタ通信・DB・プロキシ設定を公式のHA構成に合わせます。KeycloakのHA構成
この構成をクラウドで動かすと、月いくらか
OSSのライセンス費が無料でも、冗長化したサーバとDBの費用はかかります。 この配置案の台数とマシン仕様(SKU)で計算すると、計算資源とDBだけで月約1,100〜1,360ドルです。入口・通信・監視などの仮予算も含めるなら、月約1,300〜2,200ドルを初期検討の目安にします。
以下は2026年9月14日に確認した公式料金による概算です。実際の請求額でも、Macの結果から算出した必要台数でもありません。
比較の前提を揃える
| 項目 | 見積もりの前提 |
|---|---|
| 稼働時間・契約 | 月730時間、Linux、オンデマンド。予約・長期契約・導入クレジットなし |
| リージョン | GCP us-central1、AWS us-east-1、Azure eastus。米国リージョンの参考価格で、日本リージョンの価格ではない |
| Kubernetes | 標準サポートのクラスタ1つ、4 vCPU・16 GiBのノード3台をゾーン分散 |
| アプリ | Keycloak・業務API・OpenFGAを複数Podで配置する予算枠。負荷生成用k6は常設しない |
| DB | 認証・業務・認可の3系統。それぞれprimary 2 vCPU・8 GiB+同規模standbyのHA構成 |
| DBストレージ | 各primaryに128 GiB相当。HA側のストレージ費も計上 |
| 円換算 | 読みやすさのため1ドル=150円と仮置き。現在の為替を示すものではない |
CPU数とメモリを近づけた比較であり、各SKUの性能、IOPS、SLAを同一とする比較ではありません。3ノードで必要なPodを配置できるか、1ゾーン停止後も目標負荷を維持できるかは、別途確認が必要です。
まず、計算資源とDBの小計
この配置案の固定費を大きくしているのは、3系統のHA DBです。 下表では計算資源とDBの小計の半分以上を占めます。DBを同居させれば費用を抑えられますが、結果4のように、ひとつのDBの停止が複数の機能に及びます。
| 月額USD | GCP | AWS | Azure |
|---|---|---|---|
| ノード3台 | 293.51 | 420.48 | 420.48 |
| Kubernetes管理料 | 73.00 | 73.00 | 73.00 |
| HA DBの計算資源・3系統 | 607.07 | 779.64 | 779.64 |
| HA DBのストレージ・3系統 | 130.56 | 88.32 | 88.32 |
| 計算資源+DBの小計 | 1,104.14 | 1,361.44 | 1,361.44 |
| 円換算の小計 | 約16.6万円 | 約20.4万円 | 約20.4万円 |
クラウド別のSKU・単価・計算式
単価と計算の根拠は次のとおりです。HA単価にstandbyが含まれる場合は、さらに2倍していません。
-
GCP:ノードは
e2-standard-4を0.13402284ドル/時。Cloud SQL Enterprise N2のHAをvCPU 0.0826ドル/時、メモリ0.014ドル/GiB時として、1系統あたり730 × (2 × 0.0826 + 8 × 0.014)。HA SSDは0.000465753ドル/GiB時です。regional Standardのクラスタ管理料は0.10ドル/時で、zonal向け無料枠は適用していません。Compute料金、Cloud SQL料金、GKE料金 -
AWS:ノードは
m6i.xlargeを0.192ドル/時。RDS PostgreSQLdb.m6i.largeの単一standby付きMulti-AZは0.356ドル/時、gp3はHA込み0.23ドル/GB月。EKSの標準バージョン管理料は0.10ドル/時です。EC2単価例、RDS公式価格データ、EKS料金 -
Azure:ノードは
Standard_D4s_v5を0.192ドル/時。PostgreSQL Flexible ServerStandard_D2ds_v5は1台0.178ドル/時で、primaryとstandbyの2台分を計上。ストレージも1台0.115ドル/GB月を2台分、AKS Standardは0.10ドル/時です。単価はRetail Prices APIのeastus・Consumptionから取得しました。Azure公式料金API、PostgreSQLのHA課金
ロードバランサ、WAF、NAT、IP、ノードのディスク、通信、監視ログ、追加バックアップ、秘密情報・コンテナイメージの保管費は、この小計に別途加算します。
例えば図で採用したAzure Application Gateway WAF_v2は、eastusの固定料金だけで0.36 × 730 = 262.80ドル/月、さらに容量単位の料金が加わります。GCP・AWSの入口も、要求数・処理量等に応じた追加費用があります。Application Gateway料金、Cloud Load Balancing料金、AWS ELB料金
初期検討用に、入口・通信・監視・ディスク等へ月200〜600ドルを仮置きします。Azureには上記のWAF固定料金も加算します。合計は次のとおりです。この200〜600ドルは私が置いた予算枠で、各社の定額料金でも、指定トラフィックを積算した見積もりでもありません。
| 仮予算・月額 | GCP | AWS | Azure |
|---|---|---|---|
| 小計+追加費用の仮枠(丸め) | 約1,300〜1,700ドル | 約1,600〜2,000ドル | 約1,800〜2,200ドル |
| 1ドル150円の仮換算 | 約19.5〜25.5万円 | 約24〜30万円 | 約27〜33万円 |
大量の通信やログではこの範囲を超えます。写真ストレージ・CDN、メール・SMS送信、税、有償サポート、開発・運用の人件費も別です。東京など別リージョンに置く場合は、同じ台数・SKUで再見積もりします。リージョンや構成が変われば、費用の順も変わり得ます。
IDaaSに置き換えると、何が減るか
IDaaSは、認証機能をサービスとして利用する選択肢です。今回の構成でKeycloakを置き換えるなら、次のように費用が変わります。
| 費用・運用 | Keycloak自前運用 | IDaaSへ置換 |
|---|---|---|
| 認証サーバと認証DB | 自分で配置・更新・復旧 | 原則としてIDaaSの料金へ移る |
| OpenFGA・認可DB・Outbox | 必要 | 今回の共有モデルを残すなら必要 |
| 業務API・業務DB・入口 | 必要 | 引き続き必要 |
| サーバの固定費 | 台数・スペックに応じる | ノードを縮小できた分だけ減る |
| 認証の利用料 | Keycloak自体のMAU課金なし | MAUやプラン・追加機能に応じる |
IDaaSの料金だけと、OSS構成全体の費用を比べてはいけません。 また、KeycloakのPodを削除しても、同じ3ノードを維持すればVM料金は減りません。独立した認証DBを1系統削除できる場合、今回の条件で削減できる計算資源・ストレージはGCPで月245.88ドル、AWS・Azureで289.32ドルです。
MAU別の認証サービス料金
MAUは月間アクティブユーザー数です。登録者数や毎秒ログイン数とは異なり、何を「アクティブ」と数えるかもサービスごとに確認が必要です。ここでは一般消費者向けのメール/パスワード・標準のソーシャルログインを想定し、SMS・企業向けSAML/OIDC連携・M2M・高度な追加機能は含めません。
| 認証部分のみ・月額USD | 1万MAU | 10万MAU | 100万MAU |
|---|---|---|---|
| Google Cloud Identity Platform:Tier 1 | 0 | 275 | 4,415 |
| Amazon Cognito:Lite、新規アカウントの無料枠 | 0 | 495 | 4,635 |
| Amazon Cognito:Essentials | 0 | 1,350 | 14,850 |
| Microsoft Entra External ID:Basic | 0 | 1,500 | 28,500 |
Identity Platformは最初の5万MAUが無料、次の5万が0.0055ドル、次の90万が0.0046ドルです。Tier 2のOIDC/SAMLは外部IdPとの連携に対する別料金で、「アプリがOIDCを使うからTier 2」という区分ではありません。Identity Platform料金
Cognito Liteは今回の新規条件で最初の1万MAUが無料、10万までの残り9万が0.0055ドル、その次の90万が0.0046ドル。Essentialsは無料枠を超えたMAUが0.015ドルです。旧アカウント向けの無料枠や高度なセキュリティ機能の追加料金は混ぜていません。両プランの機能は同一ではありません。Cognito料金・機能差
External ID Basicは最初の5万MAUが無料、超過分は0.03ドルです。単価はMicrosoftのRetail Prices APIでBasicの段階料金を確認しました。旧Coreの割引単価や、従来のAzure AD B2Cの料金とは分けています。External ID料金、公式料金API
Auth0も候補です。Freeは最大25,000 MAUですが、有償プランの機能・API制限・SLAは別途比較が必要です。10万・100万MAUは必要機能を指定して見積もり、料金ページの最小プラン価格を人数比例で外挿しません。Auth0料金
10万MAUで、同じノードを維持したまま置き換えると
10万MAU・同じノード数の試算では、IDaaSへの置換でインフラと認証料の合計は増えます。 その増額分より、減らせる保守作業の費用が大きければ、総額ではIDaaSの方が安くなります。
認証DBを1系統削除し、OpenFGA・業務DB・3ノードは残すと仮定します。入口等は両方式とも除外し、先ほどの計算資源+DB小計と同じ範囲で比べます。
| 月額USDの小計 | Keycloak構成 | 残るインフラ+IDaaS認証料 |
|---|---|---|
| GCP+Identity Platform | 1,104.14 | 858.26+275=1,133.26 |
| AWS+Cognito Lite | 1,361.44 | 1,072.12+495=1,567.12 |
| AWS+Cognito Essentials | 1,361.44 | 1,072.12+1,350=2,422.12 |
| Azure+External ID Basic | 1,361.44 | 1,072.12+1,500=2,572.12 |
料金の比較例なので、この台数で10万MAUを処理できるかは未検証です。 月間利用者数だけでは、ピークの必要容量もAPI呼び出し量も決まりません。
10万MAUの例では、Keycloak構成に対する増額は月約29ドル、206ドル、1,061ドル、1,211ドルです(表の順)。保守を任せるために月いくら追加で払うかは、サービスやプランによって大きく違います。各プランの機能差も含めて選びます。
1万MAUなら上記IDaaSの認証料は無料枠内なので、独立した認証DBをなくせる分は下がります。利用者が増えたときにOSSが有利になる可能性はありますが、Keycloak側の増強費用と運用工数を一定のまま置く比較はできません。
自前運用とIDaaSのメリット・デメリット
IDaaSなら、認証サーバと認証DBの保守を任せられます。 どの作業が減り、何が残るかを整理しました。
| 比較する点 | Keycloakを自前運用 | IDaaSを利用 |
|---|---|---|
| 認証サーバの更新・脆弱性対応 | 更新時期を選べる。更新・動作確認・切り戻しは自分で行う | 基盤の更新は事業者が担当。アプリのSDK・連携コードの保守は残る |
| 認証DBの保守・復旧 | 復元手順を制御できる。マネージドDBでも、復旧後のKeycloakとの整合確認は必要 | 基盤DBの保守・復旧を任せられる。誤削除したアカウントの復元やデータ抽出は仕様次第 |
| 負荷増加・可用性 | 容量・冗長化を設計できる。増強・切り替えの検証も自分で行う | 基盤の容量管理・冗長化を任せられる。利用上限・SLAの確認や上限緩和申請は必要 |
| 障害対応 | 自分で調査・復旧できる。対応要員と手順が必要 | 基盤の復旧は事業者が担当。障害の検知・問い合わせ・利用者への案内は残る |
| 費用 | MAU課金なし。設備費・保守人件費が必要 | 運用負担を減らせる。MAU・追加機能に応じた利用料がかかる |
| 認証フロー・機能 | 拡張の自由度が高い。独自実装の保守も必要 | 提供機能を利用できる。カスタマイズやプランに制約がある |
| 移行・データの扱い | 配置先・DBを制御しやすい。移行作業は自分で行う | 保管場所・データ抽出・認証情報の移行可否を確認する必要がある |
| アプリの認証設定・業務認可 | トークン検証、認証設定、OpenFGA・共有解除の実装と運用が必要 | 左記の作業は残る。API・認可基盤の費用も引き続き必要 |
例えばCognitoは、基盤のOS・DBへのパッチ適用や災害復旧をAWSが担当すると説明しています。一方、アプリケーションの安全性や利用者側の設定は利用者の責任です。上表の具体的な責任範囲・復旧機能は、採用するサービスと契約で確認します。Cognitoの基盤保守、アプリ側に残る責任
費用は、保守作業も含めた合計で比べます。
Keycloak運用 = インフラ費 + 認証の更新・監視・障害対応費
IDaaS利用 = 残るインフラ費 + IDaaS利用料 + 連携・運用費
例えば、Keycloakを維持するための追加作業を月8時間、人件費単価を1時間1万円と仮定すると月8万円です。工数は仮定ですが、保守費用がインフラの差額を上回る場合もあります。IDaaS側にも、上表の連携・運用費を計上します。
また、本人IDの対応、トークンのissuer・audience・検証方法、ログインUI、MFA、アカウント移行の方式を確認する必要があります。特にIdentity PlatformのようにSDKを含めた連携方式が変わる場合、KeycloakのURLを差し替えるだけの移行にはなりません。
認証に独自の要件が少なく、運用担当も限られるなら、まずIDaaSを検討します。認証フローや配置先を細かく決めたい場合や、既にKeycloakを運用できる体制がある場合は、自前運用も費用と合わせて比べます。どちらでログインを実装しても、共有ルールをOpenFGAで管理するかは別に選べます。
では、この構成を本番で採用するか
共有ルールを複数APIで使い回したいなら、この構成を検討します。ただし、実験用の構成をそのまま本番には使いません。 OpenFGAやDBが落ちたときの切り替えと、バックアップを戻した後に共有権限を確認する手順が、まだ足りないためです。
所有者だけが操作できればよいサービスなら、APIと業務DBから始める方が構成は単純です。OpenFGAを使う場合に追加で試したいことを、次の表にまとめました。
自分のサービスで確かめること
以下は、今回未検証の項目も含む本番前の試験案です。停止時間や応答時間の合格値は、そのサービスで利用者をどこまで待たせてよいかに合わせて決めます。
| 何を試すか | どうなればよいか | 満たせなければ見直す箇所 |
|---|---|---|
| ピーク時の閲覧・編集に、ログインと更新を重ねる | 判定を誤らず、予定件数を処理し、目標時間内に応答する | サーバ容量、DB接続数、再試行、負荷生成器の能力 |
| 閲覧を続けながら共有を解除する | 定義した解除完了後の新規要求を拒否し、反映中の待ち時間も許容内 | Outbox、キャッシュ、変更中の拒否、完了通知 |
| 冗長化したOpenFGAの1台を止める | 誤許可せず、残るインスタンスで目標の処理量を維持する | 振り分け、タイムアウト、残るサーバの容量 |
| 認証・業務・認可それぞれのDB障害と切り替えを試す | 障害の影響が想定内に収まり、許容時間内に回復する | DBの共有範囲、HA、接続の再確立 |
| Keycloak停止を鍵キャッシュ更新の前後で試す | 新規ログインへの影響と、ログイン済み利用者が操作できる範囲が設計どおり | JWKS取得失敗時の扱い、鍵失効、タイムアウト |
| バックアップ後に共有解除し、別環境へ復元する | アクセス再開前に最新の解除を反映・照合でき、確認できない対象は公開しない | 変更履歴の保全、復元後の照合、再開手順 |
| 権限変更も混ぜて数時間〜日単位で動かす | メモリ・接続・未処理イベントが増え続けず、応答時間も目標内 | メモリの内訳、接続管理、ワーカーの処理能力 |
登録・メール確認・パスワード再設定・MFA・外部IdP、不正ログイン対策、一覧API、監査・秘密情報のローテーションは今回の試験範囲外です。月間可用性、復旧までに許容する時間(RTO)、失ってよいデータの時間幅(RPO)も事業上の目標を置き、別途検証する必要があります。
負荷試験では応答時間を満たせましたが、障害や復元まで試すと、追加で作るべきものが見えてきました。特に、DBの復元成功と、アクセスを再開してよいことは別でした。再開前に最新の共有権限まで確認する手順を、構築時から用意しておきたいところです。
実装と測定を詳しく確認したい方へ
公開しているのは架空ユーザーを使ったコードと集約結果です。トークン、秘密鍵、DBダンプ、生ログは含めません。固定版の実験報告には、API遅延を「TLS込み」とする表現や、Keycloak停止の試行差をトークン寿命に帰する説明が残っています。この記事ではコードレビューに基づき、上記の測定境界と未切り分け事項に訂正しています。