はじめに
閲覧ありがとうございます。
本記事は、ついにCDKに入門した私が初めて扱っているライブラリ「SaaS Builders Toolkit for AWS(SBT)」について学ぶにあたり作成しています。
同じようにSBTを初めて扱う方の参考になれば幸いです。
また、この記事は
- SBT公式ドキュメント
- いつもお世話になっている当社の先輩、 小杉さんのQiita記事
を参考にさせていただいています<(_ _)>
SaaS Builders Toolkit for AWS(SBT)って何?
AWS が公開している、CDKを基盤として構築されたオープンソースの開発者ツールキットです。
簡単に言うと、「マルチテナントなSaaSアーキテクチャを構築するのに絶対必要となるコードをベストプラクティスを考慮した形で再利用可能にしてくれているライブラリ」です。
※マルチテナント = ある一つのシステムを複数のユーザー(テナント)で共有するモデル。同じDBに複数のテナントの情報が入るようなシステムのこと
SBTは、以下の2つの明確な構成要素を持ちます。
-
Control Plane
コントロールプレーンとは、複数のテナントに対し個別の環境を払い出すにあたり必要な共通処理(アプリケーションインフラ展開のイベント発火 etc.)の実行や、テナントのアカウント情報の管理などテナントを横断した管理を行う役割を持ちます。 -
Application Plane
アプリケーションプレーンとは、各テナント内におけるアプリケーションの展開を実行したり、実際にアプリケーションを使用するテナント内のユーザの管理を行うなど、顧客ごとのアプリケーション基盤としての役割を持ちます。
Application Planeスタックをテナントごとに実行するイメージです。
本記事では、Control PlaneとApplication Planeはアカウント単位で境界を持つものとします。
また、各テナントの境界もコンテナごとやVPCごとではなく、AWSアカウントごとに持つものとします(=1テナント1アカウント)。
CDKの記述方法を見てみよう
以降のコードは公式のREADMEに記載されているチュートリアルのコードにコメントを挿入したものです。
コントロールプレーンの構築
CDKリポジトリの/lib/配下にコントロールプレーン用スタックを作成します。
// sbtライブラリをimport
import * as sbt from '@cdklabs/sbt-aws';
import { Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';
// コントロールプレーン用のスタックを作成
export class ControlPlaneStack extends Stack {
// 外部から引っ張ってこれるようpublic readonlyで宣言
public readonly regApiGatewayUrl: string;
public readonly eventManager: sbt.IEventManager;
constructor(scope: Construct, id: string, props?: any) {
super(scope, id, props);
//「CognitoAuth」という認証コンポーネントを作成
const cognitoAuth = new sbt.CognitoAuth(this, 'CognitoAuth', {
enableAdvancedSecurityMode: false, // Cognitoが提供するAdvanced Security機能の有効化有無
setAPIGWScopes: false, // API Gatewayの権限制限の有無
});
// コントロールプレーンをAWS上にまるごと構築
const controlPlane = new sbt.ControlPlane(this, 'ControlPlane', {
auth: cognitoAuth, // 連携するCognitoAuth
systemAdminEmail: 'ENTER YOUR EMAIL HERE', // システム管理者にするユーザのメールアドレス
});
// controlPlaneコンポーネントで作成したイベントバスを公開
this.eventManager = controlPlane.eventManager;
// controlPlaneコンポーネントで作成したAPIのURLを公開
this.regApiGatewayUrl = controlPlane.controlPlaneAPIGatewayUrl;
}
}
ポイントについて解説です。
-
new sbt.CognitoAuth
CognitoAuthは、SBTが提供する、SaaSアプリケーションに必須の認証(ログイン)とユーザー管理の仕組みをまとめて作成・設定してくれるコンポーネントです。
setAPIGWScopesをtrueにすると、Cognitoで認証されたユーザしかAPIにアクセスできないようにすることが可能です。
※ここではCognitoを用いた認証を実装していますが技術的には任意のIDプロバイダーでこのインターフェースを実装することも可能です -
new sbt.ControlPlane
この1行で、数十個ものAWSリソースが連携したテナントの管理基盤が自動で組み立てられます。
具体的には以下に示すようなリソースが作られています
-
テナントのメタデータを保存するためのAmazon DynamoDBテーブル
- 契約している企業名、テナントID、料金プランなどを管理します
-
プロビジョニングに必要なAPI(API Gateway)とLambda
- テナントのプロビジョニングをAPI経由で実現できるよう、テナント一覧取得のためのAPI, テナント追加用のAPIなどを作成します
- 新規テナント登録リクエストを受け取るとLambda関数が起動し、環境プロビジョニングを開始します
-
システム内のイベントを中継するバス(Event BridgeのEvent Bus)
- テナント登録イベントをトリガーにして、別のスタックを駆動させたりすることが可能です
-
this.eventManager = controlPlane.eventManager;で代入しているeventManagerの中身にあたります
-
CognitoAuth と連携
-
systemAdminEmailで指定したメールアドレスでCognito上にユーザを自動作成し、その管理者にアプリケーションのAdmin権限を付与します
-
/bin/ 配下のファイルでコントロールプレーンスタックを呼び出す
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
// コントロールプレーンスタックをimport
import { ControlPlaneStack } from '../lib/control-plane';
const app = new cdk.App();
// コントロールプレーンスタックのコンストラクタ作成
const controlPlaneStack = new ControlPlaneStack(app, 'ControlPlaneStack');
アプリケーションプレーンの構築
CDKリポジトリの/lib/配下にアプリケーションプレーン用スタックを作成します。
SBTはデプロイ先のアプリケーションについて特に意見を持たないため、SBTが提供するのはコントロールプレーンとの接続部分のみで、特に何も機能はしません。
export interface AppPlaneProps extends cdk.StackProps {
// コントロールプレーンスタックで作成したイベントバスを受け取る
eventManager: sbt.IEventManager;
}
export class ApplicationPlaneStack extends Stack {
constructor(scope: Construct, id: string, props: AppPlaneProps) {
super(scope, id, props);
// アプリケーションプレーンをAWS上にまるごと構築
new sbt.CoreApplicationPlane(this, 'CoreApplicationPlane', {
// コントロールプレーンで作成したイベントバスを指定
eventManager: props.eventManager,
scriptJobs: [
// アプリケーションのプロビジョニング処理等を記述
],
});
}
}
ポイントについて解説です。
① eventManager: sbt.IEventManager; / eventManager: props.eventManager,
コントロールプレーンで使用したのと同じEventBusを使用して作成したEventManagerを渡しています。これにより、両方のプレーンがAmazon EventBridgeの同じイベントに接続されます。
② scriptJobs: []
コントロールプレーンメッセージを受信した際に任意のジョブを定義して実行することができます。
/bin/ 配下のファイルでアプリケーションプレーンスタックの呼び出しを追加する
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { ControlPlaneStack } from '../lib/control-plane';
// アプリケーションプレーンスタックをimport
import { AppPlaneStack } from '../lib/app-plane';
const app = new cdk.App();
const controlPlaneStack = new ControlPlaneStack(app, 'ControlPlaneStack');
// アプリケーションプレーンを呼び出し(コントロールプレーンのeventManagerを引数として渡す)
const appPlaneStack = new AppPlaneStack(app, 'AppPlaneStack', {
eventManager: controlPlaneStack.eventManager,
});
2つのプレーンのインターフェース
上記のコードから、2つのプレーン間ではEventBridgeがインターフェースとなっていることがわかると思います。
たとえば、コントロールプレーンから以下のようなオンボーディングのリクエストイベントが発行されるとします。
{
"source": "controlPlaneEventSource",
"detail-type": "onboardingRequest",
"detail": {
"tenantId": "guid string",
"tenantName": "tenant$RANDOM",
"email": "tenant@example.com",
"tier": "basic",
"tenantStatus": "In progress",
"tenantRegistrationId": "guid string"
}
}
上記のイベントを受け取ったアプリケーションプレーンは、オンボーディング処理を開始します。
そして、処理が完了すると、以下のような完了イベント(例)を発行します。
{
"source": "applicationPlaneEventSource",
"detail-type": "provisionSuccess",
"detail": {
"jobOutput": {
"tenantStatus": "created",
"tenantConfig": "{\n \"userPoolId\": \"MY_SAAS_APP_USERPOOL_ID\",\n \"appClientId\": \"MY_SAAS_APP_CLIENT_ID\",\n \"apiGatewayUrl\": \"MY_API_GATEWAY_URL\"\n}",
"tenantName": "tenant$RANDOM",
"tenantS3Bucket": "mybucket",
"someOtherVariable": "this is a test",
"email": "tenant@example.com"
},
"tenantId": "guid string"
}
}
これをコントロールプレーンが結果イベントを受け取ってまた次の処理を発火する、という流れになります。
まとめ
- SBTは、大きく分けると「Control Plane」と「Application Plane」の2つの要素から構成されている
- Control PlaneとApplication PlaneはEvent Bridgeをインターフェースとしており、共通のものを指定して使用することで相互にイベント発火して処理を自動化している
- Control Planeではプロビジョニング処理を行うためのAPIとLambda、認証用のCognito(&テナント管理用DynamoDB)が作成される
- SBTはマルチテナントなSaaSのインフラ基盤を構築するときに必要となる処理をまとめて簡単に再利用できるようにしてくれているライブラリ
最後までお読みいただき、ありがとうございました。
まだまだCDKもSBTも勉強を始めたてですので、これからたくさん触って学んでいきたいと思います!