Pulumi ESC(Environments, Secrets, and Configurations)完全ガイド:次世代シークレット・設定管理の全貌
こんにちは。長年にわたり大規模クラウドインフラの自動化とSREプラットフォームの構築に身を捧げてきた。
HCL(HashiCorp Configuration Language)の静的な限界に絶望し、カスタムシェルスクリプトのスパゲッティコードに涙を流してきたエンジニアなら、今日のテーマは心臓が跳ねるほど魅力的はずだ。
現代のインフラストラクチャにおいて、最大にして最も厄介な敵は「設定の肥大化」と「シークレットのライフサイクル管理」だ。
AWS SSM Parameter Store、AWS Secrets Manager、HashiCorp Vault、Doppler、1Password、そして環境変数……。これらがカオスに散らばり、誰がどこで何を参照しているか分からない「設定のブラックボックス化」が、幾多のデプロイを失敗に導いてきた。
ここで登場するのが Pulumi ESC(Environments, Secrets, and Configurations) だ。
これは単なるシークレットマネージャーのラッパーではない。インフラストラクチャの状態(State)から切り離された、「階層型・動的・文脈依存のコンテキスト・オーケストレーション・エンジン」である。
本稿では、Pulumi ESCの内部アーキテクチャの深層から、OIDC(OpenID Connect)を用いた完全なパスワードレス・シークレット動的生成、多層環境(dev/staging/production)における安全なオーバーライド戦略、そしてAPIを直叩きする高度な自動化まで、骨の髄まで解説する。
—
1. 内部アーキテクチャの解剖:なぜESCは従来のツールを駆逐するのか?
従来のIaCツール(Terraform/OpenTofu等)やシークレット管理は、静的な値の保持に終始していた。「暗号化された文字列を保存し、実行時に復号する」――これだけの仕組みでは、クラウドネイティブな動的インフラのスピードについていけない。
ESCのコアエンジンは、以下の3つのレイヤーで構成されている。
[External Identity Providers (AWS/GCP/GitHub)]
│ (OIDC / JWT)
▼
┌────────────────────────┐
│ Pulumi ESC Engine │
│ (Dynamic Resolution) │
└───────┬────────┬───────┘
│ │
┌─────────┘ └─────────┐
▼ ▼
[Dynamic Secrets] [Hierarchical Inheritance]
(AWS STS / Vault / DB) (Base -> Env -> User)
1. Dynamic Resolution(動的解決プロバイダ):
ESCは値を持つだけでなく、実行時に外部APIを叩いて「その場で一時的なクレデンシャルを生成」するプロセスの抽象化層を持つ。AWS STSからのAssumeRole、VaultからのDynamic Secrets、StripeやDatadogのAPIトークン生成をネイティブに統合する。
2. Hierarchical Inheritance(多重継承グラフ):
オブジェクト指向におけるクラス継承のように、環境設定をツリー構造で継承・オーバーライドできる。`base` 環境があり、それを継承した `staging`、さらにそれを継承した開発者個人の `dev-alice` 環境を構築できる。
3. Stateless Evaluation(ステートレス評価モデル):
ESC環境は、PulumiのStateファイルとは独立して評価(Evaluate)される。つまり、Pulumiだけでなく、Kubernetesのマニフェスト生成、GitHub Actionsのワークフロー、ローカルでのコンテナ起動など、あらゆるコンテキストで同一の信頼できる設定ソース(Single Source of Truth)として機能する。
—
2. 実践:Pulumi ESC環境定義の極致
まずは、実際のESC環境定義ファイル(YAML形式)を見ていこう。
ここでは、OIDCによるクラウド認証、AWSの一時クレデンシャルの動的生成、そして複数環境間の継承を組み合わせたプロダクション・グレードの設定を構築する。
ベース環境 (`base.yaml`)
全環境共通のベースラインと、組織共通の変数を定義する。
yaml-language-server: $schema=https://raw.githubusercontent.com/pulumi/esc/main/schema.json
values:
# 静的な設定値
aws:
region: ap-northeast-1
# 共通タグ
tags:
Project: “FinTech-Core”
ManagedBy: “Pulumi-ESC”
# 外部プロバイダのモルト連携(AWS OIDC)
# Pulumi CloudのOIDCアイデンティティを利用して静的クレデンシャルを排除
environmentVariables:
AWS_REGION: ${aws.region}
ステージング環境 (`staging.yaml`)
`base.yaml` を継承し、AWSの一時IAMロール(AssumeRole)と、動的なデータベースシークレットを注入する。
values:
# base環境の継承
- fn::open::esc: “base”
aws:
region: ap-northeast-1
roleArn: “arn:aws:iam::123456789012:role/PulumiESCStagingRole”
# AWS STSを利用した動的クレデンシャルの取得
# Pulumi ESCが自動的にAssumeRoleを実行し、一時的なAccessKey/SecretKeyを生成する
awsSecrets:
fn::aws::login:
roleArn: ${aws.roleArn}
sessionName: “pulumi-esc-staging-session”
duration: 3600 # 1時間で失効
environmentVariables:
AWS_ACCESS_KEY_ID: ${awsSecrets.accessKeyId}
AWS_SECRET_ACCESS_KEY: ${awsSecrets.secretAccessKey}
AWS_SESSION_TOKEN: ${awsSecrets.sessionToken}
NODE_ENV: “staging”
# 動的シークレット(例: 外部APIトークンやデータベース接続文字列)
secrets:
databaseUrl: “postgresql://app:${dbPassword}#rds.internal:5432/staging_db”
values-secrets:
dbPassword:
fn::secret: “enc:v1:AbCdEfG…== (Pulumi Secret Encryption)”
この構成の恐ろしいほどの美しさは、静的なシークレット(パスワードやAPIキー)をGitリポジトリや開発者のローカルディスクに一切保存しなくてよい点にある。すべては実行時の動的解決(Dynamic Resolution)に委ねられる。
—
3. 外部IDP(OIDC)とCI/CDパイプラインの完全統合
モダンなDevOpsパイプラインにおいて、GitHub ActionsやGitLab CIからクラウドへアクセスする際に、長期的なIAMシークレット(`AWS_ACCESS_KEY_ID` など)をリポジトリのSecretsに保存することは「アンチパターン」の極みである。
Pulumi ESCとGitHub ActionsをOIDCで結合し、完全にパスワードレスなパイプラインを構築する手順を解説する。
GitHub Actions ワークフローの極限最適化
以下のワークフローでは、GitHub自身が発行するOIDCトークンをPulumi ESCに渡し、ESC側でそれを検証した上で、クラウドの一時クレデンシャルや各種シークレットを環境変数として自動展開する。
name: “Production Deploy via Pulumi ESC & OIDC”
on:
push:
branches:
- main
permissions:
id-token: Wite # OIDCトークンの発行に必須
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
# Pulumi CLIのセットアップ
- name: Setup Pulumi
uses: pulumi/actions@v5
# Pulumi ESCを用いてシークレットと環境変数を動的にインポート
# 認証には自動的に環境変数 (PULUMI_ACCESS_TOKEN) またはOIDCが使用される
- name: Login to Pulumi ESC & Export Environment
uses: pulumi/esc-action@v1
with:
organization: “my-enterprise-org”
environment: “production”
export-to: “env” # 取得したすべての値をGitHub Actionsのenvに流し込む
- name: Run Pulumi Up
uses: pulumi/actions/up@v5
with:
stack: “production”
work-dir: “./infra”
env:
# ESCによって解決された環境変数がそのままPulumiプロセスに引き渡される
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ env.AWS_SESSION_TOKEN }}
DATABASE_URL: ${{ env.DATABASE_URL }}
このパイプラインには、ハードコードされたクラウドの長期クレデンシャルが1バイトたりとも存在しない。セキュリティ監査において、これ以上の回答はないだろう。
—
4. APIとCLIを駆使した高度な自動化スクリプト
UIやコンソールでの操作は人間のミスを生む。インフラエンジニアであれば、すべてをAPIとCLIでコード化し、冪等性を担保すべきだ。Pulumi ESCは強力なCLIおよびREST APIを提供する。
ここでは、Pythonを用いて、動的にESC環境を評価し、その結果をJSONとして抽出して任意のローカル設定ファイルに書き出すカスタム自動化スクリプトを提示する。
!/usr/bin/env python3
“””
Pulumi ESC Environment Resolver Script
高度なSRE自動化のためのESCプログラム的評価スクリプト
“””
import subprocess
import json
import sys
import os
ORG_NAME = “my-enterprise-org”
ENVIRONMENT_NAME = “staging”
def run_esc_open(org: str, env: str) -> dict:
“””
Pulumi ESC CLIを使用して環境を評価(Open)し、解決されたすべての変数をJSONとして取得する。
“””
cmd = [“esc”, “open”, f”{org}/{env}”, “–format”, “json”]
print(f”[] Resolving ESC environment: {org}/{env}…”, file=sys.stderr)
try:
result = subprocess.run(
cmd,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
print(f”[!] Error resolving ESC environment: {e.stderr}”, file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f”[!] Failed to parse ESC output as JSON: {e}”, file=sys.stderr)
sys.exit(1)
def main():
# 1. ESC環境の評価と値の解決
resolved_data = run_esc_open(ORG_NAME, ENVIRONMENT_NAME)
# 2. 必要な環境変数やシークレットの抽出
env_vars = resolved_data.get(“environmentVariables”, {})
secrets = resolved_data.get(“secrets”, {})
print(f”[] Successfully resolved {len(env_vars)} environment variables and {len(secrets)} secrets.”, file=sys.stderr)
# 3. ローカルのランタイム設定ファイル(例: .env.gen)として安全にダンプ
# 権限を 0600 に設定して書き出す
output_path = “.env.generated”
try:
# ファイルディスクリプタを直接操作して厳格なパーミッションで作成
fd = os.open(output_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, ‘w’) as f:
f.write(“# This file is auto-generated by Pulumi ESC. DO NOT EDIT.\n”)
for key, value in env_vars.items():
f.write(f”{key}={value}\n”)
for key, value in secrets.items():
f.write(f”SECRET_{key.upper()}={value}\n”)
print(f”[] Securely generated config file at: {output_path}”, file=sys.stderr)
except Exception as e:
print(f”[!] Failed to write config file: {e}”, file=sys.stderr)
sys.exit(1)
if __name__ == “__main__”:
main()
このスクリプトをローカル開発環境や、コンテナの初期化フェーズ(Entrypoint)に組み込むことで、開発者は `pulumi esc run staging — my-app` のように、シークレットがメモリ上にのみ展開された安全なサンドボックス内でアプリケーションを起動できるようになる。
—
5. パフォーマンス・セキュリティ・最適化の極意(低レイヤ知見)
最後に、Pulumi ESCをプロダクション環境の幹として運用する上で、筆者が現場の修羅場から導き出した極限の最適化知見を共有する。
1. 評価キャッシュとスロットリングの回避
ESCは動的プロバイダ(AWS STS, Vault等)を叩いて値を解決するため、頻繁すぎる環境のオープンは外部APIのレートリミット(Throttling)を引き起こす。
- 対策: ローカル開発では `esc open` の結果をセッション単位でキャッシュし、CI/CDパイプラインではジョブごとに一度だけ解決(Export)して、以降のステップでは環境変数として使い回すアーキテクチャを徹底すること。
2. 最小権限の原則(Least Privilege)と監査ログ
ESC経由で取得される動的シークレットの有効期限(TTL)は、可能な限り短く設定せよ。
- AWS STSのセッション有効期限はデフォルトの1時間ではなく、デプロイに必要な最小限の時間(例: `900` 秒=15分)に絞る。
- Pulumi Cloudの監査ログ(Audit Logs)を Datadog や AWS S3/CloudWatch Logs に転送し、「誰が、どの環境設定を、いつ評価したか」の完全なトレーサビリティをSREチームとして担保すること。
3. シークレット汚染(Secret Pollution)の防止
環境変数をログに出力するミス(`console.log(process.env)` やバグによるトレースバック)は、いかに優れた組織でも発生し得る。
- Pulumi ESCのシークレット管理機構(`fn::secret`)を通じた値は、ESC CLIやAPIによって自動的にマスク処理(`[secret]`)される。
- アプリケーションコード側でも、ログライブラリのマスキングフィルターと組み合わせることで、万が一の漏洩リスクを二重・三重にブロックする設計にせよ。
—
結びにかえて
インフラストラクチャのコード化(IaC)が当たり前になった今、次のフロンティアは間違いなく「設定とシークレットの動的オーケストレーション」である。
Pulumi ESCは、静的でサイロ化した設定管理の悪夢から私たちを解放し、コードのように美しく、かつクラウドネイティブに動的な設定基盤を提供してくれる。
静的なシークレットを捨て去り、OIDCと動的解決に満ちた次世代のインフラ管理へ移行せよ。あなたのパイプラインは、もっと速く、もっと安全になるはずだ。