ゼロトラストの極み:Pulumiで実現する「消えゆくプレビュー環境(Ephemeral Environments)」の完全自動構築と破棄
テックリードの私たちが日々の開発現場で直面する最大のボトルネックは何か?
それは「Staging環境の奪い合い」であり、「誰が作ったか分からない孤児リソース(Orphaned Resources)によるAWS/GCPのクラウドビル急騰」であり、そして「本番と乖離した検証環境でのデバッグに費やす無駄な時間」である。
ゼロトラスト・インフラストラクチャの思想において、リソースは「永続的なもの」ではなく、必要に応じて生まれ、役目を終えた瞬間に跡形もなく消え去る「エフェメラル(一時的)な存在」でなければならない。
本稿では、Pulumiのプログラマイブルなインフラ定義能力とGitHub Actionsを極限まで組み合わせ、「PRごとに完全隔離されたプレビュー環境を秒速で立ち上げ、マージ/クローズと同時に1バイトのゴミも残さず自動破棄する」ための、プロダクションクオリティのCI/CDパイプライン構築手法を解説する。
—
1. エフェメラル環境設計の核心:なぜTerraformではなくPulumiなのか?
「動的なプレビュー環境」を構築する上で、Terraform(HCL)には構造的な限界がある。HCLは静的な宣言言語であるため、PR番号やブランチ名に応じた複雑なリソース名の動的生成、条件分岐、既存VPCとの動的なピアリング設定などを綺麗に記述しようとすると、モジュール地獄に陥る。
一方、PulumiはTypeScriptやPythonといった汎用プログラミング言語を使える。これにより、「インフラストラクチャをアプリケーションコードと同等の表現力で制御する」ことが可能になる。
チーム開発で絶対守るべきPulumiプロジェクト構造のベストプラクティス
エフェメラル環境を安全に量産するためには、コードの重複を避け、かつスタック間の依存関係を完全に分離する必要がある。
infrastructure/
├── package.json
├── tsconfig.json
├── Pulumi.yaml
├── common/ # 共有基盤(VPC, IAM等 – 変更頻度低)
│ └── index.ts
└── ephemeral/ # エフェメラル環境用テンプレート(PRごとにインスタンス化)
├── Pulumi.yaml
└── index.ts
この構成の肝は、「共有基盤(Common)」と「エフェメラル(Ephemeral)」のステートを完全に分離することだ。エフェメラル側は、共有基盤のOutputs(VPC IDやSubnet IDなど)を `pulumi.StackReference` を介して安全に参照する。これにより、プレビュー環境の構築・破棄が共有基盤を巻き込むリスクをゼロにする。
—
2. 実装:TypeScriptによる動的プレビュー環境のコード
以下は、PR番号(例: `pr-42`)をスタック名およびリソースのプレフィックスとして受け取り、独立したVPCサブネット、ECSセキュリティグループ、およびコンテナ用タスク定義を動的にプロビジョニングするPulumiコードの実装例である。
`ephemeral/index.ts`
import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
// 1. 現在のPulumiコンテキストとスタック設定の取得
const config = new pulumi.Config();
const prNumber = config.require(“prNumber”); // CI/CDから渡されるPR番号
const envName = `preview-${prNumber}`;
// 2. 共有基盤(StagingのVPC等)のスタック参照
// ゼロトラストの原則に基づき、既存のセキュアなネットワーク境界を共有利用する
const sharedStackOrg = config.require(“sharedStackOrg”);
const sharedStackName = config.require(“sharedStackName”);
const sharedStack = new pulumi.StackReference(`${sharedStackOrg}/common/${sharedStackName}`);
const vpcId = sharedStack.requireOutput(“vpcId”);
const privateSubnetIds = sharedStack.requireOutput(“privateSubnetIds”);
// 3. エフェメラル環境専用のセキュリティグループ
// この環境へのアクセスは、社内VPNまたは特定のALB経由のみに制限
const ephemeralSg = new aws.ec2.SecurityGroup(`${envName}-sg`, {
vpcId: vpcId,
description: `Security group for Ephemeral Environment PR#${prNumber}`,
ingress: [
{
protocol: “tcp”,
fromPort: 80,
toPort: 80,
cidrBlocks: [“0.0.0.0/0”], // ※実運用ではCloudflare Access等のIPに制限を推奨
description: “Allow HTTP from Proxy/ALB”,
},
],
egress: [
{
protocol: “-1”,
fromPort: 0,
toPort: 0,
cidrBlocks: [“0.0.0.0/0”],
description: “Allow all outbound for package fetching”,
},
],
tags: {
Environment: envName,
ManagedBy: “Pulumi”,
TTL: “24h”, // 自動クリーンアップの目印
},
});
// 4. エクスポート:CI/CDやPRコメントへ動的URLを返すために利用
export const previewMetadata = {
prNumber: prNumber,
securityGroupId: ephemeralSg.id,
createdAt: new Date().toISOString(),
};
—
3. 究極のCI/CDワークフロー:GitHub Actionsによる完全自動化
エフェメラル環境の成否は、「どれだけノーメンテナンスで、確実に作られ、確実に消えるか」にかかっている。途中でビルドがコケてリソースが取り残される「ゾンビリソース」を防ぐため、GitHub Actionsの `pull_request` イベントを精密にハンドリングする。
`.github/workflows/ephemeral-environment.yml`
name: Ephemeral Environment Pipeline
on:
pull_request:
types: [opened, synchronize, reopened, closed]
concurrency:
group: ephemeral-pr-${{ github.event.pull_request.number }}
cancel-in-progress: false # デプロイ中のキャンセルによるリソース破損を防ぐ
permissions:
id-token: write # AWS OIDC認証用
contents: read
pull-requests: write # PRへのコメント自動化に必須
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
AWS_REGION: ap-northeast-1
jobs:
deploy-or-destroy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
cache-dependency-path: infrastructure/package.json
- name: Install Dependencies
working-directory: infrastructure
run: npm ci
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Setup Pulumi CLI
uses: pulumi/actions@v5
- name: Define Stack Name
id: stack
run: |
PR_NUM=${{ github.event.pull_request.number }}
echo “stack_name=org/ephemeral/preview-${PR_NUM}” >> $GITHUB_OUTPUT
echo “pr_num=${PR_NUM}” >> $GITHUB_OUTPUT
# — [CASE A] PRがクローズされた場合:完全削除 (Destroy) —
- name: Destroy Ephemeral Environment (On PR Close)
if: github.event.action == ‘closed’
working-directory: infrastructure/ephemeral
run: |
pulumi stack select ${{ steps.stack.outputs.stack_name }} –create || exit 0
# リソースが存在しない場合のエラーを防ぎつつ確実に破棄
pulumi destroy –yes –non-interactive
pulumi stack rm ${{ steps.stack.outputs.stack_name }} –yes || true
# — [CASE B] PR作成・更新の場合:動的プロビジョニング (Up) —
- name: Deploy Ephemeral Environment (On PR Open/Sync)
if: github.event.action != ‘closed’
working-directory: infrastructure/ephemeral
run: |
# スタックが存在しなければ新規作成、存在すれば選択
pulumi stack select ${{ steps.stack.outputs.stack_name }} –create
# コンフィグの設定(PR番号をコードに渡す)
pulumi config set prNumber ${{ steps.stack.outputs.pr_num }}
pulumi config set sharedStackOrg “org”
pulumi config set sharedStackName “staging”
# プレビューとデプロイの実行
pulumi up –yes –non-interactive
- name: Post Preview URL to PR
if: github.event.action != ‘closed’
uses: actions/github-script@v7
with:
script: |
const prNumber = context.payload.pull_request.number;
const message = `🚀 Ephemeral Environment Deployed!\n- PR: #${prNumber}\n- Status: \`Ready\`\n- Managed by Pulumi`;
// 既存のボットコメントがあれば更新、なければ新規作成
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existingComment = comments.find(c => c.user.login === ‘github-actions[bot]’ && c.includes(‘Ephemeral Environment Deployed!’));
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: message,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: message,
});
}
—
レイヤードされたこのアーキテクチャにより、開発者はインフラの存在を意識することなく、単にコードを書き、PRを投げるだけで、自分専用のセキュアな検証サンドボックスを手に入れることができる。そして、その環境はマージの瞬間にクラウド上から綺麗に消え去る。
これが、PulumiとモダンなCI/CDが生み出す、ゼロトラストかつハイパーアジャイルなインフラストラクチャの到達点だ。コードを書き、世界を自動化せよ。