TerraformとPulumiの実装比較:インフラコード化でどちらを選ぶべきか

TerraformとPulumiは両者ともInfrastructure as Code(IaC)の主流ツールですが、言語の汎用性、学習曲線、チーム体制によって最適な選択は異なります。本記事では、実務での導入判断に必要な機能比較、コード例、そして具体的なユースケースを提供します。

TerraformとPulumiの位置付け

Infrastructure as Code市場において、TerraformとPulumiは異なる哲学で設計されています。

Terraform

Pulumi


flowchart LR
    A["インフラコード記述"] --> B{言語の選択}
    B -->|HCLを学ぶ| C["Terraform"]
    B -->|既知のプログラミング言語| D["Pulumi"]
    C --> E["宣言的定義"]
    D --> F["手続き的定義"]
    E --> G["シンプル、予測可能"]
    F --> H["柔軟、プログラマブル"]
  

主要機能の詳細比較

状態管理とバックエンド

Terraformは明示的な状態ファイル(terraform.tfstate)を使用してリソースの現在の状態を追跡します。ローカルストレージ、S3、Azure Blob Storage、Terraformクラウドなど、複数のバックエンドをサポートしています。

# Terraform: S3バックエンドの設定例
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}
  

Pulumiも同様に状態を追跡しますが、デフォルトではPulumi Service(クラウドホスト)またはセルフホストのバックエンドを使用します。設定はより簡潔です。

# Pulumi: バックエンド設定(Pulumi.yamlの例)
name: my-infrastructure
runtime: python
backend:
  url: s3://my-pulumi-state
  

実務では、Terraformの状態ロック機能(DynamoDB統合)が大規模チームでの競合防止に有効です。一方、Pulumiはスタック概念によってプロジェクト単位での管理が直感的です。

プログラミング言語とコードの再利用性

Terraformは独自のHCLを学ぶ必要があります。これはシンプルですが、複雑なロジックを記述する際に表現力が限定されます。モジュール機能で再利用可能なコンポーネントを作成できます。

# Terraform: EC2インスタンスとセキュリティグループの定義
resource "aws_security_group" "web" {
  name = "web-sg"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  vpc_security_group_ids = [aws_security_group.web.id]

  tags = {
    Name = "web-server"
  }
}
  

Pulumiは汎用言語を使用するため、既知の言語スキルを即座に活かせます。ループ、関数、クラスなどの言語機能をそのまま使用可能です。

# Pulumi: Pythonでの同等の実装
import pulumi
import pulumi_aws as aws

# セキュリティグループの定義
web_sg = aws.ec2.SecurityGroup("web-sg",
    ingress=[
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=80,
            to_port=80,
            cidr_blocks=["0.0.0.0/0"],
        ),
    ],
    egress=[
        aws.ec2.SecurityGroupEgressArgs(
            protocol="-1",
            from_port=0,
            to_port=0,
            cidr_blocks=["0.0.0.0/0"],
        ),
    ])

# EC2インスタンスを3つ作成(Pythonのループを活用)
instances = []
for i in range(3):
    instance = aws.ec2.Instance(f"web-server-{i}",
        ami="ami-0c55b159cbfafe1f0",
        instance_type="t3.micro",
        vpc_security_group_ids=[web_sg.id],
        tags={"Name": f"web-server-{i}"})
    instances.append(instance)

pulumi.export("instance_ids", [inst.id for inst in instances])
  

このコード例は、Pythonのループ機能を活用することで、3つのインスタンスを効率的に定義しています。Terraformでもcountfor_eachで同等の処理が可能ですが、記法がより複雑です。

学習曲線と導入速度

Terraformは新しい言語(HCL)を学ぶ必要があるため、初心者向けのドキュメントが充実しています。シンプルなリソース定義から始められるため、スタートは早いです。

Pulumiはプログラミング経験者にとっては直感的ですが、IaC特有の概念(スタック、シークレット管理、参照の遅延評価など)の理解に時間がかかります。特にプログラミング初心者には学習曲線が急です。

プロバイダーのサポート状況

Terraformは圧倒的にプロバイダー数が多く、AWS、Azure、GCP、KubernetesDatadogGitHub、Slackなど1000以上のリソースタイプをサポートしています。

Pulumiも主要クラウドプロバイダーをサポートしていますが、Terraformと比べると若干少なく、新しいサービスへの対応が遅れることがあります。ただし、Terraformプロバイダーをラップしているため、Terraformが対応しているリソースの大部分はPulumiでも利用可能です。


graph TD
    A["プロバイダーサポート"] --> B["Terraform"]
    A --> C["Pulumi"]
    B --> D["AWS"]
    B --> E["Azure"]
    B --> F["GCP"]
    B --> G["1000+ その他"]
    C --> H["AWS"]
    C --> I["Azure"]
    C --> J["GCP"]
    C --> K["200+ その他
一部はTerraform経由"]

実装パターンと実務での選択基準

Terraformを選ぶべき場面

  • チーム全体の学習コストを最小化したい場合:HCLは小さな学習曲線で導入できます
  • 複雑なプロバイダーエコシステムが必要な場合:Terraform固有のプロバイダーを多数使用する場合、選択肢がPulumiより豊富です
  • 宣言的なシンプルさを重視する場合:「目指すべき状態」の明確性により、予測可能な運用ができます
  • 業界標準ツールが必要な場合:ジョブマーケット、コミュニティリソース、採用面で圧倒的有利です
  • 非エンジニアもコード化に参加させたい場合:HCLの シンプルさにより、インフラエンジニア以外の参入障壁が低い

Pulumiを選ぶべき場面

  • 開発チームがすでに特定の言語スキルを持つ場合:Python、TypeScript、Goなど、既知言語のスキルを直接活用できます
  • 複雑なプログラミングロジックが必要な場合:条件分岐、ループ、関数の再利用、テストの記述などが言語レベルで自然にできます
  • 開発パイプラインと密に統合したい場合:CI/CDパイプライン内で、インフラコードとアプリケーションコードを同じ言語で管理できます
  • スタートアップやイノベーション重視の組織:新しいツールへの投資が経営方針と合致している場合

実務ケーススタディ:中規模SaaS企業での導入判断

筆者の経験上、従業員50-200名のSaaS企業では以下のような事例がありました:

ケース1:Terraform導入企業

  • インフラチーム(3-4名)が中心となってコード化
  • 開発チーム(10-20名)は参照のみで、インフラチームに依頼する運用
  • 結果:運用負荷が集中し、デプロイ速度が遅延。HCLの学習コストは最小限に抑えられたが、スケール上の問題が発生

ケース2:Pulumi(TypeScript)導入企業

  • 開発チームのTypeScriptスキルを活用し、各機能チームが自身のインフラを定義
  • プラットフォームチーム(2名)がコアライブラリ(再利用可能なコンポーネント)を管理
  • 結果:開発チームの自律性向上、デプロイ時間短縮。ただし、IaC概念の導入研修に1ヶ月要した

このケースから、組織の成熟度とチーム構成により最適なツールが異なることが明確です。

パフォーマンスとコスト考慮事項

実行速度

Terraformはterraform planで差分検出を高速に実行でき、大規模インフラ(数千のリソース)でも10-30秒程度で完了します。

Pulumiも同等の速度ですが、Pythonランタイムの起動に若干の遅延があります。実務では顕著な差ではありませんが、頻繁にplan実行する開発フローでは、わずかなストレスになる可能性があります。

ライセンスと価格

Terraformはオープンソース(Mozilla Public License v2.0)で完全に無料です。Terraform Cloudは有料(Free プラン から Team & Governance $20/月以上)ですが、セルフホストの状態管理は無料です。

Pulumiも基本的にはオープンソース(Apache 2.0)ですが、Pulumi Serviceの商用機能(Team Collaboration、Advanced Policy Engine)は有料です(Team プラン $30/月から)。

コスト面ではTerraformがより低コストですが、機能要件により判断する必要があります。

よくある質問

直接的な自動移行ツールは存在しませんが、概念的には比較的容易です。各Terraformリソースに対応するPulumiリソースがあり、手動での記述変換が必要です。100-200リソース程度なら数日で移行可能ですが、数千リソースの場合は数週間から数ヶ月要することもあります。

技術的には可能です。例えば、基盤インフラはTerraform、アプリケーション固有のインフラはPulumiという使い分けが考えられます。ただし、チーム運用の複雑性が増すため、よほどの理由がない限り、単一ツールでの統一を推奨します。

TerraformやPulumiで直接管理できないサービスに対しては、以下のアプローチがあります:

どちらも本質的にセキュアですが、以下の点で差があります:

よくあるハマりポイントと解決策

Terraformでのstate lockの競合問題

複数チームメンバーが同時にデプロイすると、DynamoDBロックがタイムアウトし、エラーが発生することがあります。

# 解決策: DynamoDB設定の適切化
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# DynamoDBテーブル設定(Terraform外で実行)
# aws dynamodb create-table \
#   --table-name terraform-locks \
#   --attribute-definitions AttributeName=LockID,AttributeType=S \
#   --key-schema AttributeName=LockID,KeyType=HASH \
#   --billing-mode PAY_PER_REQUEST
  

Pulumiでの遅延評価(Lazy Evaluation)

Pulumiでは、クラウドリソースの属性(IDなど)を取得する際、非同期で解決されるため、即座に値を使用できないことがあります。

# Pulumiの解決策: apply()を使用した遅延値の処理
import pulumi
import pulumi_aws as aws

# EC2インスタンスを作成
instance = aws.ec2.Instance("web",
    ami="ami-0c55b159cbfafe1f0",
    instance_type="t3.micro")

# instance.idは直接値ではなく、Output型
# apply()を使って遅延値を処理
instance_ip = instance.public_ip.apply(lambda ip: f"http://{ip}:80")

pulumi.export("server_url", instance_ip)
  

Terraformでのリソース参照の複雑性

複数のモジュール間でリソース参照する際、参照パスが複雑になり、エラーが発生しやすいです。

# module/main.tf内で定義したセキュリティグループを他のモジュールから参照
# main.tf(メインモジュール)
module "networking" {
  source = "./modules/networking"
  vpc_cidr = "10.0.0.0/16"
}

module "compute" {
  source = "./modules/compute"
  # モジュール出力を参照
  security_group_id = module.networking.web_sg_id
}

# modules/networking/outputs.tf
output "web_sg_id" {
  value = aws_security_group.web.id
}

# modules/compute/main.tf
variable "security_group_id" {
  type = string
}

resource "aws_instance" "web" {
  # ... 設定 ...
  vpc_security_group_ids = [var.security_group_id]
}
  

テスト環境と動作確認情報

本記事のコード例は以下の環境で動作確認しました:

  • Terraform: v1.7.0 / HCL2 / AWS Provider v5.20.0
  • Pulumi: v3.85.0 / Python 3.11 / Pulumi AWS v6.15.0
  • テスト環境: macOS 14.2 / AWS Account with proper IAM permissions
  • 検証日: 2025年1月

公式ドキュメント・参考資料

Terraform公式ドキュメント

Pulumi公式ドキュメント

Terraformモジュール開発ガイド

Pulumiシークレット管理ドキュメント

まとめ

  • Terraform
  • Pulumi
  • プロバイダーのサポート範囲ではTerraformが圧倒的に有利。特定のクラウドサービスに依存する場合は事前確認が必須
  • チーム規模、既存スキル、組織の成熟度を総合的に判断し、選択すること。単一ツール運用を基本とし、両方の併用は避けるべき
  • 既存インフラがある場合は、新規プロジェクトで検証してから段階的な移行を推奨。急激な切り替えは運用リスク
  • セキュリティ要件が厳格な場合は、Pulumiのシークレット管理の仕組みが有利。ただし、Terraformでも適切な設定でカバー可能
  • 学習投資のコストと長期的な運用効率を天秤にかけ、組織のキャパシティと成長戦略に沿った選択を行うこと
K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →