[レポート] SaaSアーキテクチャーのパターンを学ぶ #reinvent #ARC306

2022.01.17

re:Invent2021のセッション、「SaaS architecture patterns: From concept to implementation」を聴講したのでレポートします。

セッション概要

タイトル

SaaS architecture patterns: From concept to implementation

セッションID

ARC306

スピーカー

  • Tod Golding : Principal Partner Solutions Architect, SaaS Factory AWS

紹介文 和訳

SaaSのアーキテクチャは、ドメイン、スタック、顧客要件によって異なります。しかし、SaaSソリューションが対応しなければならない明確なパターンが存在します。アイデンティティ、オンボーディング、テナントの分離、データパーティショニング、tier… これらはAWSのスタックやサービスによって実装は異なりますが、ほとんどのSaaSソリューションにとってコアとなるパターンです。このセッションでは、これらのSaaSパターンを詳しく見ていきます。異なるコンピュート、ストレージ、アイデンティティ、ネットワーキング、管理の概念を用いて、これらのパターンを最適化するアーキテクチャーやコードの組み合わせをご紹介します。

レポート

SaaSに鉄板構成は存在しない

  • ベストプラクティスアーキテクチャーみたいなものは存在しない
  • 要求を満たす、異なるアーキテクチャのメカニズムやコンポーネントの組み合わせ。その可能性を網羅し、最良の選択肢を見つけ出す必要がある
  • 本セッションではその際に役立つパターンや、それらのパターンのカテゴリーを提供する

SaaSを構成するグループ

grouping

1. マネジメント

以下のような要素を如何に管理していくか

  • メトリクス
  • 分析
  • 請求
  • プロビジョニング

2. アプリケーション

マルチテナンシーに関するポイントが多い

  • 環境分離方法
  • データ分離方法
  • デプロイ方法
  • ルーティング

3. テナンシー

SaaS環境内に如何にしてテナンシーを設けるか

  • アイデンティティ
  • オンボーディング = 新テナント作成時のプロセス
  • Tier = テナントのランク、機能差みたいなもの

決定因子

上記グループそれぞれで採用パターンを検討する際に考慮点になる要素たち

  • ドメイン要件
  • ビジネスゴール
  • 市場投入までの時間
  • レガシーな考慮点

2つのドメイン

SaaSのアーキテクチャーを考えるにあたって、大きく2つのドメインが存在する。コントロールプレーンとアプリケーションプレーン。 two-halves

コントロールプレーン

  • テナント郡を操作、管理する場所
  • Shared Seriviceを配置する場所
  • コントロールプレーンはマルチテナントではない

アプリケーションプレーン

  • マルチテナントビジネスサービスを配置する場所
  • データ分離を実装する場所
  • 構成検討に多くの労力を割かれる場所

サイロ、プール、ブリッジ

以降頻出する用語の解説。 silo-pool-bridge

サイロ

  • テナントのリソースがそのテナント専有になっている構成
  • たとえ全リソースがサイロ構成なSaaSであっても、それを管理・操作するコントロールプレーンがあるのならばマルチテナントSaaSといえる

プール

  • サイロの真逆
  • リソースを共有する構成

ブリッジ

  • サイロとプールの組み合わせ
  • すべてがサイロ、もしくはプールな構成のSaaSは殆どないはず

コントロールプレーンのパターン集

オンボーディングについて

新テナントの追加方法について考える。以下のような処理が必要になるはず。

onboarding

  1. レジストレーションサービスがリクエストされる。図ではLambda関数になっているが、ECS、EKS、EC2かもしれない。要はレジストレーション用のマイクロサービスがあるということ。
  2. レジストレーションサービスからテナントマネジメントサービスがコールされる。テナントマネジメントはテナントにまつわるすべての情報を設定する。IDやポリシーなど。
  3. ユーザーのプロビジョンも行なう。システム上の最初のユーザー、Adminのようなユーザーをセッティングする。
  4. そしてそのユーザーの設定はIDプロバイダーに連携される。図ではCognitoを使っているが他のIDプロバイダーの場合もある。カスタムクレームを作成し、ユーザーアイデンティティとテナントアイデンティティとを連携させる。
  5. ユーザーに適用するポリシーのプロビジョニングも必要。このタイミングでは無い場合もあるが、遅かれ早かれ必要なステップ。
  6. 課金システムも立ち上げなければならない。
  7. 請求プロバイダーへと連携する。
  8. プロビジョニングステップ。すべてがプール構成になっているのであれば、オンボーディングでインフラを全く作る必要がないかもしれない。
ティアドリブンオンボーディング

テナント登録時に、tierを同時に決める→tierによってオンボーディングプロセスが左右される。

例えば以下の例。basic tierとplatinum tierがある。basic tierはプール構成になっており、テナント追加時には新たなインフラリソースをプロビジョニングする必要がない。既存のリソースを使う。一方でplatinum tierはサイロ構成。新しいテナント追加時には、そのテナント専有のリソースをプロビジョニングするために、プロビジョニングプロセスをキックする必要がある。 test-driven-onboarding

アイデンティティについて

コントロールプレーンに関するもう一つの主要部分。

マルチテナントシステムの場合、テナントを意識したアイデンティティを持つことが非常に重要。 どのIDプロバイダーに対しても認証できるようにするだけでは不十分。 環境にテナント性を導入するような方法で認証する必要がある。

tenant-aware-identity

  1. テナントユーザーがウェブアプリにアクセスした場合、
  2. アイデンティティプロバイダーにリダイレクトし認証を受ける。
  3. 認証できるとIDトークンとアクセストークンが返される。自分がどのユーザーなのかだけでなく、属しているテナントやtierなどといった情報が得られる。
  4. ユーザーはトークンをウェブアプリに渡す。
  5. アプリケーションの中でテナント情報などのコンテキストが必要になった場合、わざわざ別の場所に移動して入手する必要はない。JWTトークンを開いて中を覗けばOK。
    また、別のサービスを呼び出す場合にも、同じトークンを次のサービスに渡すだけで良い。

これがテナント性のあるIDという概念。テナントのコンテキストを皆がどの工程でも手元に置き、必要なときに利用する。

以下は認証フローの例。

tenant-aware-identity-flow

  1. テナント(ユーザー)がSaaSアプリケーションに入ってくる。
  2. SaaSアプリケーション自体は何に対して認証するのかわかっていないので、テナントマネジメント(コンポーネント)に(ユーザー情報を)渡す。ここ(テナントマネジメント)はID設定やその他の情報、どのユーザープールに対して実行されているかなどを保存している場所。
  3. 上記情報をアプリに返す。(おそらく矢印は逆方向が正?)
  4. アプリが上記情報を認可ライブラリに渡す。
  5. 認可ライブラリがそのテナントコンテキストを使用して Cognitoユーザープールでユーザーを認証する。ここで使うユーザープールは、前述のテナントマネジメントから取得したもの。
  6. コードが返される。
  7. そのコードがJWTと交換される。
  8. JWTを使ってプロダクト内で認証する。
テナントごとにユーザープールを作るべきか、1ユーザープールにまとめるか
  • テナントごとにユーザープールを作る場合
    • 利点:テナントごとに異なるポリシーを設定できる(=カスタマイズできる)
    • 欠点:テナントとユーザープールのマッピングを実装する必要がある
  • 1ユーザープールの場合
    • 利点欠点は上記「テナントごとにユーザープール」の逆
ハイブリッドアイデンティティモデル

一部の顧客が自身のアイデンティティを使いたい場合。 hybrid-identity-model

  1. テナント(ユーザー)はまず認証マネージャーにアクセス。
  2. 認証マネージャーはテナント設定にアクセスして、そのテナントが自分のアイデンティティを持っているのかそれともSaaSが用意したアイデンティティを持っているのか確認する。そして判明したアイデンティティを使ってユーザーを認証。
  3. 自分のアイデンティティで認証した場合、必要なコンテキストが揃っているとは限らない。そこでアイデンティティエンリッチメントマネージャーにて不足しているコンテキストを補い、JWTを更新する。
  4. あとは通常のフロー。後段のアプリはJWTを使って認証する。
マルチリージョンアイデンティティ

マルチリージョンでSaaSを展開したい場合はどのようなアイデンティティの構成が良いのか?

multi-region-identity

一つのアプローチは、リージョン跨ぎの共通のフロントドアを用意し、集約型の認証を持つ方法。認証後はコンテキストを各リージョンに渡す。

しかしながら、この種のモデルは、コンプライアンスやリージョンに関する配慮が必要な組織では、アイデンティティを共有の集中化された場所にホストすることができないため、うまくいかない。

multi-region-identity2

そこで別案。フロントドアを単にユーザーとリージョンをマッピングするものに変え、アイデンティティは各リージョンに移動させる。

ユーザー管理

2種類のユーザー管理が必要。

  • SaaSプロバイダーの管理者権限ユーザー
  • テナントユーザー

請求設定・計装

以下は請求設定のプロセス。 billing-configuration

  1. 外部請求プロバイダーにアカウントをセットアップする。
  2. プランの設定をする。何に対して課金するか(サブスクリプション、利用量、ユーザー数…)など
  3. テナントをSaaSにオンボードする。
  4. テナントを外部請求プロバイダーと紐付ける。
  5. テナントのイベントやアクティビティをキャプチャする。
  6. キャプチャ内容に基づいて請求する。

以下は課金イベントを外部請求プロバイダーStripeに送る構成の案。 billable-events EventBridgeを使っている。EventBridgeからイベントをアグリゲーターのLambda関数に送り、関数はデータベースにイベントを保存しつつ、定期的にその情報をStripeに送る。「アグリゲーター関数やデータベースは不要。直接Stripeに転送すればいいじゃないか」との意見もあるかと思う。しかし、請求の詳細を把握したい場合がある。(外部請求プロバイダーの)課金システムの信頼性だけに頼って、すべての要求をうまく処理したいわけではない。

メトリクスの計装、集約

SaaS事業者が、ユーザーが利用している機能をどのように把握するかや、システム運用の健全性についての情報をどのように把握するかといったこと。これらのデータはSaaSビジネスやSaaSのアーキテクチャーなどを評価するのに欠かせない。

metrics-aggregation-analytics メトリクスの集計、分析については、クラシックな例を2つ挙げる。ひとつはKinesis、Redshift、QuickSightとAWSサービス郡を活用する例。もうひとつはLogstash、Elastic Search、Kibana。

metrics-instrument より興味深いのはメトリクスの計装の側面。少なくとも2つのカテゴリーのイベントがある。一つはシステムレベルのメトリックイベント、CPUやメモリの使用率といったもの。もう一つはアプリケーションイベント。アプリケーションの各機能がどのように利用されているかについての情報。

アプリケーションプレーンインテグレーション

コントロールプレーンは各アプリケーションプレーンとやり取りする必要がある。前述の通り各アプリケーションプレーン(テナント)はサイロだったりプールだったりする。それらとどのようにしてやり取りするのか?ということ。EventBridgeを使うとか、PrivateLinkを使うとか色々なやり方がある。アプリケーションのデプロイモデルに拠って構成を決めることになるだろう。

アプリケーションプレーンのパターン集

サイロ vs プール

フルスタックサイロ

環境内のあらゆるリソースが特定のテナント専有になっている構成のこと。

テナントの境界点案
  • AWSアカウント
    • 利用制限を設けたり自動化が大変になるかも
    • Control Towerを使うことができる
    • コストトラッキングがやりやすい
  • VPC
    • 1アカウントに全テナントがいることで、アカウントごとのモデルに比べてやや運用が効率的になり、自動化もしやすくなる
  • サブネット
    • 前述2案に比べるとレア
EKSでコンピュート層をサイロ化するパターン

クラスターで分ける per-cluster 名前空間で分ける per-ns (サイロやめて)プールモデル eks-pool

サーバーレスのサイロ・プールパターン

serverless-silo-pool

  • サイロにすると
    • Lambda関数の並列実行数の制限の心配が少ない
    • リソース分離がやりやすい
サイロ/プールはリソース毎に選択できる

コンピュート層はサイロだけどストレージ層はプール、みたいなこと

resource-based

テナントの分離(アイソレーション)

分離(=他のテナントの情報にアクセスさせない)パターンの紹介

リソースレベルの分離(サイロ)

テナントごとに異なるリソースを使う resource-level-isolation

  • 他のパターンに比べて分離が楽
  • デプロイ時に分離点が決められる(デプロイ駆動リソース分離)
    • デプロイ時にリソースにアクセス制限ポリシーを設定する
    • コードは分離の事を考えなくて良い(場合が多い)
アイテムレベルの分離(プール)

DBリソースを共有している例。DB内のアイテムはテナントごとに分離 item-level-isolation

  • 対象によってアイテムレベルの分離方法は異なるので難しい
実行時強制分離

アイテムレベルの分離をする場合、リソースレベル分離のようにデプロイ時に分離点を決められない。コードが実行される際に分離点を判断する必要がある。 runtime-enforce-isolation

tierベーススロットリング

あるテナントが他のテナントに影響を与えるような負荷を発生させるわけにはいかない。

tier-based-throttling API GatewayのAPIキーと使用量プランを使用する例。tier毎にAPIキーとそれに紐づく使用量プランを分けることで、tier毎にスロットリングのポリシーを分離できる。

マルチテナントマイクロサービスライブラリー

各マイクロサービスでは、これまで話したようなテナントに関することを個々で実装することはしたくない。マイクロサービスは可能な限りシンプルに実装したい。代わりに、どのマイクロサービスでも共通して使えるライブラリーにしてそれを利用する形にする。

例えばLambda関数であればLayerを使える。 layer

データパーティショニング

ドメイン駆動データパーティショニング

マイクロサービス毎にパーティショニング方法が異なっていても良い。それぞれのマイクロサービス毎にノイジーネイバー、パフォーマンス、レイテンシー、テナント分離の要件が異なる場合があるから domain-driven-data-partitioning

まとめ

  • SaaSに鉄板構成は存在しない
  • マルチテナンシーはshared serviceを必要としない
  • コントロールプレーンはSaaSのアジリティとイノベーションのためには必須
  • 各AWSスタック・サービスごとに異なるアプローチが必要
  • サイロorプールはリソースもしくはマイクロサービスレベルで適用可能
  • Tier、パフォーマンス、ノイジーネイバーが、どのパターンを適用すべきかの因子になる
  • 要件に合う最良のパターンの組み合わせを見つけましょう

感想

SaaSのアーキテクチャーを考えるのは論点がとても多くかつそのどれもが難しく、だからこそやりがいのある仕事だなと感じました。ここで紹介されたことを忘れず検討しないといけませんね。

セッション動画