破壊的変更を恐れるな:Pulumiエコシステムの自動追従と安全なマイグレーション戦略
こんにちは。大規模クラウドインフラのSREおよびテックリードを務めている者だ。
日々、数十のマイクロサービスとそれに紐づくAWS/GCPのクラウドインフラをPulumi(TypeScript/Python)で管理していると、頭を悩ませる問題が1つある。それは「プロバイダ(AWS, GCP等)のバージョンアップと言語ランタイム依存関係の追従地獄」だ。
「TerraformならHCLだから……」なんて言い訳は通用しない。Pulumiは本物のプログラミング言語を使うが故に、Node.jsのnpmパッケージ、Pythonのpipモジュール、さらにはPulumi SDKとプラグインのバージョンマトリクスが複雑に絡み合う。放置すれば、半年後にはセキュリティ脆弱性の温床となり、いざメジャーアップデートしようものなら数日間のインフラ停止を伴う流血の惨事となる。
今回は、Dependabot / Renovateを駆使してPulumiのスタック全体を常に最新かつ安全に保ち、破壊的変更(Breaking Changes)を完全自動テストでねじ伏せるための実戦的Opsワークフローを伝授しよう。
—
1. 開発スピードを極限まで高める:Pulumi開発者のための極秘ツールチェイン
自動化の話に入る前に、日々のローカル開発で生産性を限界突破させるための「神プラグイン」と「ショートカット」を共有しておく。
必携のIDE拡張・プラグイン(VS Code / JetBrains)
- Pulumi VS Code Extension: リソースのツリービュー表示だけでなく、スタックのプレビュー結果をエディタ内でインライン表示できる。これなしでのコードレビューはあり得ない。
- Error Lens: TypeScript/Pythonの型エラーやPulumiのプロパティ不足をコード行末に直接赤字で表示。コンパイルエラーや型ミスマッチをデプロイ前に100%潰す。
現場で使えるCLIショートカット&イディオム
毎回の長大なコマンド入力を排除するため、プロジェクトの `Makefile` や `package.json` の `scripts` に以下を仕込んでおけ。
高速プレビュー(差分確認のみ、ログ詳細化)
alias plup=”pulumi preview –diff –color always”
安全なデプロイ(要確認プロンプトを強制)
alias plupup=”pulumi up –refresh –suppress-outputs”
特に `–refresh` をデフォルトで有効にする文化を作れ。クラウド側の実態とStateのドリフト(乖離)を自動検知しない自動化など、百害あって一利なしだ。
—
2. Renovateによる「Pulumiスタック完全自動追従」の設計
Dependabotでも良いが、柔軟なグルーピングとセマンティックバージョニングの制御において、PulumiエコシステムにはRenovateを強く推奨する。Pulumiは「言語SDK」「プロバイダプラグイン」「言語ランタイム」の3層が連動するため、これらをバラバラにアップデートされると依存関係の不整合で死ぬ。
プロジェクトルートに配置する `renovate.json` のベストプラクティスを公開する。
実用的な `renovate.json` の決定版
{
“$schema”: “https://docs.renovatebot.com/renovate-schema.json”,
“extends”: [
“config:base”,
“group:monorepo”,
“:preserveSemverRanges”
],
“packageRules”: [
{
“description”: “Pulumiエコシステムはまとめて同時にアップデートし、バージョンの不整合を防ぐ”,
“matchPackagePatterns”: [
“^@pulumi/”,
“pulumi”
],
“groupName”: “pulumi-ecosystem”,
“schedule”: [“every weekend”],
“automerge”: false
},
{
“description”: “AWS/GCPなどのクラウドプロバイダは個別に検証が必要なため分離”,
“matchPackagePatterns”: [
“^@pulumi/aws”,
“^@pulumi/gcp”
],
“groupName”: “pulumi-cloud-providers”,
“schedule”: [“every weekend”],
“automerge”: false
},
{
“description”: “言語ランタイム(Node.js/Python)はセキュリティパッチを即座に適用”,
“matchManagers”: [“npm”, “pip_requirements”],
“matchUpdateTypes”: [“minor”, “patch”],
“automerge”: true
}
],
“customManagers”: [
{
“description”: “Pulumiプラグインのバージョン指定(Pulumi.yaml)を追跡する”,
“fileMatch”: [“^Pulumi\\.yaml$”],
“matchStrings”: [
“name:\\s+(?
],
“datasourceTemplate”: “github-releases”
}
]
}
この設定のキモは、Pulumiエコシステムとクラウドプロバイダのアップデートを「週末」に寄せ、手動でのマージ&テストを必須化(`automerge: false`)している点だ。自動マージはマイナー・パッチの言語ランタイムだけに絞るのが、夜間インフラ爆破を防ぐ鉄則だ。
—
3. 破壊的変更(Breaking Changes)を無力化するCI/CDマイグレーション戦略
メジャーバージョンアップ(例: `@pulumi/aws` v5からv6への移行)では、プロパティの非推奨化やデフォルト挙動の変更といった破壊的変更が必ず発生する。これを安全に乗り切るためのCIパイプライン設計を解説する。
GitHub Actionsによる「プレビュー検証&自動テスト」ワークフロー
PRが作成された際、単に `pulumi preview` を走らせるだけでは不十分だ。「既存のリソースを破壊せずに更新できるか(Replacementが発生しないか)」を機械的に検知する必要がある。
以下に、実戦投入しているGitHub Actionsワークフローの核心部分を示す。
name: Pulumi Safe Migration CI
on:
pull_request:
branches: [ main ]
jobs:
pulumi-check:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-request: write # PRへのコメント自動化に必須
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi@v3
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-pulumi
aws-region: ap-northeast-1
- name: Select Pulumi Stack
run: pulumi stack select staging
- name: Install Dependencies
run: npm ci
- name: Run Pulumi Preview & Capture Output
id: preview
uses: pulumi/actions@v5
with:
command: preview
stackName: staging
comment-on-pr: true
json-diff: true
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
- name: Guard Against Destructive Changes (Replacement)
run: |
# PulumiのJSON diffから、リソースの「削除・再作成 (create-replacement)」を検知する
# 意図しないリソースの作り直しが含まれている場合はCIを強制的に落とす
node .github/scripts/validate-preview-diff.js
秘伝のタレ:リソースの「予期せぬ置換(Replacement)」検知スクリプト
Pulumiのプレビュー結果(JSON)を解析し、既存の本番・ステージングリソースが勝手に消されて作り直される(`create-replacement`)悲劇をブロックするスクリプト (`.github/scripts/validate-preview-diff.js`) の実装例だ。
// .github/scripts/validate-preview-diff.js
const fs = require(‘fs’);
// Pulumi actionが生成または渡すJSON diffを読み込む
// 実際には環境変数やファイル出力から取得する
const diffReport = process.env.PULUMI_JSON_DIFF || ‘{}’;
try {
// 簡易的なパース例:実際にはステップ出力をJSONとして受け取る
// リソースの変更操作の中に “delete-replaced” や “create-replaced” が含まれていないか検証
const hasDangerousChanges = false; // パースロジックをここに実装
if (hasDangerousChanges) {
console.error(“❌ 警告: このアップデートには既存リソースの置換(再作成)を伴う変更が含まれています!”);
console.error(“プロパティの仕様変更を確認し、必要に応じて pulumi state rename や alias を設定してください。”);
process.exit(1);
}
console.log(“✨ 危険なリソース置換は検出されませんでした。安全なアップデートです。”);
} catch (e) {
console.error(“Failed to parse preview diff:”, e);
process.exit(1);
}
—
4. メジャーバージョンアップ時のマイグレーションパターン
もしRenovateが `@pulumi/aws` のメジャーバージョンアップPRを作成し、上記のCIで「リソースの置換」が検知された場合、テックリードとしてどう対処すべきか。現場で使える2つの王道パターンを授ける。
パターンA: `alias` オプションによるリソース名の維持
プロバイダのメジャーアップに伴い、内部的なリソースクラス名やARNの構造が変わる場合、Pulumiの `alias` 機能を使って「古い名前から新しい名前への継承」を明示的にコードに記述する。これを行わないと、Pulumiは「古いリソースを削除し、新しいリソースを新規作成する」と勘違いしてインフラを吹き飛ばす。
// 例: v5からv6への移行でリソースの命名規則が変わった場合
const bucket = new aws.s3.Bucket(“my-bucket”, {
// 既存のStateとの連続性を保つためのエイリアス定義
aliases: [
{ name: “my-old-bucket-resource-name” }
],
});
パターンB: ステートの直接操作(`pulumi state`)
どうしても構造的な乖離が直せない場合や、プロバイダの仕様変更でURN(Uniform Resource Name)が変わってしまう場合は、ローカルまたはCI上で `pulumi state` コマンドを用いてリソースの紐付けを強制修正する。
既存のステート内のリソースURNを変更先のURNにリネーム
pulumi state rename urn:pulumi:staging::my-project::aws:s3/bucket:Bucket$aws:s3/bucket:Bucket old-name new-name
※注意: この操作を行う前には必ず `pulumi stack export > backup.json` でステートのバックアップを取ること。SREとしての最低限の良心だ。
—
最後に:インフラを「常に最新」に保つ文化の醸成
ツールやワークフローをどれだけ完璧に整えても、チームメンバーがその意図を理解していなければ意味がない。
「古いプロバイダを放置することは、古いLinuxカーネルをパッチ当てずに放置するのと同じ脆弱性リスクである」という認識をチームで共有し、Renovateが上げてきたPRを金曜日の夕方に放置せず、月曜日の朝イチでCIの緑を確認してマージする――この「小さな規律の積み重ね」こそが、障害フリィでスケールするクラウドインフラストラクチャを支える唯一にして最大の防壁となる。
さあ、今すぐ `renovate.json` をプロジェクトにブチ込み、面倒なプロバイダ追従の呪縛から解放されよう。