PulumiのResource Options(Protect, IgnoreChanges)を極める:意図しないインフラ破壊を防ぐ鉄壁の防御策
インフラストラクチャ・イン・コード(IaC)の最大のベネフィットは、インフラの再現性と変更の追跡可能性にある。しかし、その強大なパワーは、一歩間違えれば「たった一行のコード修正、あるいは誤ったターゲット指定による、プロダクション環境の全データベースの消滅」という破滅的なインシデントに直結する。
Terraformにおける `lifecycle` ブロックの概念と同様に、Pulumiにも宣言的インフラの暴走を防ぐための強力なメカニズムが存在する。それが Resource Options である。
本稿では、Pulumiの内部ステート管理の挙動を紐解きながら、`protect` と `ignoreChanges` を駆使して「意図しないインフラ破壊」を完全に無力化する、プロダクション現場の極限知見を共有する。
—
1. なぜ IaC は牙をむくのか:ステートと実態の乖離が生む恐怖
Declarative(宣言的)なIaCツールは、常に「コードの状態」と「クラウド上の実態(Actual State)」の差分(Diff)を計算し、一致させようと動く。ここに、SREを夜中に起こす根本的な原因がある。
破壊的変更の主要なトリガー
1. プロパティの強制置換(Force Replacement):
AWSの `AWS::RDS::DBInstance` や `AWS::S3::Bucket` などのリソースにおいて、一部のプロパティ(例: `availabilityZone`, `bucket` 名自体など)を変更すると、Pulumi(というか底层のクラウドプロバイダーAPI)は「既存を削除して新規作成する(Create-before-destroy または Destroy-and-create)」という挙動をとる。
2. プレフィックスや参照の誤認:
スタックの切り替えミスや、変数のスコープ汚染により、StagingのコードでProductionの物理リソースを指してしまった場合、IaCは忠実に本番環境を破壊しにかかる。
3. 外部コントローラーによるドリフト(Drift):
KubernetesのOperator、AWS Auto Scaling、あるいは手動オペレーションによってクラウド側でリソースが変更された際、Pulumiがそれを「コードからの乖離」と検知し、勝手に上書き・削除を試みる。
この暴走をコードベースで物理的に封じ込めるのが、Resource Optionsである。
—
2. `protect` オプション:重要リソースの削除ガード実装
`protect: true` は、Pulumiエンジンに対して「このリソースはいかなる理由があっても削除してはならない」という絶対的なロックをかけるオプションだ。
単に `pulumi destroy` から守るだけでなく、「プロパティ変更に伴うリソースの置換(Replacement)」の際の削除フェーズをもブロックする。これがこのオプションの真骨頂である。
実装パターン:TypeScriptによる鉄壁のRDS保護
本番用のAurora ClusterやKMSキーなど、誤削除が会社を揺るがすリソースには、デフォルトでこのオプションを組み込むべきだ。以下に、単なるフラグ設定にとどまらず、組織統制として強制するパターンを示す。
import as aws from “@pulumi/aws”;
import as pulumi from “@pulumi/pulumi”;
// スタックの設定から本番環境かどうかを判定
const stackName = pulumi.getStack();
const isProduction = stackName.startsWith(“prod”);
// 本番環境であれば強制的に protect を true にするファクトリ関数のラップ
function createProtectedResourceOptions(): pulumi.ResourceOptions {
return {
protect: isProduction, // プロ免罪符としての保護
// 例外的な削除を許可する場合のみ明示的に保持するが、通常はtrue固定
};
}
// データベースサブネットグループ
const dbSubnetGroup = new aws.rds.SubnetGroup(“prod-db-subnet-group”, {
subnetIds: [“subnet-xxxxxx”, “subnet-yyyyyy”],
}, {
// 削除保護を適用(万が一のコード削除から守る)
protect: true,
});
// 本番用Auroraクラスター
const cluster = new aws.rds.Cluster(“prod-aurora-cluster”, {
engine: “aurora-postgresql”,
engineVersion: “15.4”,
databaseName: “core_production”,
masterUsername: “root_admin”,
// … その他の設定
}, {
// リソースオプションの指定
protect: true,
// 万が一依存関係の都合で再作成が必要になった場合でも、削除を拒否して停止させる
deleteBeforeReplace: false,
});
`protect: true` がもたらす内部動作の深層
Pulumiエンジンは、リソースのメタデータ(Stateファイル内)に `protect: true` を書き込む。
もし誰かがコードからこのリソース定義を削除し、`pulumi up` を実行した場合、Pulumiは以下のように警告を出して処理をアボート(中断)する。
Diagnostics:
aws:rds:Cluster (prod-aurora-cluster):
error: resource ‘urn:pulumi:prod::my-app::aws:rds:Cluster::prod-aurora-cluster’ is protected and cannot be deleted
【プロの知見】
保護を解除するには、一時的にコード側で `protect: false` に書き換えて `pulumi up` を叩くか、Pulumi CLIを使って明示的にステートを操作する必要がある:
pulumi state unprotect ‘urn:pulumi:prod::my-app::aws:rds:Cluster::prod-aurora-cluster’
この一手間があるおかげで、「うっかりエンターキー」による大惨事を確実に防ぐことができる。
—
3. `ignoreChanges` オプション:外部からの自動変更を華麗にいなす
クラウドインフラは、IaCコードだけで完結することは稀だ。AWS Auto Scalingによるインスタンス数の増減、Kubernetes HPAによるレプリカ数の変動、あるいはサードパーティ製セキュリティツールが勝手に追加するタグなど、「IaC管理外で動的に変化するプロパティ」が存在する。
これらを `ignoreChanges` で指定しないと、`pulumi up` を実行するたびに、Pulumiは「実態がコードと違う!」と検知し、外部ツールが行った変更を強制的にロールバック(上書き)してしまう。
実装パターン:Auto Scaling とタグの動的変更を無視する
import as aws from “@pulumi/aws”;
import as pulumi from “@pulumi/pulumi”;
const appServer = new aws.ec2.Instance(“app-server”, {
ami: “ami-0c55b159cbfafe1f0”,
instanceType: “t3.medium”,
tags: {
Environment: “production”,
ManagedBy: “Pulumi”,
// セキュリティ監査ツールが動的に付与するメタデータ
LastScannedBy: “InitialValue”,
},
});
const autoScalingGroup = new aws.autoscaling.Group(“app-asg”, {
desiredCapacity: 2,
maxSize: 10,
minSize: 1,
// …
}, {
// 外部変更や動的変化を無視する設定
ignoreChanges: [
// 1. Auto Scalingによるインスタンス数の動的変動を無視
// (これがないと、スケールアウトした後に pulumi up すると強制的にminSizeに巻き戻される)
“desiredCapacity”,
// 2. タグの一部、または特定パターンの変更を無視
// サードパーティツールが勝手に書き換えるタグをIaCの管理対象外にする
“tags[\”LastScannedBy\”]”,
// 3. ライフサイクルフックなどで動的に変わるプロパティ
“initialLifecycleHooks”,
],
});
ワイルドカードと配列要素の高度な指定
Pulumiの `ignoreChanges` は、単なるプロパティ名だけでなく、配列やネストしたオブジェクト、さらにはワイルドカード(“)を用いた高度な指定が可能だ。
const securityGroup = new aws.ec2.SecurityGroup(“dynamic-sg”, {
ingress: [
{ protocol: “tcp”, fromPort: 80, toPort: 80, cidrBlocks: [“0.0.0.0/0”] }
],
}, {
// ingressルール全体、あるいは特定のインバウンドルールの動的追加を許容する
ignoreChanges: [
“ingress[].description”, // セキュリティツールが後から付与する説明文の変更を無視
],
});
—
4. 実務で防ぐべきアンチパターンとベストプラクティス集
長年、大規模なクラウドインフラストラクチャをPulumiで運用してきた中で培った、現場で震えるほど役立つ知見をここに還元する。
ベストプラクティス 1: 「全リソース一括Protect」の罠を避ける
すべてのリソースに `protect: true` をかけるのは、一見安全に見えて実はアンチパターンである。
一時的な検証用リソース、CI/CDパイプラインで動的に生成・破棄されるプレビュー環境のスタック等において `protect: true` が有効になっていると、クリーンアップパイプライン自体が粉砕される。
「永続データストア」「DNSゾーン」「コアネットワーク(VPC等)」に絞って適用し、ステート管理のポリシーとしてCIで静的解析(Policy as Code / Pulumi CrossGuard)を組み込むべきだ。
ベストプラクティス 2: `ignoreChanges` を「隠れ蓑」にしない
`ignoreChanges` は強力だが、「IaCのコードと実際のインフラストラクチャが乖離している状態を正当化する免罪符」になりやすい。
本当に動的な変更(AWS側で自動付与されるタグやAuto Scalingの数値など)以外にこれを使用すると、誰も実態を把握できない「シャドウIT」ならぬ「シャドウインフラ」が誕生する。
`ignoreChanges` を適用したプロパティには、必ずコード上に「なぜ無視する必要があるのか(どの外部ツールが干渉するのか)」のコメントを詳細に残すこと。
ベストプラクティス 3: プレビュー(`pulumi preview`)の厳格なパイプライン統合
Resource Optionsをどれだけ極めても、人間の目やCIがそれを見落としては意味がない。
プロダクション環境へのデプロイメントパイプラインでは、以下の原則を徹底せよ。
1. Destroy の検知:
`pulumi preview –json` の出力をパースし、`op: “delete”` または `op: “replace”` が発生するリソースの中に、`protect` 対象外の重要リソースが含まれていないかをCI上でアサーションする。
2. 差分の可視化:
`ignoreChanges` が正しく機能しているか確認するため、定期的に `pulumi refresh` を実行するCronジョブを回す。これにより、コード外の意図しない変更(ドリフト)を早期に検知できる。
—
5. 終わりに:インフラストラクチャの尊厳を守るために
IaCにおけるコードは、単なる設定ファイルの羅列ではない。それは稼働中のビジネスそのものを定義する「契約書」である。
`protect` によって物理的な破壊からリソースを守り、`ignoreChanges` によって外部エコシステムとの優美な調停を行う。この2つのResource Optionsを掌中に収めたとき、あなたの管理するインフラストラクチャは、いかなるヒューマンエラーやシステムの暴走をも跳ね返す、真の鉄壁の要塞となる。
コードの海に身を委ねよ。そして、その全権を掌握せよ。