【入門編】PulumiでマルチテナントSaaS環境におけるテナントごとの独立したStack生成と動的プロビジョニング自動化アーキテクチャ – インフラ構成管理(IaC)活用バイブル

こんにちは!クラウドインフラの世界へようこそ。
今日は、私たちが日々頭を悩ませている「マルチテナントSaaS環境におけるインフラ管理」のブレイクスルーについて、お話ししようと思います。

「新しいテナント(顧客)が増えるたびに、手動でリソースを作ったり、複雑なスクリプトを叩いたりしていませんか?」
「テナント数が数百、数千にスケールしたとき、TerraformのStateファイルやPulumiのStack管理がパンクしそうになっていませんか?」

今回は、世界中のSREが頭を抱えるこの課題に対し、「Pulumi Automation API」という最強の武器を使って、テナントの追加を完全に自動化・動的プロビジョニングするアーキテクチャの核心を解説します。

これをマスターすれば、深夜のテナント追加作業から解放され、あなたのインフラ基盤は劇的に美しく、強靭に生まれ変わりますよ。それでは、一緒に深淵を覗いてみましょう。

—

1. テナント増加に伴うPulumi Stack管理の限界

まず、敵を知ることから始めましょう。
Pulumiの基本単位である「Stack(スタック)」は、開発・ステージング・本番といった環境を分離するのに非常に強力です。しかし、これを「テナントごと」に適用しようとすると、すぐに壁にぶつかります。

静的なStack管理の絶望

通常、私たちは `pulumi stack init tenant-a`, `pulumi stack init tenant-b` のように、手動あるいは静的なパイプラインでStackを定義します。しかし、SaaSの顧客が毎日のように増減する環境でこれをやるとどうなるでしょうか?

1. CI/CDパイプラインの肥大化: 新規テナントが増えるたびにGitHub ActionsやGitLab CIのワークフローファイルや設定を書き換えるのは、スケーラブルではありません。
2. Stateの爆発: 数千のStackが並ぶと、バックエンドのストレージ(S3やPulumi Service)の管理や検索が地獄絵図になります。
3. 障害ドメインの設計ミス: コスト最適化のためにリソースを共有しすぎると、一社のバグや負荷が「隣の部屋」のテナントを巻き込んでダウン(ノイジーマイナー問題)を引き起こします。

ここで求められるのは、「人間がStackを意識せず、プログラム(API)が動的にStackを生成・実行するアーキテクチャ」です。それを実現するのが Pulumi Automation API です。

—

2. Automation APIとは何か?(基礎セットアップ)

Pulumi Automation APIは、CLIをラップするのではなく、Pulumiのエンジンそのものをプログラム(Go, TypeScript, Python等)から直接埋め込み、実行するためのライブラリです。

これを使うと、「テナントからのリクエストをトリガーに、コード側でStackを動的に組み立て、AWS等のリソースをプロビジョニングして、結果を返す」という神業が、数行のコードで書けるようになります。

最速のHelloWorld:TypeScriptでAutomation APIを動かす

まずは、環境構築と最もシンプルな動作確認から行いましょう。ここではTypeScriptをベースに進めます。

前提条件

  • Node.js (v18以降)
  • Pulumi CLI インストール済み
  • AWS等のクラウド認証情報が設定済み

プロジェクトの初期化

適当なディレクトリを作成し、プロジェクトを初期化します。

mkdir pulumi-tenant-provisioner
cd pulumi-tenant-provisioner
npm init -y
npm install @pulumi/pulumi @pulumi/aws
npm install -D typescript @types/node ts-node
npx tsc –init

動的プロビジョニングの心臓部 (`index.ts`)

ここでは、特定のテナント名を受け取り、そのテナント専用のS3バケットを動的に作成・デプロイするスクリプトを書きます。

import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
import { localWorkspace } from “@pulumi/pulumi/automation”;

async function provisionTenantInfrastructure(tenantId: string) {
// 1. スタック名を動的に決定(例: tenant-acme-corp)
const stackName = `tenant-${tenantId}`;
const projectName = “SaaSTenantProvisioner”;

console.log(`[1/3] 🚀 テナント ‘${tenantId}’ のワークスペースを準備中…`);

// 2. インメモリでPulumiプログラムを定義(これがAutomation APIの真髄)
const pulumiProgram = async () => {
// テナントごとに完全に独立したS3バケットを生成
const bucket = new aws.s3.Bucket(`data-bucket-${tenantId}`, {
bucket: `my-saas-tenant-${tenantId}-data`,
tags: {
Environment: “Production”,
TenantId: tenantId,
},
});

// 出力値を定義
return {
bucketName: bucket.id,
bucketArn: bucket.arn,
};
};

// 3. Automation APIを使ってLocalWorkspaceを作成し、Stackを初期化/選択
const ws = await localWorkspace({
projectSettings: {
name: projectName,
runtime: “nodejs”,
backend: {
// 本番ではS3やPulumi Serviceなどの永続バックエンドを指定してください
url: “s3://my-pulumi-state-bucket-foo”,
},
},
// コードをプログラム内から直接渡すため、別ファイルのindex.tsインポートも可能です
program: pulumiProgram,
});

// Stackが存在しない場合は作成し、存在するなら選択する
const stack = await localWorkspace.selectStack({
stackName: stackName,
projectName: projectName,
program: pulumiProgram,
});

console.log(`[2/3] ⚙️ テナント ‘${tenantId}’ のインフラをデプロイ中…`);

// 4. プレビューなしで直接アップデートを実行(自動化パイプライン用)
const upResult = await stack.up({
onOutput: (out) => console.log(`[Pulumi Engine] ${out}`),
});

console.log(`[3/3] ✨ デプロイ成功!`);
return upResult.outputs;
}

// 実行テスト(HelloWorld)
async function main() {
try {
const tenantId = “acme-corp”;
const outputs = await provisionTenantInfrastructure(tenantId);
console.log(“生成されたリソース情報:”, outputs);
} catch (error) {
console.error(“プロビジョニングに失敗しました:”, error);
}
}

main();

これを `npx ts-node index.ts` で実行してみてください。
たったこれだけのコードで、Pulumi CLIをバイパスし、プログラム自身がStackのライフサイクル(Init -> Select -> Up)を完全に制御してAWS上にリソースを作り上げます。これをAPIサーバー(NestJSやFastAPIなど)に組み込めば、テナント登録APIと連動した動的プロビジョニング基盤の完成です。

—

3. 障害ドメインの分離とコスト最適化を両立する設計パターン

さて、動的にStackを生成できるようになったところで、SREとしての「アーキテクチャの美しさ」の話をしましょう。
マルチテナントSaaSにおいて、すべてのテナントを全く同じリソース構成(完全分離型)にすると、小規模なテナントに対してもコストがかかりすぎて破産します。逆に、すべてを共有すると「隣のテナントのバグのせいで自社データが消えた」という大惨事(ノイジーマイナー)が起きます。

ここで私たちが取るべきは、「階層型テナント分離アーキテクチャ」です。

[テナント追加リクエスト (API Gateway)]
│
▼
[プロビジョニング・ワーカー (Automation API)]
├── [Tier 1: Enterprise] → 完全分離Stack(専用VPC, 専用DB, 専用S3)
└── [Tier 2: Standard] → 名前空間・タグ分離Stack(共有EKS + 専用DBスキーマ)

1. ティアに応じた動的テンプレートの切り替え

Automation APIのプログラム(`pulumiProgram`)をテナントのプラン(Enterprise / Standard)に応じて動的に切り替えます。

  • Enterpriseプラン: セキュリティとSLAが最優先。テナントごとに専用のAWS VPC、専用RDSインスタンスをデプロイするPulumiコードを動的生成。
  • Standardプラン: コスト最適化が最優先。共通のEKSクラスタ内にテナント用のKubernetes Namespaceを切り、DBは共有RDSの「専用スキーマ(Schema)」を作成するコードを実行。

2. 冪等性(Idempotency)とステート管理の鉄則

動的プロビジョニングにおいて最も恐ろしいのは、「二重実行によるリソースの競合やデータロス」です。
Automation APIを使用する際は、以下の鉄則を必ず守ってください。

  • スタック名の命名規則を厳格化する: `tenant-{tenantId}` のように予測可能でユニークな名前空間にし、すでに同じStackが存在する場合は `up` が安全に差分検知(No-op)するようにする。
  • ロック機構の実装: 同じテナントに対して同時に2つのリプロビジョニング走らないよう、RedisやDBのトランザクションで排他制御(Distributed Lock)をかける。Pulumi自体にもステートロック機能はありますが、API層での多重実行を防ぐのがSREの優しさです。

—

先輩エンジニアからのエール

お疲れ様でした!
Pulumi Automation APIを使った動的プロビジョニングの概念と、その実装の第一歩を掴んでいただけたでしょうか。

「テナントが増えるたびにインフラエンジニアが手動でコードを書き換える時代」は、今日で終わりです。このアーキテクチャを導入すれば、営業部門が新しい大口顧客を獲得した瞬間、バックエンドのAPIが自動でPulumiを叩き、数分後にはその顧客専用の安全な要塞(インフラ)が組み上がっている――そんな美しい自動化の世界が実現します。

最初は難しく感じるかもしれませんが、動くコードを手元で動かしたときの感動は格別です。
あなたのSaaS基盤が、よりスケーラブルで、より堅牢になることを心から応援しています。日々のインフラ開発を、もっとエキサイティングに楽しんでいきましょう!

タイトルとURLをコピーしました