【テクニカル・上級編】Pulumi Stateバックエンドのカスタマイズと暗号化キーローテーションの安全な運用手順 – インフラ構成管理(IaC)活用バイブル

Pulumi Stateの極限掌握:セルフホストバックエンドの完全移行とKMSキーローテーションの要塞化

インフラストラクチャ・アズ・コード(IaC)の領域において、ツール自体の学習コストよりも遥かにクリティカルな問題――それは「State(状態)の生命線」の管理だ。

PulumiはデフォルトでSaaS型の「Pulumi Service」を提供しており、暗号化やチーム管理が抽象化されていて非常にスマートだ。しかし、金融、ヘルスケア、あるいは厳格なデータ主権(Data Sovereignty)を要求されるエンタープライズ環境において、ステートファイルをサードパーティのSaaSに預けるという選択肢は存在しない。また、極限のパフォーマンス、エアギャップ環境、組織固有のコンプライアンス要件を満たすためには、セルフホストのオブジェクトストレージへの移行が不可欠となる。

本稿では、PulumiのデフォルトバックエンドからAWS S3 / GCSへの完全移行、AWS KMS / Cloud KMSを用いたステートファイルの厳格な暗号化、そしてSREの悪夢である「キーローテーション時のデプロイロック・復号不能障害」をゼロダウンタイムで回避する極限の運用手順を、生粋のアーキテクトの視点から解き明かす。

—

1. 内部アーキテクチャの理解:Pulumi Stateの正体とロック機構

まず、敵を知ることから始める。Pulumiのステートファイルは単なるJSONの塊ではない。
実態としては、リソースの依存関係グラフ(DAG)、URN、プロバイダの設定、そして各リソースのシークレットがAES-256-GHE等の方式で暗号化されたメタデータの集合体だ。

セルフホストバックエンドにおける「分散ロック」の罠

S3やGCSをバックエンド(`s3://` や `gs://`)として指定した場合、Pulumi CLIはストレージを直接読み書きする。ここで問題になるのが同時実行制御(Concurreny Control)だ。

Pulumi Serviceは中央集権的なAPIサーバーがロックを管理するが、セルフホストの場合はオブジェクトストレージのネイティブな仕組み、あるいはS3であればDynamoDBを用いた分散ロック(State Locking)を構成する必要がある。これを行わないと、CI/CDパイプラインの並列実行時や複数エンジニアの同時デプロイ時にステートが破損(State Corruption)し、リカバリ不能な状態に陥る。

—

2. セルフホストバックエンドへの完全移行手順(AWS S3 + DynamoDB編)

ゼロから堅牢なセルフホストバックエンドを構築する。ここでは、Terraform等を用いてこの「インフラのためのインフラ」を事前にプロビジョニングしておくアプローチをとる(あるいは手動で一度だけ厳格に作成する)。

バックエンドインフラの定義 (HFA / Terraform or Pulumi itself)

ステートを保存するバケットは、バージョニングを有効化し、パブリックアクセスを完全ブロック、さらにKMSで暗号化する。

S3 Bucket for Pulumi State
resource “aws_s3_bucket” “pulumi_state” {
bucket = “enterprise-pulumi-state-store-production”

lifecycle {
prevent_destroy = true
}
}

resource “aws_s3_bucket_versioning” “enabled” {
bucket = aws_s3_bucket.pulumi_state.id
versioning_configuration {
status = “Enabled”
}
}

resource “aws_s3_bucket_server_side_encryption_configuration” “encryption” {
bucket = aws_s3_bucket.pulumi_state.id

rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.pulumi_state_key.arn
sse_algorithm = “aws:kms”
}
}
}

DynamoDB for State Locking
resource “aws_dynamodb_table” “pulumi_locks” {
name = “pulumi-state-locks”
billing_mode = “PAY_PER_REQUEST”
hash_key = “LockID”

attribute {
name = “LockID”
type = “S”
}
}

既存ステートのエクスポートとインポート

Pulumi Serviceからセルフホストへ移行する手順は以下の通りだ。データの整合性を保つため、必ず全デプロイを停止した状態で実施する。

1. 既存ステートのエクスポート

pulumi stack export –stack production > stack_backup.json

2. バックエンドの切り替え
環境変数 `PULUMI_BACKEND_URL` を設定し、新しいバックエンドを指すようにする。

export PULUMI_BACKEND_URL=”s3://enterprise-pulumi-state-store-production?awslockid=pulumi-state-locks”

3. 新しいスタックの初期化とインポート

pulumi stack init production –non-interactive
pulumi stack import –file stack_backup.json

これで、バックエンドは完全に自社管理のAWSインフラストラクチャ下へと移行される。

—

3. KMSを用いた多層暗号化(Envelope Encryption)の極意

バックエンドストレージのサーバーサイド暗号化(SSE-KMS)だけでは不十分だ。Pulumiはスタック内の「シークレット(DBパスワードやAPIトークンなど)」を個別に暗号化する機能(Passphrase / KMS Secrets Provider)を持っている。

セルフホスト環境では、AWS KMSをSecrets Providerとして指定することで、ステート内の機密情報をAWSのハードウェアセキュリティモジュール(HSM)で保護する。

KMS Secrets Providerを使ったスタックの初期化

pulumi stack init production \
–secrets-provider=”awskms://arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012″

この設定を行うことで、Pulumiはスタック内のシークレットをAWS KMSのEnvelope Encryption(エンベロープ暗号化)を用いて暗号化し、ステートファイルに書き込むようになる。万が一ステートファイルがS3から流出しても、KMSの権限(IAM)がなければシークレットを復号することは不可能となる。

—

4. 運用中のキーローテーションでデプロイ障害を起こさないためのベストプラクティス

セキュリティ監査の要請などで、定期的なKMSカスタマーマネージドキー(CMK)のローテーションが必要になる。しかし、これを安易に行うと、「既存のステートファイルやシークレットが復号できなくなり、全リソースがデプロイ不能・管理喪失になる」という致命的な障害(SREの悪夢)を引き起こす。

AWS KMSの「自動キーローテーション」はキーマテリアルを更新するが、すでに暗号化された既存データは古いキーバージョンを指し続けるため、基本的に復号は可能である。しかし、暗号化プロバイダーとしてKMSを使用するPulumiにおいては、新しいキーバージョンへの完全移行と、アクセス権限の整合性が極めて重要になる。

ここでは、ゼロダウンタイムかつ安全にKMSキーをローテーションする極限の運用手順を提示する。

手順A: AWS KMS側のローテーション方針

AWS KMSの自動ローテーション(毎年)を有効化するか、手動で新しいキーを作成しエイリアスを付け替えるアプローチをとる。手動エイリアス切替方式は最も予測可能で安全性が高い。

1. 新しいKMSキーの作成

NEW_KEY_ARN=$(aws kms create-key –description “Pulumi Secrets Provider Key 2024-Q3” –query ‘KeyMetadata.Arn’ –output text)

2. 既存のPulumiスタック設定の確認
現在のスタック設定ファイル `Pulumi.production.yaml` を確認する。

encryptionsalt: v1:xxxx…
# シークレットプロバイダーの設定はバックエンド側ではなくスタックメタデータに保持される

手順B: 「Re-encryption(再暗号化)」パイプラインの実行

新しいKMSキーへ切り替える際、単にキーを変えるだけでは既存のシークレットは古いキーのままである。古いキーを将来的に廃止(Disable/Delete)する日に備え、すべてのシークレットを新しいKMSキーで再暗号化(Re-encrypt)する必要がある。

これを行うための自動化スクリプト(Python / Pulumi CLIラッパー)の核心部分を以下に示す。このスクリプトは、エクスポートされたステート内のシークレットを新キーで再ラップする処理を抽象化する。

import subprocess
import json
import os

def rotate_pulumi_secrets(stack_name: str, new_kms_arn: str):
print(f”[] Starting secret re-encryption for stack: {stack_name}”)

# 1. 現在のスタックをエクスポート
export_result = subprocess.run(
[“pulumi”, “stack”, “export”, “–stack”, stack_name],
capture_output=True, text=True, check=True
)
state_data = json.loads(export_result.stdout)

# 2. シークレットプロバイダーの変更をスタックメタデータに適用
# ※実際には pulumi stack change-secrets-provider を使用するのが最も安全
print(f”[] Migrating secrets provider to: {new_kms_arn}”)
subprocess.run(
[“pulumi”, “stack”, “change-secrets-provider”, f”awskms://{new_kms_arn}”, “–stack”, stack_name, “–yes”],
check=True
)

print(“[+] Key rotation and re-encryption pipeline completed successfully.”)

if __name__ == “__main__”:
rotate_pulumi_secrets(“production”, “arn:aws:kms:us-east-1:123456789012:key/new-key-uuid”)

アーキテクチャ上の鉄則:キーの寿命管理

  • 即時削除の禁止: 古いKMSキーを削除する際は、必ず `PendingWindowInDays` を最大(30日間)に設定し、万が一の復号エラーが発生した際に即座に復旧(CancelKeyDeletion)できる猶予を確保すること。
  • IAM権限の分離: デプロイを実行するCI/CDランナーのIAMロールには、「新しいKMSキーの `Decrypt`/`Encrypt`」だけでなく、「古いKMSキーの `Decrypt`」権限を最低30日間は維持させよ。これを怠ると、ローテーション直後の過去のバージョンへのロールバックデプロイが即座に失敗する。

—

5. エキスパートのためのパフォーマンス&メモリ最適化ハック

最後に、大規模なモノリススタック(数千リソースを管理する巨大なPulumiプロジェクト)をセルフホストバックエンドで運用する際の、低レイヤチューニング知見を共有する。

1. S3クライアントの接続プーリングとリトライ設定
CI/CD環境において、同時多発的にPulumiが実行されると、S3/DynamoDBへのリクエストがスロットリング(429 Too Many Requests)を引き起こす。AWS SDKの環境変数を調整し、バックオフアルゴリズムを最適化せよ。

export AWS_MAX_ATTEMPTS=10
export AWS_RETRY_MODE=adaptive

2. 巨大ステートにおけるメモリ消費の抑制
Pulumi Engineは、ステート全体のグラフをメモリ上に展開して差分計算を行う。リソース数が5,000を超えるような超巨大スタックでは、Node.js(Pulumiのランタイム)のヒープサイズ上限にヒットして `FATAL ERROR: Ineffective mark-compacts near heap limit` が発生する。
CI環境では必ず以下のNode.jsオプションを付与し、メモリ制限を拡張すること。

export NODE_OPTIONS=”–max-old-space-size=8192″

3. ステートファイルの断片化を防ぐライフサイクル
S3にバージョンニングを有効化すると、デプロイのたびにオブジェクトバージョンが蓄積し、ストレージコストの増大とリスト処理のパフォーマンス低下を招く。ライフサイクルルールを適用し、古いバージョンはGlacierへ移行、あるいは90日後に完全削除するポリシーを必ずコード化しておけ。

—

結びにかえて

セルフホストされたPulumiバックエンドの運用は、SaaSの利便性を手放す代わりに、インフラストラクチャの主権を完全に自らの手中に収めることを意味する。
S3とDynamoDBによる堅牢なロック、AWS KMSによる多層暗号化、そして綿密に設計されたキーローテーションパイプライン。これらをコードと厳格な手順によって要塞化することこそが、真の「インフラストラクチャ・エンジニアリング」の姿である。妥協なき自動化とセキュリティの追求を、あなたのパイプラインにも実装してほしい。

タイトルとURLをコピーしました