マルチテナントSaaSの極限:Pulumi Automation APIによる「テナント完全分離」動的プロビジョニングの深淵
テックリードの君なら、こんな絶望的な状況に直面したことがあるはずだ。
「新規エンタープライズ顧客の獲得に伴い、データ漏洩リスクを完全に排除するため、テナントごとに完全に独立したAWS環境(VPC、RDS、ECS)をデプロイしてほしい。ただし、プロビジョニングは数分以内に完了させ、営業部が管理画面からポチるだけで自動完結するようにしてくれ」
……正気か?これを静的なHCL(Terraform)や手動のPulumi CLI操作でやろうものなら、CI/CDパイプラインのコードは破綻し、Stateファイルの管理地獄でSREチームは全員鬱になる。
静的なStack管理の限界を突破し、Pulumi Automation APIを用いて「コードとしてインフラを動的に生成・実行する」次世代のアーキテクチャを解説しよう。
—
1. テナント増加に伴うPulumi Stack管理の課題とスケーラビリティの限界
一般的なPulumiの運用では、`Pulumi.dev.yaml` や `Pulumi.prod.yaml` のように、環境ごとにStackを手動または静的パイプラインで定義する。しかし、B2B SaaSにおいてテナント数が「数十〜数千」のオーダーに達した瞬間、このパラダイムは完全に崩壊する。
静的Stack管理が破綻する3つの理由
1. コード生成の爆発(Code Generation Explosion): テナントごとにStackファイルを用意すると、Gitリポジトリが数千のYAMLファイルで汚染される。
2. State競合とロックの嵐: 単一のモノリシックなPulumiプロジェクトで全テナントを管理すると、1つのテナントの障害調査や更新が、他のテナントのデプロイブロックを引き起こす。
3. IAM権限の過剰付与(Blast Radiusの未分離): 万が一、CI/CDの権限が侵害された場合、全テナントのインフラが人質に取られる。
解決策:
テナント管理を「Gitリポジトリ上のファイル管理」から切り離し、データベースでテナントのメタデータを管理し、APIサーバー(またはLambdaワーカー)がPulumi Automation APIを叩いて動的にStackを生成・実行するアーキテクチャへ移行するのだ。
—
2. Automation APIを利用した動的Stack生成・プロビジョニングパイプライン
Pulumi Automation APIは、CLIのラッパーではなく、Pulumiエンジンをプログラム(TypeScript / Python / Go)から直接ドライブするためのSDKである。これを使えば、「コード自体を動的に合成し、インメモリでStackを初期化・実行」できる。
以下のTypeScriptコードは、テナントIDを受け取り、オンデマンドで専用のVPCとRDSインスタンスをプロビジョニングする Automation API ワーカーの核心部だ。
実装例: 動的プロビジョニング・ワーカー (`provisioner.ts`)
import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
import { LocalWorkspace } from “@pulumi/pulumi/automation”;
interface TenantConfig {
tenantId: string;
tier: “standard” | “enterprise”;
dbInstanceClass: string;
}
/
- テナントごとのインフラストラクチャを定義するPulumiインラインプログラム
/
function createTenantInfra(config: TenantConfig) {
return async () => {
// テナント専用VPCの構築 (Blast Radiusの完全隔離)
const vpc = new aws.ec2.Vpc(`tenant-${config.tenantId}-vpc`, {
cidrBlock: “10.100.0.0/16”,
enableDnsHostnames: true,
enableDnsSupport: true,
tags: { TenantId: config.tenantId, ManagedBy: “AutomationAPI” },
});
const subnet = new aws.ec2.Subnet(`tenant-${config.tenantId}-subnet`, {
vpcId: vpc.id,
cidrBlock: “10.100.1.0/24”,
tags: { TenantId: config.tenantId },
});
// エンタープライズ顧客向けの高可用性RDSインスタンス
const dbSubnetGroup = new aws.rds.SubnetGroup(`tenant-${config.tenantId}-dbsg`, {
subnetIds: [subnet.id],
tags: { TenantId: config.tenantId },
});
const db = new aws.rds.Instance(`tenant-${config.tenantId}-db`, {
engine: “postgres”,
instanceClass: config.dbInstanceClass,
allocatedStorage: 20,
dbSubnetGroupname: dbSubnetGroup.name,
skipFinalSnapshot: true,
// 本番ではKMSキーやパスワードをSecrets Managerから動的にインジェクションすること
username: “dbadmin”,
password: “SuperSecretPassword123!”,
});
// 出力変数のエクスポート(API経由で親システムへ返却される)
return {
vpcId: vpc.id,
dbEndpoint: db.endpoint,
};
};
}
/
- テナントプロビジョニングを実行するメイン関数
/
export async function provisionTenantInfrastructure(config: TenantConfig) {
const stackName = `tenant-${config.tenantId}`;
const projectName = “saas-tenant-engine”;
// 1. Automation APIのワークスペースをインメモリ(またはローカル一時ディレクトリ)で初期化
const ws = await LocalWorkspace.createOrSelectStack({
stackName: stackName,
projectName: projectName,
// インラインでプログラムを渡すことで、外部のPulumiプロジェクトファイルを不要にする
program: createTenantInfra(config),
});
// 2. バックエンド(S3/DynamoDBなど)の設定
await ws.setAllConfig({
“aws:region”: { value: “ap-northeast-1” },
});
console.log(`[Automation API] Starting deployment for stack: ${stackName}`);
// 3. べき等性を担保したUp(apply)の実行
const upResult = await ws.up({
onOutput: (out) => console.log(`[Pulumi Output] ${out}`),
});
console.log(`[Automation API] Deployment completed for stack: ${stackName}`);
return upResult.outputs;
}
このアプローチにより、テナント追加のリクエストが飛んできた瞬間に、APIサーバーがこの関数を非同期実行し、完全に分離されたステートとリソース群が数分で立ち上がる。
—
3. 障害ドメインの分離とコスト最適化を両立するマルチテナント基盤の設計パターン
マルチテナントSaaSのアーキテクチャ設計において、最大の難所は「コスト(高密度化)」と「安全性(完全分離)」のトレードオフだ。Pulumi Automation APIを活用することで、テナントの契約プラン(Tier)に応じた動的なリソーストポロジーの切り替えが可能になる。
テント階層別の設計パターン
| 項目 | Standard Tier (相乗り/Siloed-Pool) | Enterprise Tier (完全分離/Silo) |
| :— | :— | :— |
| compute | ECS Fargateタスクのネームスペース分離 | 専用ECS Cluster + 専用VPC |
| Database | Aurora Serverless v2のスキーマ分離 | 専用Amazon RDS (マルチAZ) |
| Pulumi Stack | 単一Stack内で論理リソースを動的生成 | テナントごとに独立したStack (`tenant-xxx`) |
| 障害ドメイン | 狭い(同一DB障害の影響を受ける可能性あり) | 完全に独立(100%隔離) |
コスト最適化の自動化
Enterpriseテナントが解約された場合、管理画面からのシグナルを受けてAutomation APIが `ws.destroy()` を実行し、さらに `ws.stack.destroy()` でStack自体を完全に消去する。これにより、ゾンビリソースによるクラウドコストの無駄撃ちをシステム的に根絶できる。
—
4. プロの現場で差がつく!Pulumi開発効率を爆上げするプラクティス
ここからは、日々の開発・運用で圧倒的なスピードと安全性を叩き出すための「プロの隠し技」を伝授する。
⌨️ 開発スピードを加速する VS Code ショートカット & 設定
Pulumi開発において、TypeScriptの型補完を制する者がインフラを制する。
- Cmd + Shift + P (Mac) / Ctrl + Shift + P (Windows) -> “TypeScript: Restart TS Server”: インラインプログラムや複雑なカスタムコンポーネントを書く際、型の解決がおかしくなった瞬間に叩く。
- `.vscode/settings.json` の最適化:
{
“editor.codeActionsOnSave”: {
“source.organizeImports”: “explicit”
},
“typescript.suggest.completeFunctionCalls”: true
}
🔌 絶対導入すべき神プラグイン(VS Code)
1. Pulumi (公式): リソースのホバープレビュー、スタック情報の可視化に必須。
2. Error Lens: インフラコードの型エラーやプロパティのタイポをコード行の末尾にインライン表示。デプロイ前のコンパイルエラー検知率が跳ね上がる。
3. AWS Toolkit: 生成されたVPCやRDSの状態をIDEから直接ビジュアル確認。
👥 チーム開発で絶対守るべき設定の共有化ルール
マルチテナント環境のAutomation APIを複数人で開発・運用する場合、環境のブレは即座に本番障害に直結する。
1. Node.js / Pulumi CLIバージョンの厳密な固定 (`package.json`):
“engines”: {
“node”: “>=18.0.0”,
“npm”: “>=9.0.0”
},
“volta”: {
“node”: “18.19.0”,
“npm”: “10.2.3”
}
Voltaやnvmを強制し、開発者間のランタイム差異をゼロにする。
2. 共通型定義(Tenant Schema)のモジュール化:
テナントのコンフィグ構造体はモノレポの `packages/shared-types` として切り出し、APIサーバーとプロビジョニングワーカー間で厳密に型共有する。これにより、API層とインフラ層のパラメータ齟齬をコンパイルタイムで完全に防ぐ。
—
結び
静的なIaCの時代は終わった。
数千のテナントを抱えるSaaSにおいて、インフラは「静的な設定ファイル」ではなく、「APIを通じて動的にコンパイル・実行されるアプリケーションコード」の一部でなければならない。
Pulumi Automation APIをマスターした君のチームは、ビジネスの急拡大に対して、背筋が凍るほどのスピードと、鉄壁の堅牢性を備えたインフラ基盤を提供できるようになるはずだ。さあ、コードを書こう。