Amazon Web Services ブログ
AWS サーバーレスサービスによるマルチテナント SaaS ソリューションの構築
この記事は、Building a Multi-Tenant SaaS Solution Using AWS Serverless Services を翻訳したものです。
本投稿は、AWS SaaS Factory の Sr. Partner Solutions Architect である Anubhav Sharma と AWS SaaS Factory の Partner Solutions Architect である Ujwal Bukka により寄稿されました。
SaaS (Software-as-a-Service) 提供モデルへの移行に際しては、コストと運用効率を最大限に高めたいという要望が伴います。
これは、利用傾向を予測することが困難なマルチテナント環境では特に難しい場合があります。なぜならば、テナントの活動とリソースの実際の消費量を一致させるスケーリング戦略の組み合わせを見つけることは困難だからです。今日はうまくいっていても、明日はうまくいかないかもしれません。
このような特性により、SaaS はサーバーレスモデルに非常に適していると言えます。SaaS のアーキテクチャからサーバーの概念を取り除くことで、企業はマネージドサービスを利用することによって、アプリケーションが消費するリソースの正確な数をスケーリングして提供することができます。
これにより、アプリケーションのアーキテクチャと運用のフットプリントが簡素化され、スケーリングポリシーを継続的に追跡・管理する必要がなくなります。また、運用上のオーバーヘッドや複雑さも軽減され、運用責任の多くをマネージドサービスに委ねることができます。
この記事では、機能的なマルチテナントのサーバーレス SaaS 環境に関してエンドツーエンドで提供するリファレンスソリューションを見ていきます。その目的は、このリファレンスソリューションを作成する際に考慮されたアーキテクチャと設計の検討事項を探ることです。
加えて本記事では、Amazon API Gateway、Amazon Cognito、AWS Lambda、Amazon DynamoDB、AWS CodePipeline、Amazon CloudWatch などの Amazon Web Services (AWS) のサーバーレスサービスを使用して、サーバーレスモデルを活用する方法をご紹介します。
リファレンスソリューションでは、オンボーディング、テナント分離、データパーティショニング、テナントのデプロイパイプライン、可観測性 (observability) など、マルチテナントの SaaS ソリューションを構築するために必要なコンポーネントの多くが示されています。GitHub リポジトリを使用して、AWS アカウントにリファレンスソリューションをデプロイし、確認することができます。
マルチデプロイモデルへの対応
このサーバーレス SaaS リファレンスソリューションの詳細を説明する前に、このエクスペリエンスでサポートされるさまざまなデプロイモデルの概要を説明しておきます。このソリューションには、「専用 (サイロ)」と「共有 (プール)」という 2 つのデプロイモデルが含まれており、これらのモデルが、サーバーレス SaaS 環境のオンボーディング、アイソレーション、ノイジーネイバー、パフォーマンス、ティアリングプロファイルにどのような影響を与えるかを明らかにしています。
専用モデルでは、各テナントが独自のインフラストラクチャリソースを持っていることがわかります。一方、共有モデルでは、すべてのテナントが共通のストレージとコンピュートのインフラを共有しています。これらの分離モデルについては、「SaaS Tenant Isolation Strategies whitepaper」で紹介しています。
このサーバーレス SaaS ソリューションでは、テナントがどのモデルを使用するかを決定するためにプラン (tier) を使用しています。Basic、Standard、Premium の各プランのテナントは、共通のリソースを共有します。Platinum プランのテナントは、独自の専用リソースを持ちます。
ベースライン環境のデプロイ
README ファイルには、サーバーレス SaaS のベースライン環境をデプロイするための手順が示されています。
インストール手順では、ベースライン環境の一部であるすべてのリソースがプロビジョニングされます。これは、専用テナントと共有テナントの両方のオンボーディングを開始するのに必要なインフラストラクチャとリソースを表しています。このベースラインインフラストラクチャの図を以下に示します。
図 1 – ベースラインデプロイのフットプリント
マルチテナント型のサーバーレス SaaS ソリューションを実行するために必要なすべてのアプリケーション、サービス、インフラストラクチャがエンドツーエンドで表現されていることがわかります。この後のセクションでは、この環境の主要な要素について説明していきます。
Webアプリケーション
環境のバックエンドサービスとやり取りする 3 つの異なるアプリケーションが構築されていることがわかります。これらのアプリケーションの構築には Angular を使用しています。
「SaaS provider admin console」は、SaaS プロバイダの管理者が使用するアプリケーションです。「Landing/sign-up application」は、新しいテナントが自分自身を登録するための一般向け登録ページとして機能します。「Sample SaaS commerce application」は、典型的な e コマースアプリケーションを表しています。
このアプリケーションには、SaaS アプリケーションをシミュレートするための最小限の機能も含まれており、商品や注文を作成・更新することができます。
共有サービス
この環境には、アプリケーションのオンボーディング、テナント、およびユーザー管理を担当する一連の共有サービスも含まれています。
「共有」という名前は、これらのサービスが SaaS 環境の基盤であり、アプリケーションサービスとは別に、すべてのテナントで共有される横断的な機能を提供するという概念を表しています。つまり、テナントの登録、管理、認証、設定などの操作やデータは、これらの共有サービスによって処理されます。
アプリケーションサービス
アプリケーションサービスは、アプリケーションのビジネス機能を提供するマイクロサービスの表現です。前述のように、これらのアプリケーションサービスのデプロイと役割は、テナントの各プランに応じて変化します。
今回のベースライン環境では、共有モデルを使用するプランのテナントが利用するアプリケーションサービスをデプロイしました。その後、Platinum プランのテナントを導入すると、このプランの各テナントに個別のアプリケーションサービスが展開されることがわかります。
マルチテナント型データストレージ
本アプリケーションのマルチテナントアプリケーションサービスでは、Amazon DynamoDB を使用してマルチテナントデータを保存しています。共有テナントについては、複数テナントのデータが混在する共有 DynamoDB テーブルに共有構造でデータを格納します。このアプローチでは、DynamoDB のアイテムと個々のテナントを関連付けるパーティションキーの導入が必要です。
このアプローチの詳細については、こちらのブログ記事をご覧ください。
サーバーレスマイクロサービスの概念
このアーキテクチャをよりよく理解するためには、サーバーレス環境でマイクロサービスをどのように構成するかという概念を理解する必要があります。確かに、それぞれの関数がマイクロサービスになることもありますが、論理的なマイクロサービスを表す関数のコレクションを持つことの方が一般的です。
このケースでは、マイクロサービスの境界は Amazon API Gateway であり、1つ以上の Lambda 関数によって支えられています。例えば、注文を作成、読み取り、更新、削除するための個別の関数を持つ注文サービスを考えてみましょう。これらの機能はすべて同じデータで動作するため、論理的なマイクロサービスとしてまとめられるべきです。
以下の図は、この概念を高レベルで表現したものです。さらにこの概念を詳しく理解するために、このホワイトペーパーを参照することをおすすめいたします。
図 2 – サーバーレスマイクロサービス
テナントの登録とオンボーディング
ベースラインアーキテクチャについて理解したところで、この環境にテナントがどのように導入されるかを見てみましょう。テナントは、サインアップ Web アプリケーションを使って自ら登録することができます。テナントの登録フローは、サインアッププロセスでテナントが選択したプラン (tier) によって若干異なります。
次の図は、テナント登録の流れと、テナント登録サービスが他のサービスを利用してテナント登録をどのように調整するかを示しています。
図 3 – テナント登録の流れ
サインアッププロセスの手順は以下の通りです。
- テナントがサインアップの詳細とプラン (tier) を提供します。登録サービスはプランを調査し、テナントが Platinum プランを選択した場合は、サイロモデル (専用リソース) でオンボーディングを行うフラグが付けられます。それ以外のプランを選択した場合、そのテナントはプールモデル (共有リソース) にオンボードされるようフラグが立てられます。
- 登録サービスは、ユーザー管理サービスを呼び出し、新しいテナント管理ユーザーを作成します。Platinum プランのテナントでは、各テナントに個別のユーザープールが用意されていることがわかります。他のプランは共通のユーザープールを共有していますが、その単一のユーザープール内で異なる Cognito グループが割り当てられています。テナント ID とユーザーロールは、Cognito 内のカスタムクレームとして保存されます。
- 登録サービスはテナント管理サービスを呼び出し、テナントの詳細を保存します。このプロセスの一部として、オンボーディング時に選択されたテナントプラン (tier) に基づいて、API キーをテナントに関連付けます。
- 最後に、テナントプロビジョニングサービスがテナントパイプラインを呼び出し、Platinum/サイロのテナント専用のインフラを提供します。
テナント登録サービスは、API Gateway リソースポリシーを使用して、ユーザー管理、テナント管理、プロビジョニングサービスに対して認証を行います。
テナントデプロイパイプライン
このソリューションでは、AWS CodePipeline を使用して、アプリケーションサービス (この例では Product と Order サービス) のデプロイを管理しました。
このパイプラインは、CI/CD アプローチを使用してテナントインフラストラクチャの作成と更新を行います。コードを main ブランチに publish/merge すると、(図 4 のように) パイプラインが自動的にトリガーされ、ソースをビルドし、必要なユニットテストをすべて実行し、すべてのテナントのサービスが自動的にデプロイされます。
図 4 – テナント CodePipeline
このデプロイ手順の一環として TenantStackMapping テーブルが導入されました。このテーブルはパイプラインの中心となるもので、テナントの展開方法を制御するテナントとプラン (tier) のマッピングを提供します。
共有テナント (Basic、Standard、Premium の各プランのテナント) は、ベースラインインフラストラクチャのデプロイの一環として、共有されたスタックごとにこのテーブルのエントリが格納されます。その後、専用テナント (Platinum プラン) がオンボーディングされると、システムは各専用テナントに対してこのテーブル内にエントリを作成します。CodePipeline は、必要に応じてこのテーブルを参照してスタックを作成および更新します。
アーキテクチャの最終確認
1 つまたは複数の Platinum プランのテナントをシステムにプロビジョニングすると、アーキテクチャにはこれらのテナントをサポートする新しいインフラが追加されます (図 5)。ここでは、最初のプロビジョニングスクリプトの一部としてデプロイされた共有インフラに加えて、Platinum プランテナント専用のインフラが用意されます。
このモデルでは、Platinum プランの各テナントは、独自の Cognito ユーザープール、個別の API Gateway、個別の Lambda 関数群、および独自の DynamoDB テーブルのセットを取得します。
図 5 – 最終的なデプロイのフットプリント
API の認証とテナントの分離
サーバーレス SaaS リファレンスソリューションは、様々なメカニズムを活用してセキュリティを管理し、テナントの活動を制御します。図 6では、このソリューションが Lambda オーソライザー、Amazon Cognito、動的なアイデンティティとアクセス管理 (IAM) ポリシー、および STS サービスの組み合わせを使用して、これらのコントロールを実装していることがわかります。
図 6 — 認可フロー
このフローの始まりは、テナントが Amazon Cognito で認証を行い、JWT トークンを発行することから始まります (Step 1 と 2)。この JWT は、API Gateway で処理される各リクエストに渡されます (Step 3)。API Gateway が着信すると、Lambda オーソライザーを使用してリクエストの検証と認可を行います (Step 4)。
オーソライザー内では、ユーザーのロールに基づいてルートを有効/無効化し、そのユーザーに対して有効でないルートへのアクセスを阻止します (Step 5)。また、JWT からのテナントコンテキストを使用して、テナントのスコープされた認証情報を取得して Lambda 関数に引渡し、関数がアクセスするリソースにテナントスコープのアクセスを適用するために使用します (Step 6)。
テナント分離ポリシーの生成
認可プロセスの一環として Lambda オーソライザーは、そのテナントに固有の IAM ポリシーに基づいて短期間の認証情報を生成します。これらのポリシーは、入力されたテナント ID に基づいて Lambda オーソライザーによって動的に生成されます。
以下は DynamoDB テーブルのテナント分離ポリシーの例を示す JSON スニペットです。
{
"Effect": "Allow",
"Action": [
"dynamodb:UpdateItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:{0}:{1}:table/Product-*".format(region, aws_account_id),
],
"Condition": {
"ForAllValues:StringLike": {
"dynamodb:LeadingKeys": [
"{0}-*".format(tenant_id)
]
}
}
}
このスニペットにおける「tenant_id」変数は、入力されたテナント ID に置き換えられるプレースホルダーです。その結果、そのテナントにのみ関連するポリシーが取得されます。
Lambda オーソライザーはこの IAM ポリシーを使用して STS サービスの assumeRole メソッドを使用してきめ細かな短期間の認証情報を生成することで、そのテナントのみのデータへのアクセス権を持つシークレットアクセスキーが生成されます。Lambda オーソライザーは、Lambda コンテキストの一部としてダウンストリーム Lambda 関数にこれらの短期間の認証情報を提供します。
このプロセスの効率を高めるために、Lambda オーソライザーは JWT トークンに基づいて設定可能な、期限付き認証情報をキャッシュします。
テナント分離の適用
このアプリケーションの異なる (プランによる) 階層化とデプロイモデル (専用と共有) は、サーバーレス SaaS ソリューションの分離のストーリーにも影響を与えます。共有テナント層は、Lambda オーソライザーによって生成されたクレデンシャルに依存して、共有アプリケーションサービスが必要とするきめ細かいアクセスコントロールを提供しています。
専用テナントの場合は、テナントごとに個別の DynamoDB テーブルをプロビジョニングしているため、戦略が少し異なります。この場合、テナントの DynamoDB テーブルにアクセスする際に、スコープ付きの認証情報を適用する必要はありません。代わりに、専用の Lambda 関数のプロビジョニング時に適用されるテナント実行ロールによって、そのテナントにプロビジョニングされた特定のテーブルへのアクセスが制限されます。
次の図は、このバリエーションを示しています。図 7 では、分離モデルはオーソライザーによって提供されるスコープの認証情報を使用して、共有 DynamoDB テーブル内の DynamoDB アイテムへのアクセスを制限しています。
図 7 — テナント分離 — 共有 (プール) モデル
一方、図 8では、Lambda 関数に関連付けられた実行ロールを使用して、Amazon DynamoDB テーブルへのアクセスを制御しています。
図 8 — テナント分離 — 専用 (サイロ) モデル
テナントスロットリング
SaaS プロバイダーは、システムを利用するテナントのプランごとに異なる機能を提供するのが一般的です。スロットリングは通常、幅広いプラン戦略の一環として行われます。例えば基本プランのテナントは、上位プランのテナントの機能に影響を与える能力を制限するスロットリングポリシーがあります。
サーバーレス SaaS のサンプルソリューションでは、これらのテナントプランに基づく戦略を API Gateway を通じて適用します。API Gateway では、プランを特定のプランに結びつけるための API キーに関連付けられる使用量プランを作成します。この例では、各テナントプラン (Basic、Standard、Premium、Platinum) に 1 つの使用量プランを用意しています。これらの使用量プランごとに個別の API キーを関連付けて、プランベースのスロットリング制限を設定しています。
お客様のシナリオに応じて、例えばテナントの数が限られている場合には、テナントごとに 1 つの使用量プランを用意することも可能です。
Lambda レイヤーを使用したテナント可観測性
原則として SaaS ソリューションは、マルチテナントに関連する多くの詳細を隠すライブラリやモジュールを導入することで、マイクロサービスの開発を可能な限りシンプルにしようとしています。例えば、ロギング、メトリクス、データアクセスなどはすべてテナントコンテキストを必要としますが、開発者がこのテナントコンテキストを管理/アクセス/適用するために、それぞれのサービスで一回限りのコードを作成することは避けたいと考えています。
サーバーレス環境では、このような共通のマルチテナントニーズに対応するために再利用できる共通のコードを導入することができる自然なメカニズムがあります。サーバーレスの SaaS アーキテクチャでは、Lambda レイヤーを使って、ロギングやメトリクス収集を一元化しています。
リファレンスソリューションでは、マイクロサービスに共通するニーズに対応するためにレイヤーを導入しています。例えば、JWT トークンからテナント ID を抽出するためのレイヤーです。また CloudWatch に、パブリッシュされるテナントの情報を含むログやメトリクスを記録するためのコードをレイヤーに使用しています。
またリファレンスソリューションでは、トレースに AWS X-Ray を活用し、X-Ray の注釈を利用して、テナントごとにトレースを区別しています。最後に、CloudWatch Logs Insight を使ってログを照会し、これらのメトリクスを要約することもできます。
テナント毎のコスト
テナント ID をログに記録することで、テナントの消費状況、特に共有モデルで運用されているテナントの消費状況をさらに把握することができます。例として、このブログ記事では CloudWatch ログ内の テナント ID に基づいて、テナントあたりのコストを決定するアプローチを順を追って説明します。さらにこの記事では、AWS Application Cost Profiler (ACP)を利用して、テナント間で共有する AWS リソースの詳細なコスト内訳を取得することを紹介しています。
サイロ (Platinum プラン) テナントのコストは、コスト配分タグを使用してコストの内訳を得ることができるので、比較的簡単です。これは、サイロテナントのリソースが共有されていないために可能です。
まとめ
この記事では、AWS サーバーレスサービスを使用して、スケーラブルで安全な SaaS ソリューションを作成する方法について説明しました。また、AWS サーバーレスサービス内のさまざまな機能を SaaS のベストプラクティスにマッピングする方法についてもご紹介しました。
このソリューションはSaaS ジャーニーにおけるスタート地点となり、参考になるでしょう。また、GitHub リポジトリには、このリファレンスソリューションの内部構造に関する詳細なドキュメントが用意されています。アーキテクチャや実装を深く掘り下げる際に役立つことでしょう。
AWS SaaS Factory について
AWS SaaS Factory は、SaaS 導入のどの段階の企業でも支援します。新製品の構築、既存のアプリケーションの移行、AWS 上での SaaS ソリューションの最適化など、どのようなご要望にもお応えします。AWS SaaS Factory Insights Hub では、技術的、ビジネス的なコンテンツやベストプラクティスをご覧いただけます。
また、SaaS 開発者の方は、エンゲージメントモデルや AWS SaaS Factory チームとの連携について、アカウント担当者にお問い合わせください。
こちらにご登録いただくと、最新の SaaS on AWS ニュース、リソース、イベントの情報を入手できます。
翻訳はソリューションアーキテクト 杉本 晋吾 が担当しました。原文はこちらです。