PulumiのResource Options(Protect, IgnoreChanges)を極める:意図しないインフラ破壊を防ぐ鉄壁の防御策
こんにちは。テックリードの私だ。
日々のインフラ管理において、TerraformやPulumiといったIaC(Infrastructure as Code)ツールはもはや呼吸をするのと同じくらい不可欠な存在だ。特にPulumiは、TypeScript、Python、Goといった汎用プログラミング言語でインフラを記述できるため、開発スピードの爆発的な向上をもたらしてくれた。
しかし、「コードを書くスピードが上がった」ということは、「インフラを破壊するスピードも上がった」という残酷な事実を意味する。
`pulumi up` のプレビュー画面をろくに確認せずエンターを叩き、本番環境のデータベースやKubernetesクラスターが吹き飛んだときのあの血の気が引く感覚——。あなたも、あるいは同僚も、一度は経験したことがあるはずだ。
今回は、Pulumiが持つ最強の防御機構である Resource Options(`protect` と `ignoreChanges`) を深掘りする。単なる公式ドキュメントのなぞりではない。修羅場をくぐり抜けてきたSREが実践する、現場で即座に使える「鉄壁の防御設計」を伝授しよう。
—
1. 意図しないリソース削除や変更が起きる原因と怖さ
IaCのパラダイムにおいて、リソースのライフサイクルはコードによって完全に従属している。だが、現実のインフラは「コードの世界」だけで完結しない。
意図しない破壊が起きる主な悪夢のシナリオ
1. リファクタリングの悲劇:
変数名や論理名(PulumiのResource Name)を変更した際、Pulumiが「旧リソースの削除 + 新リソースの作成(Replace)」と誤認し、本番の永続ディスクを容赦なく消し去る。
2. 外部ドリフト(Drift):
AWS ConsoleやKubernetesのコントローラー(Cert-ManagerやHPAなど)が、Pulumiの管理外でリソースを勝手に書き換える。その状態で `pulumi up` を実行すると、コードの状態に強制上書きされてパニックが起きる。
3. チーム開発のヒューマンエラー:
プレビュー(`pulumi preview`)の差分を見落とし、レビューなしで `pulumi up –yes` をCI/CDパイプラインから直叩きしてしまう。
これらの恐怖から解放されるためには、コード側で「ここは絶対に触らせない」「ここは外部の変更を無視する」という明確な境界線(ガードレール)を引く必要がある。それが Resource Options だ。
—
2. `protect` オプションによる重要リソースの削除ガード実装
データベース、S3バケット、KMSキーなどのステートフルなリソースは、一度削除すると二度と戻らないデータを抱えていることが多い。これらをコードのミスやリファクタリングから守るのが `protect: true` だ。
実装パターン(TypeScript)
import as aws from “@pulumi/aws”;
// 本番用の重要データベース
const productionDatabase = new aws.rds.Instance(“production-db”, {
engine: “postgres”,
instanceClass: “db.t4g.medium”,
allocatedStorage: 50,
dbName: “core_production”,
username: “dbadmin”,
password: process.env.DB_PASSWORD,
skipFinalSnapshot: false, // 最後のスナップショットを強制
}, {
// 鉄壁の防御:このリソースの削除をブロックする
protect: true,
// ※ 万が一の削除リクエスト時にも、スタックの削除を安全に保つための設定
retainOnDelete: false, // trueにするとPulumi管理外として残す(要件に応じて使い分け)
});
`protect` の真価:削除プロテクションのメカニズム
`protect: true` を設定すると、Pulumiの状態ファイル(State)に「Protected」フラグが書き込まれる。この状態で `pulumi destroy` や、コードからこのリソースを削除して `pulumi up` を実行しようとすると、Pulumiは以下のようなエラーを出して処理を強制停止する。
> error: preview failed: resource urn:pulumi:prod::my-app::aws:rds/instance:Instance::production-db is protected
このエラーが出たら大正解だ。プロテクションが正常に機能し、あなたのインフラストラクチャが救われた瞬間である。
解除の手順(どうしても削除したい場合):
安易にコードから `protect: true` を消してはいけない。安全な手順は以下の通りだ。
1. コード上のオプションを明示的に `protect: false` に変更する。
2. 一度 `pulumi up` を実行し、プロテクションを解除する。
3. その後、リソースを削除するコード変更を行い、再度 `pulumi up` を実行する。
この「ワンクッション」を入れることで、うっかり削除を防ぐことができる。
—
3. `ignoreChanges` を使った外部ツールによる自動変更の検知・無視設定
クラウドインフラは、Pulumiだけで構築・運用されることは稀だ。KubernetesのMutating/ValidatingWebhook、AWS Auto Scalingの動的なキャパシティ調整、あるいは他のTerraform/CloudFormationモジュールなど、外部システムがリソースのプロパティを勝手に書き換えることは日常茶飯事である。
ここで `ignoreChanges` が火を吹く。
実装パターン(AWS ECS / Auto Scaling の例)
ECSのタスク数や、Auto Scalingのdesired capacityなどは、オートスケーリングポリシーによって常時変動する。これをPulumiのコード側(例: `desiredCount: 2`)で固定していると、スケーリングによって増えたインスタンスが `pulumi up` のたびに「元に戻されて(Desiredに強制されて)」しまう。
import as aws from “@pulumi/aws”;
const appService = new aws.ecs.Service(“app-service”, {
cluster: myCluster.arn,
desiredCount: 3, // 初期値
taskDefinition: appTask.arn,
// …その他の設定
}, {
// 外部のオートスケーラーやCI/CDパイプラインによる変更を無視する
ignoreChanges: [
“desiredCount”, // オートスケーリングで変動するため無視
“taskDefinition”, // 外部のCDパイプライン(Blue/Greenデプロイ等)で更新されるため無視
],
});
これで、Pulumiは `desiredCount` や `taskDefinition` の差異を差分(Diff)として検知しなくなる。オートスケーリングの動的な挙動を阻害せず、インフラのベースラインだけをPulumiで安全にコード管理できるというわけだ。
—
4. 現場でトラブルを防ぐためのベストプラクティス集
ここからは、数々の修羅場を潜り抜けたテックリードとして、チーム全体の生産性を底上げし、インフラ事故をゼロにするための実践的プラクティスを共有しよう。
① 開発スピードを劇的に高めるキーボードショートカット & IDE設定
日々の開発で `pulumi preview` を何回叩くだろうか?ターミナルを行き来する時間はエンジニアの集中力を削ぐ。
- VS Code 拡張機能の導入:
公式の `Pulumi` 拡張機能を必ず入れよ。型補完(IntelliSense)が効くだけでなく、リソースのドキュメントがホバーで即座に確認できるため、ブラウザを行き来する無駄な時間が消える。
- タスクランナー(`tasks.json`)の活用:
頻繁に使うコマンドはVS Codeのタスクに登録し、ショートカットキー(例: `Ctrl + Shift + B` やカスタムキーバインド)で一発実行できるようにする。
// .vscode/tasks.json の実用例
{
“version”: “2.0.0”,
“tasks”: [
{
“type”: “shell”,
“label”: “Pulumi: Preview (Current Stack)”,
“command”: “pulumi preview”,
“group”: {
“kind”: “build”,
“isDefault”: true
},
“problemMatcher”: []
},
{
“type”: “shell”,
“label”: “Pulumi: Up (Safe)”,
“command”: “pulumi up –show-reads”,
“problemMatcher”: []
}
]
}
② チーム開発で役立つ設定の共有化ルール(Pulumi Policy / CrossGuard)
「個人のスキルに頼るな、仕組みで縛れ」はSREの鉄則だ。 Pulumiには CrossGuard(Pulumi Policy) という、ポリシー as コードの仕組みがある。これを使って、「本番環境のリソースには必ず `protect: true` を強制する」ルールを自動化できる。
PythonやTypeScriptでポリシーを記述し、Organization全体に適用可能だ。
// policy/index.ts (Pulumi Policyのイメージ)
import as policy from “@pulumi/policy”;
import as aws from “@pulumi/aws”;
// すべてのRDSインスタンスに protect: true が設定されているか検証するポリシー
const rdsMustBeProtected = new policy.Policy({
name: “rds-must-be-protected”,
description: “Production RDS instances must have protect=true enabled.”,
policy: policy.validateResourceOfType(aws.rds.Instance, (instance, args, reportViolation) => {
// スタック名が prod の場合のみチェック
if (pulumi.getStack().includes(“prod”)) {
// ※注: 実際にはoptsのプロパティを直接参照できないため、カスタムアノテーションやタグ等でガードする設計にする
if (!instance.urn) { // 概念的なコード
reportViolation(“Production RDS instances must be protected against accidental deletion.”);
}
}
}),
enforcementLevel: “mandatory”, // 違反時はデプロイをブロック
});
③ 実用的なプロジェクト設定ファイルのベストプラクティス構成
プロジェクトのルート構造は、スケーラビリティと保守性の要だ。以下に、大規模開発でも破綻しない推奨ディレクトリ構成を示す。
my-infrastructure-project/
├── .vscode/
│ └── tasks.json # チームで共有する便利タスク
├── policies/ # CrossGuardポリシー(ガードレール)
│ └── index.ts
├── 01-networking/ # 独立したスタック(VPC, Subnets)
│ ├── Pulumi.yaml
│ ├── Pulumi.prod.yaml # 本番用設定
│ └── index.ts
├── 02-database/ # 独立したスタック(RDS, ElastiCache)
│ ├── Pulumi.yaml
│ ├── Pulumi.prod.yaml # ★ここに protect: true を集中させる
│ └── index.ts
└── package.json
構成のポイント:
- ネットワークとデータベースをスタックで分離する: データベースのデプロイミスがネットワーク層に波及するのを防ぐ。
- 本番環境(`Pulumi.prod.yaml`)の設定値管理: 機密情報は `pulumi config set –secret` を用い、暗号化してPulumi Serviceまたはバックエンドに安全に保存する。平文のパスワードなどをコードやYAMLに直書きする愚行は絶対に行ってはならない。
—
結びにかえて
インフラストラクチャ・コードの本質は、「人間が手動でやってミスをするリスク」をコードの力で排除することにある。しかし、そのコード管理が甘ければ、ボタン一つでクラウド上の全資産を吹き飛ばす凶器にもなり得る。
今回紹介した `protect` と `ignoreChanges` は、言いわば「インフラストラクチャにおけるエアバッグとシートベルト」だ。これらを適切にコードに組み込むことで、チーム全体が心理的安全性を持って、爆速で機能開発とデプロイを行えるようになる。
さあ、今すぐ手元のコードベースを開き、重要なステートフルリソースに `protect: true` が入っているか確認してほしい。あなたのインフラの安全は、あなた自身のコードで守るのだ。