Terraform実践入門:Infrastructure as Codeで効率的なインフラ管理を実現する

インフラ・ネットワーク

現代のクラウド環境では、インフラストラクチャの管理が複雑化しています。手動でのサーバー構築やネットワーク設定は、時間がかかるだけでなく、人的ミスによる障害リスクも抱えています。そこで注目されているのがInfrastructure as Code(IaC)という概念です。

本記事では、IaCの代表的なツールであるTerraformについて、基本概念から実践的な使い方まで詳しく解説します。2025年の最新情報も含めて、効率的なインフラ管理を実現する方法をご紹介していきます。

じゅんち8

私も以前、手動でAWSのEC2インスタンスを20台以上構築していた時期がありました。設定の統一性を保つのが大変で、毎回微妙に違う設定になってしまうという苦い経験があります。Terraformを導入してからは、こうした問題が一気に解決されました。

Infrastructure as Codeが解決する課題

手動管理の限界

従来のインフラ管理では、以下のような課題がありました:

  • 再現性の低さ:同じ環境を複数作る際、設定のばらつきが発生
  • ドキュメント管理の困難:設定変更の履歴が追跡しにくい
  • スケーラビリティの問題:大規模環境での手動管理は現実的でない
  • 人的ミス:設定ミスによる障害リスク
  • 属人化:特定の担当者に依存する運用体制

Infrastructure as Codeの利点

IaCを導入することで、これらの課題を解決できます:

  • コードによる管理:インフラの設定をコードとして管理
  • バージョン管理:Gitなどを使用した変更履歴の追跡
  • 自動化:CI/CDパイプラインとの連携
  • 再現性:同じコードから同じ環境を確実に構築
  • レビュープロセス:コードレビューによる品質向上

Terraformの基本概念

Terraformとは

Terraformは、HashiCorp社が開発したオープンソースのIaCツールです。HCL(HashiCorp Configuration Language)という専用の言語を使用して、インフラストラクチャをコードで定義します。

主要な構成要素

1. プロバイダー(Provider)

プロバイダーは、Terraformが各種クラウドサービスやAPIと連携するためのプラグインです:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

2. リソース(Resource)

リソースは、実際に作成・管理するインフラストラクチャコンポーネントを定義します:

resource "aws_instance" "web_server" {
  ami           = "ami-0c3fd0f5d33134a76"
  instance_type = "t3.micro"
  
  tags = {
    Name        = "WebServer"
    Environment = "production"
  }
}

3. データソース(Data Source)

既存のリソース情報を取得するために使用します:

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

4. 変数(Variables)

設定値を動的に変更できるようにします:

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "Environment name"
  type        = string
  validation {
    condition     = can(regex("^(dev|staging|prod)$", var.environment))
    error_message = "Environment must be dev, staging, or prod."
  }
}

Terraform State(状態管理)

Terraformの重要な概念の一つが状態管理です。Terraformはterraform.tfstateファイルで現在のインフラの状態を追跡しています。

# ローカル状態管理(開発環境)
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}

# リモート状態管理(本番環境推奨)
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/terraform.tfstate"
    region = "ap-northeast-1"
    
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

2025年最新:TerraformとOpenTofuの選択ガイド

ライセンス変更の影響

2023年8月、HashiCorpは Terraform を含む主要製品のライセンスを Mozilla Public License (MPL) 2.0 から Business Source License (BSL) に変更しました。これにより、2025年7月以降、商用利用には制限が加わる可能性があります。

OpenTofuという選択肢

OpenTofuは、Terraformのオープンソースフォークとして2023年に開始されたプロジェクトです:

  • 完全オープンソース:MPL 2.0ライセンス継続
  • Terraform互換:既存のTerraformコードがそのまま使用可能
  • コミュニティ主導:Linux Foundationの傘下で開発
  • 活発な開発:機能追加とバグ修正が継続的に実施
じゅんち8

私の環境では、現在両方を並行してテストしています。OpenTofuの互換性は非常に高く、移行時のトラブルはほとんどありませんでした。企業環境では、ライセンスリスクを考慮してOpenTofuへの移行を検討する価値があります。

選択の指針

項目 Terraform OpenTofu
ライセンス BSL(商用制限あり) MPL 2.0(完全オープンソース)
機能 最新機能が先行実装 互換性重視、安定性優先
サポート HashiCorp公式サポート コミュニティサポート
移行コスト ほぼゼロ(互換性が高い)

実践例:AWS VPCとEC2の構築

プロジェクト構成

実践的なTerraformプロジェクトを構築してみましょう。以下のファイル構成で進めます:

terraform-practice/
├── main.tf              # メインの設定
├── variables.tf         # 変数定義
├── outputs.tf           # 出力値
├── terraform.tfvars     # 変数値(Gitに含めない)
└── modules/
    └── vpc/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

VPCモジュールの作成

modules/vpc/variables.tf

variable "vpc_cidr" {
  description = "CIDR block for VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  description = "Availability zones"
  type        = list(string)
  default     = ["ap-northeast-1a", "ap-northeast-1c"]
}

variable "public_subnet_cidrs" {
  description = "CIDR blocks for public subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "private_subnet_cidrs" {
  description = "CIDR blocks for private subnets"
  type        = list(string)
  default     = ["10.0.10.0/24", "10.0.20.0/24"]
}

variable "tags" {
  description = "Tags to apply to resources"
  type        = map(string)
  default     = {}
}

modules/vpc/main.tf

# VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
  
  tags = merge(var.tags, {
    Name = "main-vpc"
  })
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  
  tags = merge(var.tags, {
    Name = "main-igw"
  })
}

# Public Subnets
resource "aws_subnet" "public" {
  count = length(var.public_subnet_cidrs)
  
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true
  
  tags = merge(var.tags, {
    Name = "public-subnet-${count.index + 1}"
    Type = "public"
  })
}

# Private Subnets
resource "aws_subnet" "private" {
  count = length(var.private_subnet_cidrs)
  
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]
  
  tags = merge(var.tags, {
    Name = "private-subnet-${count.index + 1}"
    Type = "private"
  })
}

メイン設定とEC2インスタンス

main.tf

terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# 共通タグ
locals {
  common_tags = {
    Environment = var.environment
    Project     = var.project_name
    ManagedBy   = "terraform"
  }
}

# VPCモジュールの使用
module "vpc" {
  source = "./modules/vpc"
  
  vpc_cidr               = "10.0.0.0/16"
  availability_zones     = ["ap-northeast-1a", "ap-northeast-1c"]
  public_subnet_cidrs    = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnet_cidrs   = ["10.0.10.0/24", "10.0.20.0/24"]
  
  tags = local.common_tags
}

# 最新のAmazon Linux 2 AMIを取得
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]
  
  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# Security Group for Web Server
resource "aws_security_group" "web" {
  name_prefix = "${var.project_name}-web-"
  vpc_id      = module.vpc.vpc_id
  
  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # 本番環境では特定のIPに制限
  }
  
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-web-sg"
  })
}

# EC2 Instance
resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type
  key_name      = var.key_pair_name
  
  vpc_security_group_ids = [aws_security_group.web.id]
  subnet_id              = module.vpc.public_subnet_ids[0]
  
  user_data = <<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "<h1>Hello from Terraform!</h1>" > /var/www/html/index.html
              EOF
  
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-web-server"
  })
}

実行手順

1. 初期化

terraform init

2. 計画確認

terraform plan -var="key_pair_name=your-key-pair"

3. 実行

terraform apply -var="key_pair_name=your-key-pair"

4. 確認

terraform show
terraform output

ベストプラクティス

1. モジュール化

再利用可能なコンポーネントはモジュールとして分離します。これにより、コードの再利用性と保守性が向上します。

# 良い例:モジュール化されたVPC
module "production_vpc" {
  source = "./modules/vpc"
  
  environment = "production"
  vpc_cidr    = "10.1.0.0/16"
}

module "staging_vpc" {
  source = "./modules/vpc"
  
  environment = "staging"
  vpc_cidr    = "10.2.0.0/16"
}

2. 環境分離

本番、ステージング、開発環境を分離した構成を採用します:

environments/
├── production/
│   ├── main.tf
│   ├── variables.tf
│   └── terraform.tfvars
├── staging/
│   ├── main.tf
│   ├── variables.tf
│   └── terraform.tfvars
└── modules/
    ├── vpc/
    ├── ec2/
    └── rds/

3. 状態ファイルの管理

本番環境では必ずリモートバックエンドを使用し、状態ファイルのロックを有効にします:

terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "production/infrastructure.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-state-locks"
  }
}

4. 変数の管理

機密情報を含む変数は環境変数またはSecrets Managerから取得します:

# 環境変数
export TF_VAR_db_password="secure_password"

# AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "production/database/password"
}

resource "aws_db_instance" "main" {
  password = jsondecode(data.aws_secretsmanager_secret_version.db_password.secret_string)["password"]
}

5. タグ付けの標準化

すべてのリソースに一貫したタグ付けを行います:

locals {
  common_tags = {
    Environment   = var.environment
    Project       = var.project_name
    Owner         = var.team_name
    ManagedBy     = "terraform"
    CostCenter    = var.cost_center
    CreatedDate   = formatdate("YYYY-MM-DD", timestamp())
  }
}
じゅんち8

タグ付けは軽視されがちですが、コスト管理や運用では非常に重要です。特に本番環境では、コストセンターやオーナー情報を必ず含めるようにしています。これにより、AWSのコスト分析が格段に楽になります。

よくある失敗と解決方法

1. 状態ファイルの競合

問題:複数人が同時にterraform applyを実行し、状態ファイルが破損する。

解決方法

  • DynamoDBを使用した状態ロック機能を有効にする
  • CI/CDパイプラインでの自動実行を導入
  • チーム内での実行タイミングを調整
terraform {
  backend "s3" {
    bucket         = "terraform-state-bucket"
    key            = "infrastructure.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"  # ロックテーブル
  }
}

2. プロバイダーバージョンの不整合

問題:チームメンバー間でプロバイダーバージョンが異なり、動作に差異が発生する。

解決方法

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.31.0"  # 具体的なバージョンを指定
    }
  }
}

# .terraform.lock.hclをGitにコミット

3. リソースの削除防止

問題:誤って重要なリソースを削除してしまう。

解決方法

resource "aws_s3_bucket" "important_data" {
  bucket = "company-important-data"
  
  lifecycle {
    prevent_destroy = true  # 削除防止
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  
  lifecycle {
    ignore_changes = [ami]  # AMI変更を無視
  }
}

4. 大量リソースの同時作成による制限

問題:AWSのAPI制限により、大量のリソース作成が失敗する。

解決方法

resource "aws_instance" "web" {
  count = 100
  
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  
  lifecycle {
    create_before_destroy = true
  }
}

# パラレリズムを制限
# terraform apply -parallelism=5

5. 機密情報のハードコーディング

問題:パスワードやAPIキーをTerraformファイルに直接記述してしまう。

解決方法

# ❌ 悪い例
resource "aws_db_instance" "main" {
  password = "super_secret_password"
}

# ✅ 良い例
resource "aws_db_instance" "main" {
  manage_master_user_password = true  # AWS管理パスワード
}

# または
resource "random_password" "db_password" {
  length = 16
}

resource "aws_secretsmanager_secret" "db_password" {
  name = "database-password"
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id     = aws_secretsmanager_secret.db_password.id
  secret_string = random_password.db_password.result
}

テスト戦略

Terraform Testフレームワーク

Terraform 1.6以降では、組み込みテスト機能が利用できます:

# tests/vpc_test.tftest.hcl
run "create_vpc" {
  command = plan
  
  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "VPC CIDR should be 10.0.0.0/16"
  }
}

run "validate_subnets" {
  command = plan
  
  assert {
    condition     = length(aws_subnet.public) == 2
    error_message = "Should create exactly 2 public subnets"
  }
}

外部ツールによるテスト

Terratest(Go)を使用した例

package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestTerraformVPC(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../",
        Vars: map[string]interface{}{
            "environment": "test",
        },
    }

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.NotEmpty(t, vpcId)
}

CI/CDパイプライン統合

GitHub Actionsでの自動化

# .github/workflows/terraform.yml
name: Terraform CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  terraform:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v3
      with:
        terraform_version: 1.6.0
    
    - name: Terraform Format Check
      run: terraform fmt -check -recursive
    
    - name: Terraform Init
      run: terraform init
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
    - name: Terraform Validate
      run: terraform validate
    
    - name: Terraform Plan
      run: terraform plan -no-color
      if: github.event_name == 'pull_request'
    
    - name: Terraform Apply
      run: terraform apply -auto-approve
      if: github.ref == 'refs/heads/main'

運用とモニタリング

ドリフト検出

インフラの設定変更を検出するため、定期的にドリフト検出を実行します:

# ドリフト検出スクリプト
#!/bin/bash
terraform plan -detailed-exitcode
case $? in
  0)
    echo "No changes detected"
    ;;
  1)
    echo "Error occurred"
    exit 1
    ;;
  2)
    echo "Changes detected - infrastructure has drifted"
    # Slackやメール通知
    ;;
esac

コスト管理

Terraformでのコスト可視化と制御:

# Infracostを使用したコスト見積もり
name: Cost Estimation
on: [pull_request]

jobs:
  infracost:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Infracost
      uses: infracost/actions/setup@v2
      with:
        api-key: ${{ secrets.INFRACOST_API_KEY }}
    
    - name: Generate cost estimate
      run: |
        infracost breakdown --path . \
          --format json \
          --out-file /tmp/infracost.json
        
        infracost comment github \
          --path /tmp/infracost.json \
          --repo $GITHUB_REPOSITORY \
          --github-token ${{ github.token }} \
          --pull-request ${{ github.event.pull_request.number }}

まとめ

Terraformを使用したInfrastructure as Codeは、現代のクラウド環境管理において必須の技術となっています。本記事で紹介した内容を実践することで、以下の利益を得られます:

  • 再現性の確保:同じコードから一貫した環境を構築
  • バージョン管理:インフラ変更の履歴管理とロールバック
  • 自動化:CI/CDパイプラインとの統合による効率向上
  • コスト最適化:リソースの可視化と無駄な支出の削減
  • 品質向上:コードレビューによるインフラ設計の改善

2025年現在、ライセンスの変更によりOpenTofuという選択肢も生まれています。組織の方針に応じて適切なツールを選択し、継続的な改善を行いながら、効率的なインフラ管理を実現してください。

じゅんち8

Terraformの学習は最初は大変ですが、一度慣れると手放せなくなります。小さなプロジェクトから始めて、徐々に複雑な構成に挑戦してみてください。失敗を恐れず、テスト環境で色々試すことが上達の近道です。

インフラ管理の自動化は、開発チーム全体の生産性向上につながる重要な投資です。ぜひ今日から始めてみてください。

関連記事

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