マイクロサービス時代のPulumi:Stack Referencesで実現する「結合度ゼロ」のインフラアーキテクチャ
こんにちは。テックリードの私だ。
日々のインフラ運用、ご苦労様である。
「たった一つの変更で、全環境のデプロイが止まった」
「アプリケーションを1つ直したいだけなのに、巨大なTerraform/Pulumiのステートファイル(State)ロックに阻まれて絶望した」
――もし君が、このような「モノリスインフラの呪縛」に苦しんでいるなら、今日でその悪夢に終止符を打とう。
世の中の多くのチームが「マイクロサービス化」を掲げながら、インフラストラクチャはいまだに一つの巨大なモノリス(Monolith)として管理している。これは技術的負債の最上位に位置するアンチパターンだ。
本記事では、Pulumi Stack References(スタック間参照)を駆使し、ネットワーク層、共通基盤層、そしてアプリケーション層を完全にデカップリング(疎結合化)し、組織のデプロイ速度を劇的に高める実践的なアーキテクチャを伝授する。
—
1. なぜ「インフラのモノリス」を今すぐ破壊すべきなのか
アプリケーションをGoやTypeScriptで綺麗にマイクロサービス分割しているにもかかわらず、インフラの定義が単一のPulumiプロジェクト(あるいは巨大なTerraform Root Module)に閉じ込められている現場をよく見かける。
これには以下の致命的な弊害がある。
1. Blast Radius(影響範囲)の肥大化:
1つの小さなLambda関数のIAMロールを変更したいだけなのに、数千リソースを管理するステートが評価され、万が一の障害時にVPCやデータベースまで巻き添えになる。
2. デプロイのボトルネック(Lock地獄):
チームAがデータベースのスキーマを触っている間に、チームBのECSサービスのデプロイがステートロックでブロックされる。結果、CI/CDパイプラインが渋滞を起こす。
3. 認知負荷(Cognitive Load)の限界:
一人のエンジニアがVPCのCIDR設計からKubernetesのIngress設定まで全てを把握するのは不可能に近い。
解決策:レイヤー別スタック分割
インフラストラクチャを以下の3つのライフサイクル(変更頻度と責任範囲)に完全に分割する。
[ 1. Network Stack ] (VPC, Subnets, Transit Gateway) -> 変更頻度: 極めて低
│
▼ (Stack Referencesで参照)
[ 2. Shared Base Stack ] (EKS, RDS, KMS, ECR) -> 変更頻度: 低
│
▼ (Stack Referencesで参照)
[ 3. Microservice Stacks ] (App A, App B, App C) -> 変更頻度: 高
この設計において、上位のレイヤーは下位レイヤーの「内部実装」を知る必要はない。下位レイヤーが公開した出力値(Outputs)を、Stack Referencesを通じて安全に参照するだけだ。
—
2. Stack ReferencesによるVPC・共通基盤の参照実装
百聞は一見にしかず。TypeScriptによる具体的なコードでその仕組みを見ていこう。
まずは、Network Stack(VPCやサブネットを管理するプロジェクト)の出力定義だ。
実装例:Network Stack (`network/index.ts`)
import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
// VPCの作成
const vpc = new aws.ec2.Vpc(“main-vpc”, {
cidrBlock: “10.100.0.0/16”,
enableDnsHostnames: true,
enableDnsSupport: true,
tags: { Name: “production-vpc” },
});
// パブリック・プライベートサブネットの定義…(省略)
const privateSubnetOne = new aws.ec2.Subnet(“private-1”, {
vpcId: vpc.id,
cidrBlock: “10.100.1.0/24”,
availabilityZone: “ap-northeast-1a”,
});
// —————————————————————–
// 【重要】 他のスタックへ公開するOutputs
// —————————————————————–
export const vpcId = vpc.id;
export const privateSubnetIds = [privateSubnetOne.id];
次に、このネットワーク基盤を参照するMicroservice Stack側の実装だ。
実装例:Microservice Stack (`services/payment-api/index.ts`)
import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
// 1. 組織・プロジェクト名・ステージ名からNetwork Stackへの参照を作成
// 構文: `
const networkStackRef = new pulumi.StackReference(“my-org/infra-network/production”);
// 2. スタックの出力値(Exportされた値)を型安全に取得
const vpcId = networkStackRef.requireOutput(“vpcId”);
const subnetIds = networkStackRef.requireOutput(“privateSubnetIds”).apply(ids => ids as string[]);
// 3. 取得したVPC IDとサブネットIDを使って、マイクロサービス用のECSセキュリティグループやサービスを構築
const sg = new aws.ec2.SecurityGroup(“payment-api-sg”, {
vpcId: vpcId, // 別スタックのVPC IDをシームレスにバインド
description: “Security group for Payment API”,
ingress: [{ protocol: “tcp”, fromPort: 8080, toPort: 8080, cidrBlocks: [“0.0.0.0/0”] }],
});
export const serviceEndpoint = pulumi.interpolate`https://payment.internal.net`;
ここで重要なのは、`pulumi.StackReference` は単なる文字列のパースではなく、Pulumi Cloud(またはS3等のバックエンド)のAPI経由で暗号化されたステートから値を取得する点である。これにより、Terraformでいう `terraform_remote_state` のような脆弱で複雑な設定から解放される。
—
3. デプロイ順序の制御とCI/CDパイプライン設計
インフラを分割した瞬間に問題になるのが「デプロイの順序」だ。アプリケーション(Microservice)は、必ず底支えするネットワークと共通基盤(Shared Base)がデプロイされていなければ失敗する。
これを人間が手動で管理するなど論外である。GitHub ActionsやGitLab CIを用いた自動化パイプラインの制御構造を構築する。
推奨するCI/CD構成(GitHub Actionsの例)
各スタックを独立したリポジトリ、または同一リポジトリ内の独立したディレクトリとして管理し、イベント駆動(Repository Dispatch / Workflow Call)で連鎖させる。
.github/workflows/deploy-services.yml
name: Deploy Microservices
on:
workflow_run:
workflows: [“Deploy Shared Infra”] # 共通基盤のデプロイ成功をトリガーにする
types:
- completed
jobs:
deploy-payment-api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Pulumi
uses: pulumi/action-github-actions@v5
with:
command: up
stack-name: my-org/payment-api/production
work-dir: ./services/payment-api
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
手元(ローカル)で開発する際も、`Pulumi.yaml` と構成管理ファイルを整えておけば、依存関係の解決はPulumiエンジンが賢くハンドリングしてくれる。
—
4. プロの現場で差がつく!生産性を極限まで高める実践テクニック
ここからは、一般的なチュートリアルには絶対に載っていない、現場のテックリードがこっそり使っている「神テクニック」を共有しよう。
A. 開発スピードを爆発させるキーボードショートカット & ツール
- VS Code Pulumi Extension の導入:
TypeScriptでPulumiを書く際、`pulumi.Output
- エイリアスの活用 (`.bashrc` / `.zshrc`):
頻繁に使うスタックの切り替えやプレビューをショートカット化する。
alias pup=”pulumi up –refresh”
alias prv=”pulumi preview”
alias psh=”pulumi stack history”
B. チーム開発で絶対守るべき「設定の共有化ルール」
Stack Referencesを使う際、組織内でスタック名やプロジェクト名の命名規則が崩壊すると地獄を見る。以下の命名規則(Naming Convention)を組織の憲法として定めよ。
> 命名規則スキーマ:
> `
> 例: `acme-corp/infra-network/production`
> 例: `acme-corp/service-payment/staging`
C. ベストプラクティス:ConfigとSecretの分離
Stack Referencesを使う場合でも、環境ごとの設定値(インスタンスサイズ、レプリカ数など)はPulumi Configで管理するべきだ。
Pulumi.production.yaml
config:
aws:region: ap-northeast-1
payment-api:replicaCount: “5”
payment-api:dbInstanceClass: “db.t4g.xlarge”
シークレット情報は、プレーンテキストでコミットせず必ず `pulumi config set –secret` を用い、AWS KMSやHashiCorp Vault、あるいはPulumiのデフォルト暗号化プロバイダで保護すること。
—
5. おわりに:疎結合なインフラが組織の自律性を生む
モノリスインフラストラクチャは、初期の立ち上がりこそ早いものの、組織がスケールし、マイクロサービスを加速させようとした瞬間に最大の足枷となる。
Pulumi Stack Referencesを導入し、インフラを「レイヤーごとのAPI」として設計せよ。
ネットワーク層はネットワークの専門家(インフラチーム)が守り、アプリケーション層は各プロダクトチームが自由かつ安全にデプロイする。
この疎結合な世界観こそが、変更リスクを極小化し、開発チームの背中に羽を生やす最強の基盤となる。
明日からのコードから、`pulumi.StackReference` のインポートを始めてみてほしい。世界が変わるはずだ。