【実務・中級編】PulumiとAWS LocalStackを組み合わせたローカル開発環境の構築:クラウド費用をゼロにするオフラインIaCテスト手法 – インフラ構成管理(IaC)活用バイブル

雲代金0円・数秒でフィードバック:PulumiとLocalStackで極限まで加速するオフラインIaCテストの全貌

テックリードの私たちが日々の開発で最もフラストレーションを感じる瞬間は何か。それは、数行のIaCコードを修正するたびに、クラウド上の実環境へ反映されるまでの数分間をコーヒーを飲みながら待たされる瞬間だ。

「S3バケットとIAMポリシーの連携テストをしたいだけなのに、なぜAWS上のプロビジョニング完了を待たなければならないのか?」
「プルリクのたびに発生する微小なAWSの検証コストを、なぜチームが負担し続けなければならないのか?」

この問いに対する答えが、PulumiとLocalStackの完全融合だ。

今回は、真にモダンで高速なインフラ開発フローを手に入れるための、オフラインIaCテスト環境の構築手法を徹底解説する。モックではない。AWSのAPIを完全にローカルでエミュレートし、本番と全く同じコードで極限のフィードバックループを実現するプロの知見を授けよう。

—

1. なぜ Pulumi + LocalStack なのか?(アーキテクチャの深淵)

多くのチームがいまだにTerraformとAWS公式の`LocalStack`の組み合わせで消耗している。HCLの表現力の限界にぶつかり、複雑な条件分岐やJSONのシリアライズに苦しみ、ローカルエンドポイントの書き換え忘れで本番環境のバケットを吹き飛ばしかける――そんな悲劇はもう終わりにしよう。

TypeScript / Python による真のプログラミング駆動IaC

Pulumiは、HCLのようなDSLではなく、TypeScriptやPythonなどの汎用言語を使える。これにより、インフラの抽象化、テスト駆動開発(TDD)、モジュール化が劇的に容易になる。

LocalStackによる完全なAWSエミュレーション

LocalStackは単なるモックサーバーではない。AWSのIAM、S3、DynamoDBなどのAPIをローカルコンテナ上で忠実に再現し、内部でSQLiteやメモリ上にリソース状態を構築する。

統合のアーキテクチャ

[ Pulumi (TypeScript/Python) ]
│
▼ (AWS SDKのカスタムエンドポイント設定)
[ LocalStack (Docker Container on localhost:4566) ]
│
▼ (オフラインで完結)
[ ローカルストレージ / メモリ ]

この構成の最大の強みは、「コードの一文字も変えずに、エンドポイントの向き先を切り替えるだけで、ローカルとAWS本番を完全に行き来できる」という点にある。

—

2. 実装:AWSプロバイダをローカルに向ける極意

それでは、実際にコードを書いていこう。ここでは最も型安全かつ表現力の高い TypeScript を採用する。

開発環境の前提と神設定

日々の開発スピードを最大化するため、以下の前提環境を整えているものとする。

  • VSCode 拡張機能: `Pulumi` (公式), `Docker`
  • CLI ツール: `localstack`, `pulumi`

必須の`.env`設定 (チーム共有のベストプラクティス)

ローカル開発において、誤って本番環境を叩かないための安全弁(セーフティネット)として、環境変数は厳格に分離する。`.env.local`ファイルをプロジェクトルートに配置せよ。

Pulumiのバックエンドをローカルのファイルシステムに(S3バックエンドすら不要)
PULUMI_BACKEND_URL=file://.~

AWS認証情報のダミー値(LocalStackは認証を厳密に見ないがプレースホルダーが必要)
AWS_ACCESS_KEY_ID=local
AWS_SECRET_ACCESS_KEY=local
AWS_REGION=ap-northeast-1

`index.ts` の実装:エンドポイント上書きの魔法

PulumiのAWSプロバイダ(`@pulumi/aws`)に対し、すべてのサービスのエンドポイントをLocalStackのポート(`http://localhost:4566`)に向ける設定を行う。

ここで重要なのは、環境変数(例: `pulumi.config.requireBoolean(“isLocal”)`)によって、本番用プロバイダとローカル用プロバイダを動的に切り替える構造にすることだ。

import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;

// 設定の読み込み
const config = new pulumi.Config();
const isLocal = config.requireBoolean(“isLocal”);

// LocalStack用のカスタムエンドポイント設定
const localEndpoint = “http://localhost:4566”;

// プロバイダの動的定義(ローカルの場合は全サービスの宛先をLocalStackに偽装)
const provider = isLocal ? new aws.Provider(“local-provider”, {
region: “ap-northeast-1”,
s3UsePathStyle: true, // LocalStackのS3でパススタイルアクセスを強制
skipCredentialsValidation: true,
skipMetadataApiCheck: true,
skipRequestingAccountId: true,
endpoints: [
{
s3: localEndpoint,
dynamodb: localEndpoint,
iam: localEndpoint,
sqs: localEndpoint,
sns: localEndpoint,
},
],
}) : undefined; // 本番時はデフォルトプロバイダを使用

// — リソース定義 —

// 1. S3バケットの作成
const bucket = new aws.s3.Bucket(“my-local-bucket”, {
bucket: “my-offline-test-bucket”,
}, { provider }); // プロバイダを明示的にバインド

// 2. DynamoDBテーブルの作成
const table = new aws.dynamodb.Table(“my-local-table”, {
name: “UserSessions”,
billingMode: “PAY_PER_REQUEST”,
hashKey: “UserId”,
attributes: [
{ name: “UserId”, type: “S” },
],
}, { provider });

// 3. エクスポート(出力値の確認)
export const bucketName = bucket.id;
export const tableName = table.name;

—

3. 高速フィードバックループの構築:検証フローの実装

コードが書けたら、実際に手を動かしてその圧倒的な速度を体感しよう。

ステップ1: LocalStackの起動

Docker Composeを用いて、必要なAWSサービスのみを軽量に立ち上げる。

docker-compose.yml
version: “3.8”

services:
localstack:
image: localstack/localstack:latest
ports:

  • “4566:4566” # LocalStack Gateway

environment:

  • SERVICES=s3,dynamodb,iam
  • DEBUG=0
  • DOCKER_HOST=unix:///var/run/docker.sock

volumes:

  • “${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack”
  • “/var/run/docker.sock:/var/run/docker.sock”

起動コマンド:

docker compose up -d

(コンテナの起動完了まで数秒。AWSのコンソール画面を開く必要すらない。)

ステップ2: Pulumiスタックの設定とプレビュー

ローカル用のPulumiスタックを作成し、設定を有効化する。

ローカル用スタックの初期化
pulumi stack init local-dev

LocalStackモードを有効に設定
pulumi config set isLocal true

差分確認(秒速で完了する)
pulumi preview

実行結果の美しさ:
AWSの実際のAPIを叩きに行くのではなく、ローカルのLocalStackコンテナに対してドライランが行われるため、ネットワーク遅延ゼロで結果が返ってくる。

ステップ3: デプロイと動作確認

pulumi up –yes

数秒でリソースがローカル環境に構築される。

本当に構築されたか? AWS CLIのプロファイルやエンドポイントオプションを使って、ローカルのS3を叩いて確認してみよう。

aws –endpoint-url=http://localhost:4566 s3 ls

出力結果に `my-offline-test-bucket` が表示された瞬間、あなたのインフラ開発のコストとストレスは完全に消え去る。

—

4. チーム開発を加速させる極上のプラクティス

この仕組みを個人のローカルおもちゃで終わらせず、チーム全体のスタンダードにするための「プロの心得」を伝授する。

① チーム共有のための `Pulumi.local-dev.yaml`

設定値はコードとしてリポジトリにコミットし、誰もが同じ手順で再現できるようにする。

Pulumi.local-dev.yaml
config:
my-infra-project:isLocal: true

このファイルを共有しておけば、新メンバーは `git clone` して `docker compose up` し、`pulumi up` を叩くだけで、3分後には手元に完璧なAWS検証環境が完成する。

② CI/CDパイプライン(GitHub Actions)でのオフライン統合テスト

クラウドの認証情報(AWS_ACCESS_KEY_ID等)をGitHub Secretsに設定して本番AWSを汚す必要は、プルリクエストの統合テスト段階では一切ない。

GitHub Actions上でLocalStackコンテナを立ち上げ、その上でPulumiのテスト(Jest等を用いたユニット・統合テスト)を走らせることで、CI/CDの実行時間とAWSの課金を極限まで圧縮できる。

name: Offline IaC Test

on: [pull_request]

jobs:
pulumi-test:
runs-on: ubuntu-latest
steps:

  • uses: actions/checkout@v4
  • name: Start LocalStack

uses: LocalStack/setup-localstack@v0.2.2
with:
services: “s3,dynamodb,iam”

  • uses: actions/setup-node@v4

with:
node-version: ’20’

  • name: Install Dependencies

run: npm ci

  • name: Run Pulumi Up (LocalStack)

uses: pulumi/actions@v5
with:
command: up
stackName: local-dev
comment: false
env:
AWS_ACCESS_KEY_ID: local
AWS_SECRET_ACCESS_KEY: local
AWS_REGION: ap-northeast-1
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}

—

終わりに:インフラエンジニアの自由を取り戻せ

「インフラの変更テストにはクラウドが必須である」という固定観念は、今日この瞬間から捨てよう。

Pulumiの強力なプログラマビリティと、LocalStackのエミュレーション能力を掛け合わせることで、私たちは「飛行機の中でも、新幹線の中でも、あるいはスターバックスのオフライン環境でも、完璧なAWSインフラの設計・テスト・デバッグができる」という圧倒的な自由を手に入れることができる。

クラウド費用はゼロ。待ち時間もゼロ。
プロフェッショナルとして、コードのロジックと向き合う純粋な時間だけがそこにある。

さあ、今すぐターミナルを開き、あなたのローカル環境に仮想のAWS王国を築き上げよう。テックリードであるあなたの手で、チームの開発体験を次の次元へ引き上げるのだ。

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