Terraformで始めるAWSインフラ構築:初日から本番環境まで

この記事では、Terraformの基本概念から実践的なAWSリソース構築まで、すぐに仕事で活用できる内容を段階的に解説します。Infrastructure as Code(IaC)の考え方を理解し、手動運用から脱却するための具体的な手順を学べます。

Terraformがなぜ必要か:従来の運用との違い

AWSリソースを管理する際、多くの企業は以下の課題に直面しています:

  • AWS マネジメントコンソールからの手動設定による人為的ミス
  • 環境構築手順のドキュメント作成と更新の負担
  • 開発環境と本番環境の設定差異の発生
  • インフラ変更履歴の追跡困難さ

Terraformは、これらの問題をコード化することで解決します。CloudFormationやAWS CDKなど他の IaC ツールも存在しますが、Terraformはマルチクラウド対応と直感的な設定記法が特徴で、業界での採用例が最も多いです。

Terraformの基本構造をつかむ

インストールと初期設定

まずは開発環境にTerraformをインストールしましょう。以下のコマンドで最新バージョンを取得できます。

# macOSの場合(Homebrewを使用)
brew install terraform

# インストール確認
terraform version

# Terraformの補完機能を有効化(Zshの場合)
terraform -install-autocomplete

Windows環境の場合は公式インストールガイドを参照してください。

Terraformの設定ファイル形式

Terraformは .tf ファイルでインフラストラクチャを定義します。基本的なファイル構成は以下の通りです:

# main.tf
# メインのリソース定義ファイル
provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  
  tags = {
    Name = "terraform-example"
  }
}
# variables.tf
# 変数定義ファイル
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "ap-northeast-1"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}
# outputs.tf
# 出力値定義ファイル
output "instance_public_ip" {
  description = "Public IP of the instance"
  value       = aws_instance.example.public_ip
}

実践的なAWSリソース構築:VPC+EC2の例

プロジェクトの初期化

新しいディレクトリを作成してプロジェクトを開始します:

mkdir terraform-aws-project
cd terraform-aws-project

# Terraformの初期化
# .terraformディレクトリと .terraform.lock.hcl ファイルが生成されます
terraform init

VPCとEC2インスタンスの完全な構成例

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

provider "aws" {
  region = var.aws_region
}

# VPC作成
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "${var.environment}-vpc"
  }
}

# パブリックサブネット
resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.aws_region}a"

  map_public_ip_on_launch = true

  tags = {
    Name = "${var.environment}-public-subnet"
  }
}

# インターネットゲートウェイ
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.environment}-igw"
  }
}

# ルートテーブル
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block      = "0.0.0.0/0"
    gateway_id      = aws_internet_gateway.main.id
  }

  tags = {
    Name = "${var.environment}-public-rt"
  }
}

# ルートテーブルの関連付け
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# セキュリティグループ
resource "aws_security_group" "web" {
  name   = "${var.environment}-web-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.ssh_cidr_block]
  }

  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"]
  }

  tags = {
    Name = "${var.environment}-web-sg"
  }
}

# EC2インスタンス
resource "aws_instance" "web" {
  ami                    = data.aws_ami.amazon_linux_2.id
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  user_data = base64encode(file("${path.module}/user_data.sh"))

  tags = {
    Name = "${var.environment}-web-server"
  }
}

# Amazon Linux 2の最新AMIを取得
data "aws_ami" "amazon_linux_2" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }
}
# variables.tf
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "ap-northeast-1"
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

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

variable "ssh_cidr_block" {
  description = "CIDR block for SSH access"
  type        = string
  default     = "0.0.0.0/0" # 本番環境では必ず制限してください
}
# outputs.tf
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "instance_public_ip" {
  description = "Public IP address of EC2 instance"
  value       = aws_instance.web.public_ip
}

output "instance_id" {
  description = "ID of EC2 instance"
  value       = aws_instance.web.id
}

output "security_group_id" {
  description = "ID of security group"
  value       = aws_security_group.web.id
}
# user_data.sh
#!/bin/bash
set -e

# システム更新
yum update -y

# Webサーバーをインストール
yum install -y httpd

# サービスを開始
systemctl start httpd
systemctl enable httpd

# 簡単なテストページを作成
cat > /var/www/html/index.html << 'EOF'


Terraform Demo

  

Terraformで構築されたサーバーです

Infrastructure as Codeの力を体験してください!

EOF

計画と実行

リソースを作成する前に、必ず terraform plan コマンドで変更内容を確認してください:

# 変更予定を確認
terraform plan -out=tfplan

# 問題がなければリソースを作成
terraform apply tfplan

# または直接実行(対話的に確認)
terraform apply

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

AWS認証情報が見つからないエラー

Error: error configuring Terraform AWS Provider: no valid credential sources for Terraform AWS Provider found というエラーが出た場合、以下を確認してください:

# AWS CLIの認証情報が正しく設定されているか確認
aws sts get-caller-identity

# 環境変数で認証情報を指定する場合
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_DEFAULT_REGION="ap-northeast-1"

terraform init
terraform apply

状態ファイルの競合

複数人で Terraform を使用する場合、ローカルの terraform.tfstate ファイルではなく、リモートバックエンド(S3など)を使用してください:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

リソースが予期せず削除される

変数を変更するときは、-var オプションで値を明示的に指定しないと、default 値で上書きされる場合があります:

# terraform.tfvars ファイルで変数を定義すると安全です
environment = "prod"
instance_type = "t3.small"
ssh_cidr_block = "203.0.113.0/24"

# コマンドから実行
terraform apply -var-file="terraform.tfvars"

運用時に必須のコマンド

# 現在の状態を確認
terraform show

# 特定リソースの詳細を表示
terraform show aws_instance.web

# 状態ファイルを更新(リソースを手動変更した場合)
terraform refresh

# リソースを削除
terraform destroy

# 特定リソースのみ削除
terraform destroy -target=aws_instance.web

# 状態ファイルから特定リソースを削除
terraform state rm aws_instance.web

# 状態ファイルをインポート(既存リソースを Terraform で管理する場合)
terraform import aws_instance.web i-1234567890abcdef0

Terraform を使うべき場面・使うべきでない場面

使うべき場面

  • 複数環境(開発・ステージング・本番)を同じ構成で管理する
  • インフラ変更履歴をバージョン管理したい
  • 複数のクラウドプロバイダを組み合わせる
  • チーム開発でインフラコードをレビューしたい

使うべきでない場面

  • 一度限りの簡単なテスト環境構築(マネジメントコンソールで十分)
  • 頻繁に手動調整が必要な運用フロー
  • Terraform の学習に時間をかけられない急ぎのプロジェクト

テスト環境での動作確認

この記事の内容は以下の環境で動作確認しました:

  • Terraform v1.6.4
  • AWS Provider v5.30
  • macOS 13 / Amazon Linux 2

よくある質問

A: デフォルトではプロジェクトディレクトリの terraform.tfstate に保存されます。本番環境ではS3やTerraform Cloud など、リモートバックエンドを使用して複数人での同時実行を防いでください。

K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →