【実務・中級編】Pulumi vs Terraform vs CDK for Terraform (CDKTF):どれを選ぶべき?徹底比較 – インフラ構成管理(IaC)活用バイブル

伝説のSREが斬る:Pulumi vs Terraform vs CDKTF ― 現場の生産性を極限まで高めるインフラ定義の選択論

インフラストラクチャ・アズ・コード(IaC)の選択は、もはや単なる「ツールの好み」ではない。それは、組織のデリバリー速度、エンジニアのメンタルヘルス、そしてクラウドコストの総額を決定づけるアーキテクチャの根幹だ。

世の中には「Terraformが業界標準だから」「TypeScriptが書きたいから」といった浅薄な理由でツールを選定し、半年後に状態ファイルの競合や複雑怪奇なHCL(HashiCorp Configuration Language)のハックに苦しむプロジェクトがあふれている。

私はこれまでに数々の大規模クラウド移行を指揮し、地獄のような状態管理の修羅場をくぐり抜けてきた。本稿では、現在のIaC界隈を牽引する Pulumi, Terraform (HCL), CDK for Terraform (CDKTF) の3者を、単なる機能比較ではなく、「実戦でエンジニアの脳内リソースをいかに奪わず、デプロイ速度を極限まで高めるか」というプロの視点から徹底的に解剖する。

—

1. 3大ツールのアーキテクチャと「真の姿」

まずは、それぞれのツールが裏側で何をやっているのか、その骨格を剥き出しにして比較する。

[Terraform (HCL)] —> HashiCorp Config Language —> Provider (Go) —> Cloud API
[CDKTF] —> TypeScript/Python/etc. —> HCL (JSON) —> Provider (Go) —> Cloud API
[Pulumi] —> General Purpose Lang —> gRPC Engine —> Provider (Go) —> Cloud API

Terraform (HCL)

  • 思想: 宣言的DSLによるインフラの静的定義。
  • 実態: 枯れたエコシステムの王様。HCLという専用言語の制約(ループや条件分岐の貧弱さ)が、かえって巨大なチームでの「コードの属人化を防ぐ防壁」としても機能する。しかし、複雑な抽象化を行おうとすると、モジュールのネスト地獄に陥る。

CDK for Terraform (CDKTF)

  • 思想: 既存のTerraformエコシステム(膨大なProvider)の資産をそのままに、汎用言語の表現力を手に入れる。
  • 実態: コードをコンパイルして巨大なJSON形式のTerraform設定ファイルを出力する中間レイヤー。生成されたHCL/JSONを読む羽目になった時、抽象化のレイヤーが2枚重なっていることのデバッグ辛さを痛感する。

Pulumi

  • 思想: 汎用プログラミング言語(TypeScript, Python, Go, C#等)を用いた真のインフラストラクチャ・アズ・ソフトウェア。
  • 実態: 独自のエグゼキューションエンジンが各言語のランタイムとgRPCで通信し、リソースのグラフを構築する。HCLの「言語としての限界」を完全に過去のものにするが、言語ランタイムの管理(Node.jsのバージョンなど)という新たな運用コストが生まれる。

—

2. 比較軸:学習コスト・エコシステム・柔軟性

| 評価軸 | Terraform (HCL) | CDKTF | Pulumi |
| :— | :— | :— | :— |
| 学習コスト | 低〜中(HCL独自の癖がある) | 高(TS/Python + TFの知識が必要) | 中〜高(言語能力に依存) |
| エコシステム | 最強(すべてのクラウドが最初に対応) | 強(Terraform Providerをそのまま流用可能) | 強(Terraform Providerを自動変換して取り込み可能) |
| 表現力・柔軟性 | 低(泥臭い記述や変数の駆使が必要) | 高(汎用言語のクラスや関数が使える) | 最高(非同期処理、テスト駆動インフラが可能) |
| 状態管理 (State) | Terraform Cloud / S3 + DynamoDB | Terraform Cloud / S3 + DynamoDB | Pulumi Service / S3 / GCS等 |

ここで特筆すべきは、Pulumiの「Terraform Providerブリッジ技術」だ。Pulumiは、Terraformの膨大なProviderエコシステムを自動的に自社のSDK(`pulumi-terraform-native` またはブリッジ版)に変換して取り込んでいる。つまり、「TerraformにあるあのマイナーなSaaSのプロバイダーがPulumiにはない」という懸念は、現在ほとんど存在しない。

—

3. 生産性を極限まで高める「プロの実践テクニック」

ここからが本題だ。ツールを導入するだけでは生産性は上がらない。日々の開発体験を劇的にブーストするための神設定、キーボードショートカット、そして設定のベストプラクティスを伝授する。

A. 開発スピードを加速するエディタ(VS Code)設定 & ショートカット

インフラコードを書く際の手間を減らす。

  • 絶対入れるべき神プラグイン:
  • Error Lens: インフラコードのバリデーションエラーや型の不一致をコード行の末尾にインライン表示し、ビルドエラーを未然に防ぐ。
  • indent-rainbow: 複雑なJSON/YAMLやブロック構造の視認性を爆発的に高める。
  • GitLens: 「このセキュリティグループのイングラウンドルール、誰が何のチケットで追加したんだ?」を瞬時に特定。
  • 極限まで手数を減らすキーボードショートカット(VS Code):
  • `Ctrl + Shift + P` -> `Preferences: Open Keyboard Shortcuts (JSON)` に以下を追加せよ。

// インフラコードの高速リファクタリングとターミナル操作の直結
{
“key”: “alt+shift+t”,
“command”: “workbench.action.terminal.sendSequence”,
“args”: { “text”: “pulumi up –refresh\n” }
},
{
“key”: “alt+shift+p”,
“command”: “workbench.action.terminal.sendSequence”,
“args”: { “text”: “pulumi preview\n” }
}

  • これにより、コードを書きながら `Alt + Shift + P` で即座にプレビュー(差分確認)、問題なければ `Alt + Shift + T` で適用という、「思考を中断させないフロー」が完成する。

B. チーム開発で絶対共有すべき「設定の共通化ルール」

複数人でIaCを触る際、最大の敵は「フォーマットの不統一」と「シークレットの誤爆」だ。

1. Linter / Formatterの強制 (Pre-commit hooks):
手動でのフォーマットなど信用するな。Gitのコミット時に自動で静的解析とフォーマットを走らせる。
2. 構成管理のモジュール分割原則:
「1つの巨大なStateファイル」はチーム開発の癌である。ネットワーク、データベース、アプリケーション層で完全にStateを分離し、Outputs / Imports(またはPulumiの `StackReference`)で結合する。

—

4. 【実例】プロダクション品質のPulumi(TypeScript)設定構成例

HCLやCDKTFの冗長さを排し、Pulumiがいかにエレガントかつ堅牢にインフラを記述できるか、TypeScriptによる実用的なベストプラクティス構成を示す。

ディレクトリ構造

my-cloud-infra/
├── .gitignore
├── Pulumi.yaml
├── Pulumi.prod.yaml # 本番環境設定
├── package.json
├── tsconfig.json
└── src/
├── index.ts # エントリーポイント
├── network/
│ └── vpc.ts # VPC・サブネット構築コンポーネント
└── database/
└── rds.ts # RDS構築コンポーネント

1. `Pulumi.yaml` (プロジェクト定義)

name: my-cloud-infra
runtime: nodejs
description: Production-grade AWS infrastructure managed by Pulumi

2. `src/network/vpc.ts` (カスタムコンポーネントによるカプセル化)

import as aws from “@pulumi/aws”;
import as pulumi from “@pulumi/pulumi”;

interface VpcArgs {
cidrBlock: string;
environment: string;
}

/

  • ネットワーク層を安全に構築するためのカスタムコンポーネント
  • 組織のセキュリティ要件(パブリック/プライベートサブネットの分離)を強制する

/
export class SecureVpc extends pulumi.ComponentResource {
public readonly vpcId: pulumi.Output;
public readonly publicSubnetIds: pulumi.Output;
public readonly privateSubnetIds: pulumi.Output;

constructor(name: string, args: VpcArgs, opts?: pulumi.ComponentResourceOptions) {
super(“custom:infra:SecureVpc”, name, args, opts);

// VPCの作成
const vpc = new aws.ec2.Vpc(`${name}-vpc`, {
cidrBlock: args.cidrBlock,
enableDnsHostnames: true,
enableDnsSupport: true,
tags: {
Environment: args.environment,
ManagedBy: “Pulumi”,
},
}, { parent: this });

// インターネットゲートウェイ
const igw = new aws.ec2.InternetGateway(`${name}-igw`, {
vpcId: vpc.id,
tags: { Environment: args.environment },
}, { parent: this });

// パブリックサブネット(マルチAZ)
const publicSubnet = new aws.ec2.Subnet(`${name}-subnet-public`, {
vpcId: vpc.id,
cidrBlock: “10.0.1.0/24”,
mapPublicIpOnLaunch: true,
availabilityZone: “ap-northeast-1a”,
tags: { Name: `${name}-public-1a` },
}, { parent: this });

// ルートテーブルの設定
const publicRt = new aws.ec2.RouteTable(`${name}-rt-public`, {
vpcId: vpc.id,
routes: [{ cidrBlock: “0.0.0.0/0”, gatewayId: igw.id }],
}, { parent: this });

new aws.ec2.RouteTableAssociation(`${name}-rta-public`, {
subnetId: publicSubnet.id,
routeTableId: publicRt.id,
}, { parent: this });

// 出力を公開
this.vpcId = vpc.id;
this.publicSubnetIds = pulumi.all([publicSubnet.id]).apply(ids => ids);

// リソースの親子関係を明示して登録完了
this.registerOutputs({
vpcId: this.vpcId,
publicSubnetIds: this.publicSubnetIds,
});
}
}

3. `src/index.ts` (メインエントリーポイント)

import as pulumi from “@pulumi/pulumi”;
import { SecureVpc } from “./network/vpc”;

// スタックの設定値(Pulumi.prod.yamlなどから自動ロード)
const config = new pulumi.Config();
const env = pulumi.getStack();
const vpcCidr = config.require(“vpcCidr”);

// セキュアなVPCのインスタンス化
const vpc = new SecureVpc(“core”, {
cidrBlock: vpcCidr,
environment: env,
});

// 外部にエクスポートする値(CLIやCI/CDパイプラインで参照可能)
export const vpcId = vpc.vpcId;
export const publicSubnets = vpc.publicSubnetIds;

このコードを見れば一目瞭然だが、HCLの制限である「強引な `count` や `for_each` のハック」は一切不要である。通常のプログラミング言語のクラスや関数、そして強力な型システムによって、「間違ったインフラコードが書けない構造」をコードレベルで担保できる。

—

5. 結論:プロジェクトの要件に応じた最適なツールの選び方

で、結局どれを選ぶべきなのか? 現場の状況に応じて以下の基準で即決せよ。

1. Terraform (HCL) を選ぶべき現場:

  • チームメンバーのバックグラウンドがインフラエンジニア中心であり、専用DSL(HCL)の習得に抵抗がない。
  • エコシステムの安定性と「前例の多さ(ググれば大抵の解決策が出てくる)」を最優先したい。
  • 複雑なビジネスロジックをインフラに持ち込みたくない(シンプル・愚直に書きたい)。

2. CDKTF を選ぶべき現場:

  • すでにTerraformの膨大なモジュール資産(Terraform Registry)があり、それを捨てたくないが、TypeScriptやPythonの表現力(ループ処理など)でラップしたい。
  • ※ただし、中間生成されるJSON/HCLのデバッグ地獄を受け入れる覚悟が必要。

3. Pulumi を選ぶべき現場:

  • 開発チームの主力言語が TypeScript, Python, Go であり、インフラもアプリケーションコードと同じ言語・テスト手法・CI/CDパイプラインで統合管理したい。
  • 動的なインフラ構築(動的なサブネット計算、APIを叩いて動的にリソースを生成するなど)が必要。
  • 開発スピードとエンジニアの心理的安全性(モダンな開発体験)を極限まで高めたい場合、選択肢はPulumi一択である。

インフラストラクチャは「お守り」ではない。ビジネスのスピードに合わせて俊敏に変化し続けるべき「ソフトウェアの一部」なのだ。どのツールを選ぶにせよ、その特性を骨の髄まで理解し、チーム全体の生産性を最大化する設計を貫いてほしい。

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