【入門編】Pulumi型安全性を極める:TypeScript/Pythonの厳格なスキーマ検証とカスタムバリデーション実装術 – インフラ構成管理(IaC)活用バイブル

こんにちは!クラウドインフラ・SREの世界へようこそ。
日々、AWSやGCP、Azureなどのクラウド環境を構築・運用していると、こんな絶望的な瞬間に直面したことはありませんか?

  • 「あ、本番環境のインスタンスタイプに `t2.micro` って書いたまま `pulumi up` しちゃった……!」
  • 「環境変数名タイポして、変な名前のS3バケットがパブリック公開で作られちゃった……!」
  • 「デプロイが完了して数十分後に、CI/CDのパイプラインやAWS Configの通知で『設定ミス』を知る」

……夜中にこんなアラートが鳴り響いたら、エンジニアとして冷や汗ものですよね。

インフラストラクチャをコード化するIaC(Infrastructure as Code)において、私たちは長年「実際にクラウドへデプロイしてみるまで、パラメータのミスに気づけない」という宿命と戦ってきました。Terraformのプランを見ても、数千行あるJSONやHCLの海からヒューマンエラーを見つけ出すのは至難の業です。

しかし、Pulumiを使えば、その悩みは今日で終わりです。
今回は、世界中のプログラミング言語が持つ強力な型システムと、バリデーションライブラリ(TypeScriptなら Zod、Pythonなら Pydantic)を組み合わせ、「デプロイする前に、不適切なインフラパラメータを100%弾き返す」ための極限の知見を、優しく丁寧にお伝えします。

これをマスターすれば、あなたのインフラ開発は劇的に安全になり、毎日の作業から「うっかりミスによる障害の恐怖」が消え去りますよ。

—

1. なぜPulumiで「型安全性」と「バリデーション」が重要なのか?

従来のIaCツール(HCLなど)は、独自ドメイン言語(DSL)の限界もあり、複雑な条件分岐や厳格な型チェックをコードレベルで行うのが苦手でした。結果として、実行時(Runtime)エラー、もっと悪い場合は「クラウドプロバイダに怒られて初めて気づくエラー」が多発していました。

一方、Pulumiは TypeScript, Python, Go, C# といった本物の汎用プログラミング言語でインフラを記述します。
ということは、「アプリケーション開発で私たちが当たり前に行っている堅牢な入力値検証(バリデーション)を、そのままインフラ構築に応用できる」ということです。

「クラウドにリソースをリクエストする前に、プログラムの境界線でバグをねじ伏せる」。これがSREとしての正しい姿であり、Pulumiの真骨頂です。

—

2. 開発環境のセットアップと「HelloWorld」の作法

まずは、TypeScript(またはPython)を使って、Pulumiのプロジェクトを立ち上げ、型安全な世界への第一歩を踏み出しましょう。今回は多くの現場で採用されている TypeScript をベースに解説します(Pythonでも考え方は全く同じです)。

事前準備

  • Node.js (v18以上推奨)
  • Pulumi CLI のインストール
  • AWSなどのクラウド認証情報の設定(今回はAWSを想定)

プロジェクトの初期化

お好きなディレクトリで以下のコマンドを実行してください。

mkdir pulumi-type-safety-lab
cd pulumi-type-safety-lab
pulumi new aws-typescript –dir . –name pulumi-type-safety-lab –stack dev –yes

これで、TypeScriptベースのPulumiプロジェクトの雛形が生成されます。
自動生成された `package.json` に、今回の主役であるバリデーションライブラリ `zod` をインストールしましょう。

npm install zod
npm install -D @types/node

—

3. Zodで実現する!インフラパラメータの厳格なスキーマ検証

ここからが本番です。
例えば、「AWSのEC2インスタンスを作る」という要件を考えてみましょう。

  • 環境名(`env`)は `dev`, `stg`, `prd` のいずれかであること。
  • インスタンスタイプ(`instanceType`)は、本番(`prd`)のときだけコストの高い `t3.medium` 以上を許可し、開発(`dev`)のときは `t3.micro` または `t3.small` に制限したい。
  • タグ(`owner`)のメールアドレスは特定のドメイン形式であること。

これを適当に書くとバグの温床になりますが、Zod を使えば美しく宣言的にバリデーションを定義できます。

実装コード:`index.ts`

プロジェクトルートの `index.ts` を以下のように書き換えてみてください。

import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
import { z } from “zod”;

// ==========================================
// 1. 設定値のスキーマ定義(Zodによる厳格な型チェック)
// ==========================================
const ConfigSchema = z.object({
// 環境名は dev, stg, prd のみ許可
env: z.enum([“dev”, “stg”, “prd”], {
errorMap: () => ({ message: “環境(env)は ‘dev’, ‘stg’, ‘prd’ のいずれかで指定してください。” }),
}),

// インスタンスタイプは文字列だが、空文字は許容しない
instanceType: z.string().min(1, “インスタンスタイプは必須です。”),

// オーナーのメールアドレス(社内ドメイン縛りの例)
ownerEmail: z.string().email(“有効なメールアドレス形式である必要があります。”),
});

// ==========================================
// 2. PulumiのConfigから値を取得し、検証を実行
// ==========================================
const config = new pulumi.Config();

// 設定値の生データ(プレーンなオブジェクト)
const rawConfigInput = {
env: config.require(“env”),
instanceType: config.get(“instanceType”) || “t3.micro”, // デフォルト値
ownerEmail: config.require(“ownerEmail”),
};

// 【重要】ここでパース(検証)を行う!
// 不正な値があれば、ここで即座にプロセスがクラッシュし、デプロイを防ぎます。
const parsedConfig = ConfigSchema.parse(rawConfigInput);

// ==========================================
// 3. ビジネスロジック・カスタムバリデーション
// ==========================================
// スキーマの基本検証を通過したあとに、インフラ固有の複雑なビジネスルールをチェック
if (parsedConfig.env === “prd” && parsedConfig.instanceType === “t3.micro”) {
throw new Error(
“【デプロイ中止】本番環境(prd)で ‘t3.micro’ を使用することはポリシー違反です。t3.medium以上を指定してください。”
);
}

// ==========================================
// 4. クラウド・リソースの構築
// ==========================================
// 検証を無事に通過した安全なパラメータだけを使ってリソースを定義します
const securityGroup = new aws.ec2.SecurityGroup(“web-sg”, {
description: `Security group for ${parsedConfig.env}`,
ingress: [
{ protocol: “tcp”, fromPort: 80, toPort: 80, cidrBlocks: [“0.0.0.0/0”] },
],
});

// 最新のAmazon Linux 2023 AMIを取得
const ami = aws.ec2.getAmi({
mostRecent: true,
filters: [{ name: “name”, values: [“al2023-ami-2023.-x86_64”] }],
owners: [“amazon”],
});

const server = new aws.ec2.Instance(“web-server”, {
ami: ami.then(a => a.id),
instanceType: parsedConfig.instanceType,
vpcSecurityGroupIds: [securityGroup.id],
tags: {
Name: `web-server-${parsedConfig.env}`,
ManagedBy: “Pulumi”,
Owner: parsedConfig.ownerEmail,
},
});

// エクスポート(出力値)
export const serverId = server.id;
export const serverPublicIp = server.publicIp;

—

4. 動作確認:エラーが「デプロイ前」に弾かれる瞬間を体感する

それでは、実際にこのコードの動作を確認してみましょう。
Pulumiのスタック設定(`Pulumi.dev.yaml`)に、わざと不適切な値を仕込んでみます。

パターンA:不正な環境名を入れた場合

`Pulumi.dev.yaml` に以下のように設定します。

config:
pulumi-type-safety-lab:env: “invalid-env”
pulumi-type-safety-lab:ownerEmail: “sre-team@example.com”

この状態で `pulumi up` を実行すると……?

$ pulumi up
Previewing update (dev):
…
error: ZodError: [
{
“code”: “invalid_enum_value”,
“options”: [
“dev”,
“stg”,
“prd”
],
“received”: “invalid-env”,
“path”: [
“env”
],
“message”: “環境(env)は ‘dev’, ‘stg’, ‘prd’ のいずれかで指定してください。”
}
]

AWSへの接続やリソースのプレビューが行われるよりもはるか手前(Node.jsの実行フェーズ)で、親切かつ明確なエラーメッセージと共に処理が即座に中断されました! 無駄なAWS APIコールも発生しません。

パターンB:本番環境で貧弱なインスタンスを指定した場合

今度は、環境を `prd` にして、インスタンスに `t3.micro` を指定してみます。

config:
pulumi-type-safety-lab:env: “prd”
pulumi-type-safety-lab:instanceType: “t3.micro”
pulumi-type-safety-lab:ownerEmail: “sre-team@example.com”

実行結果:

$ pulumi up
Previewing update (dev):
…
error: Error: 【デプロイ中止】本番環境(prd)で ‘t3.micro’ を使用することはポリシー違反です。t3.medium以上を指定してください。

カスタムバリデーションのロジックが完璧に機能し、社内ニッチなポリシー違反も未然にブロックできました。

—

5. Python派のあなたへ:Pydanticでの実装アプローチ

「うちはTypeScriptじゃなくてPythonなんだよ!」というSREエンジニアの方もご安心ください。Pythonの業界標準である Pydantic を使えば、まったく同じことがスマートに実現できます。

参考までに、Python(Pulumi Python)でのスキーマ定義のイメージを載せておきます。

from pydantic import BaseModel, EmailStr, Field, model_validator
import pulumi

Pydanticによるスキーマ定義
class InfraConfig(BaseModel):
env: str = Field(…, pattern=”^(dev|stg|prd)$”)
instance_type: str = Field(…, min_length=1)
owner_email: EmailStr

# カスタムバリデーション(モデル全体に対する検証)
@model_validator(mode=”after”)
def validate_production_constraints(self) -> “InfraConfig”:
if self.env == “prd” and self.instance_type == “t3.micro”:
raise ValueError(“本番環境(prd)で t3.micro は使用できません。”)
return self

Pulumiの設定から取得してバリデーション
config = pulumi.Config()
try:
validated_config = InfraConfig(
env=config.require(“env”),
instance_type=config.get(“instanceType”) or “t3.micro”,
owner_email=config.require(“ownerEmail”)
)
except Exception as e:
raise RuntimeError(f”インフラ設定のバリデーションエラー:\n{e}”)

Pydanticの `@model_validator` や `Field(pattern=…)` は非常に強力です。Pythonの型ヒントと完全に統合されているため、IDEの補完も抜群に効きます。

—

6. まとめ:型安全性とバリデーションがSREの夜を守る

いかがでしたでしょうか?
今回は、PulumiでTypeScriptのZod(またはPythonのPydantic)を組み合わせ、インフラパラメータの入力値検証を極限まで厳格化する方法を解説しました。

この手法を取り入れることで、以下の圧倒的なメリットが手に入ります。

1. 「デプロイしてみないとわからない」からの脱却
クラウドAPIを叩く前にエラーを検知するため、CI/CDパイプラインのフィードバックループが劇的に高速化します。
2. 組織のインフラポリシーのコード化
「本番環境でのインスタンスサイズ制限」「命名規則の強制」「必須タグの付与」などをプログラムとして強制でき、人間のうっかりミスをシステムが自動で防いでくれます。
3. IDEによる最高の開発体験
型がしっかり定義されているため、VSCodeなどのエディタがコード補完や誤ったプロパティの指摘をリアルタイムで行ってくれます。

「これをマスターすれば、毎日の作業が劇的に楽になりますよ」。
夜中にインフラのコンフィグミスで呼び出される悪夢から解放され、よりクリエイティブなアーキテクチャ設計に集中できるようになります。

明日からのインフラ構築に、ぜひ「厳格な型安全性」を取り入れてみてください。あなたのSREライフがより快適になることを心から応援しています!

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