【2025年最新】Terraformでインフラを完全自動化!初心者でもわかる実践ガイド
Terraformとは?インフラ自動化の基本コンセプト
Terraformは、HashiCorp社が開発したオープンソースのInfrastructure as Code(IaC)ツールです。インフラの構成をコード化することで、クラウド環境やオンプレミス環境など、様々なインフラを一貫した方法で構築・管理できるようになります。この「コードとしてのインフラ」という考え方は、現代のDevOps文化の中核を担っています。
Terraformの特徴
Terraformの主な特徴は以下の通りです:
プロバイダーベースのアーキテクチャ:AWS、Azure、GCP、その他多数のサービスと連携するためのプロバイダーが用意されています。
宣言的な構文:「どのように」ではなく「何を」構築するかを記述するHCL(HashiCorp Configuration Language)という独自言語を使います。
べき等性:同じコードを何度実行しても同じ結果が得られます。これにより安全な変更が可能です。
状態管理:インフラの現在の状態を「状態ファイル」として保持し、あるべき状態との差分を検出します。
実行計画:変更前に何が変わるのかを事前に確認できるため、安全に変更を適用できます。
基本的なワークフロー
Terraformの基本的なワークフローは次の通りです:
# 初期化
terraform init
# 実行計画の確認
terraform plan
# 変更の適用
terraform apply
# 不要になったリソースの削除
terraform destroy
このシンプルなワークフローがTerraformの強みのひとつで、導入のハードルを下げています。また、既存のGit管理フローと組み合わせることで、コードレビューやバージョン管理などの恩恵も受けられます。
なぜTerraformが重要なのか?
インフラを手動で構築・管理すると、以下のような問題が発生しがちです:
- 手順の漏れやミスによる構成ドリフト
- 再現性の欠如
- 変更履歴の追跡が困難
- スケーリングの難しさ
Terraformはこれらの問題を解決し、以下のメリットをもたらします:
- 一貫性と再現性:同じコードから常に同じ環境が構築できます
- バージョン管理:Gitなどのツールと組み合わせて変更履歴を管理できます
- チームコラボレーション:コードレビューを通じて品質向上が図れます
- ドキュメント代わり:コード自体が「どのようなインフラが構築されているか」のドキュメントになります
次のセクションでは、2025年時点での最新バージョンの特徴と改善点について解説します。
Terraformの最新バージョン(2025年5月時点)の新機能と改善点
2025年5月時点での最新のTerraformは、バージョン1.9系が安定版として利用されています。また、AWSプロバイダーは5.7x系が最新です。これらの最新バージョンでは多くの改善や新機能が追加されており、特に注目すべき点を解説します。
Terraform本体の新機能
1. JSON形式のメッセージ出力
terraform init -json
terraform initコマンドに-jsonオプションが追加され、出力をJSON形式で取得できるようになりました。これにより、CI/CDパイプラインなどの自動化プロセスでの出力解析が容易になります。
2. templatestring関数の追加
HCL内で文字列テンプレートを動的に生成できるtemplatestring
関数が追加されました。これは既存のtemplatefile
関数の変数版です。
locals {
greeting_template = "Hello, $${name}! Welcome to $${company}."
greeting = templatestring(local.greeting_template, {
name = var.user_name
company = "HashiCorp"
})
}
3. テスト機能の強化
terraform test
コマンドでsensitive属性を持つ変数の扱いが改善され、モジュールテストの際の制限が緩和されました。これにより、より実践的なテストシナリオが構築可能になっています。
AWSプロバイダーの主な更新
AWSプロバイダーも継続的に更新されており、最新のAWSサービスに対応しています:
1. セキュリティ関連の更新
AWSのTransferサーバーで使用される最新のセキュリティポリシー(TransferSecurityPolicy-2025-03や、FIPS準拠のTransferSecurityPolicy-FIPS-2025-03など)がサポートされました。
2. リソース管理の改善
特に注目すべき改善点として、IAMロール関連の更新があります。managed_policy_arnsの代わりに、aws_iam_role_policy_attachments_exclusiveリソースが推奨されるようになり、より柔軟なIAMロールの管理が可能になりました。
resource "aws_iam_role" "example" {
name = "example-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy_attachments_exclusive" "example" {
role = aws_iam_role.example.name
policy_arns = [
"arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
"arn:aws:iam::aws:policy/AmazonEC2FullAccess"
]
}
3. 非推奨サービスの対応
AWS SimpleDBやWorkLinkなど、AWSが非推奨としたサービスのサポートが終了しました。また、データクリーニングやリソース移行に関するベストプラクティスも更新されています。
バージョン管理のベストプラクティス
最新のTerraformでは、バージョン管理のベストプラクティスとして以下が推奨されています:
明示的なバージョン固定:
terraform { required_version = "= 1.9.2" required_providers { aws = { source = "hashicorp/aws" version = "= 5.72.0" } } }
バージョン管理ツールの利用:
tenv
やtfenv
などのツールを使用したTerraformバージョンの管理定期的なバージョンアップデート:セキュリティ更新や新機能を利用するために四半期ごとの更新サイクルの確立
最新バージョンへの移行を検討する際は、まず非本番環境でのテストを十分に行い、互換性問題がないか確認することが重要です。
初めてのTerraformプロジェクト:環境構築と基本的な使い方
Terraformを始めるための環境構築から、基本的な使い方までステップバイステップで解説します。
1. インストール
Terraformは複数の方法でインストールできます。最新版を維持するためにもパッケージマネージャーを使用するのがおすすめです。
MacOSの場合
# Homebrewを使用する場合
brew install terraform
# バージョン管理ツールを使う場合
brew install tfenv # または新しいtenv
tfenv install 1.9.2
tfenv use 1.9.2
Linuxの場合
# Ubuntuの例
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
Windowsの場合
# Chocolateyを使用する場合
choco install terraform
# Scoopを使用する場合
scoop install terraform
2. プロジェクト構成
Terraformプロジェクトは通常、以下のような構成で作成します:
my-terraform-project/
├── main.tf # メインの設定ファイル
├── variables.tf # 入力変数定義
├── outputs.tf # 出力変数定義
├── providers.tf # プロバイダー設定
└── terraform.tfvars # 変数値の設定(gitignoreに追加推奨)
3. 最初のコード作成
簡単なAWS S3バケットを作成する例を見てみましょう。
providers.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.72.0"
}
}
}
provider "aws" {
region = var.aws_region
# タグをすべてのリソースに自動適用
default_tags {
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
}
}
variables.tf
variable "aws_region" {
description = "AWS region to deploy resources"
type = string
default = "ap-northeast-1"
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
default = "dev"
}
variable "project_name" {
description = "Name of the project"
type = string
default = "my-first-terraform"
}
variable "bucket_name" {
description = "Name of the S3 bucket to create"
type = string
validation {
condition = length(var.bucket_name) >= 3 && length(var.bucket_name) <= 63
error_message = "Bucket name must be between 3 and 63 characters."
}
}
main.tf
resource "aws_s3_bucket" "example" {
bucket = var.bucket_name
lifecycle {
prevent_destroy = false
}
}
resource "aws_s3_bucket_versioning" "example" {
bucket = aws_s3_bucket.example.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
bucket = aws_s3_bucket.example.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
outputs.tf
output "bucket_id" {
description = "The ID of the created S3 bucket"
value = aws_s3_bucket.example.id
}
output "bucket_arn" {
description = "The ARN of the created S3 bucket"
value = aws_s3_bucket.example.arn
}
terraform.tfvars(実際の値を設定)
bucket_name = "my-unique-terraform-bucket-name-2025"
environment = "dev"
project_name = "terraform-tutorial"
4. AWSの認証設定
TerraformでAWSリソースを管理するには、適切な認証情報が必要です。以下の方法があります:
AWS CLIの設定を利用:
aws configure # アクセスキーIDとシークレットキーを入力
環境変数を使用:
export AWS_ACCESS_KEY_ID="your-access-key" export AWS_SECRET_ACCESS_KEY="your-secret-key" export AWS_DEFAULT_REGION="ap-northeast-1"
プロファイルを指定:
provider "aws" { region = "ap-northeast-1" profile = "my-profile" }
5. 基本コマンドの実行
プロジェクトディレクトリで以下のコマンドを順に実行します:
# 初期化
terraform init
# コードの検証
terraform validate
# 実行計画の確認
terraform plan
# 変更の適用
terraform apply
# リソースの状態確認
terraform state list
# 特定リソースの詳細確認
terraform state show aws_s3_bucket.example
# 全リソースの破棄(不要になったら)
terraform destroy
6. Terraform実行時のオプション
よく使われるオプションを紹介します:
- 自動承認:
terraform apply -auto-approve
- 変数ファイルの指定:
terraform apply -var-file="prod.tfvars"
- 特定のリソースだけ操作:
terraform apply -target=aws_s3_bucket.example
- 状態ファイルの出力:
terraform show
- プランファイルの保存と適用:
terraform plan -out=tfplan terraform apply tfplan
7. 初心者がつまずきやすいポイント
- 状態ファイル(terraform.tfstate)の管理:このファイルは重要なので、ローカルではなくS3などのリモートバックエンドで管理することをお勧めします。
- 依存関係の理解:リソース間の依存関係を正しく設定しないと、削除時などに問題が発生することがあります。
- Secret情報の管理:パスワードなどの機密情報はterraform.tfvarsに直接書かず、AWS Secrets ManagerやHashiCorp Vaultなどを利用しましょう。
- リソース名の変更:リソース名を変更すると、Terraformは既存リソースを削除して新しいリソースを作成しようとします。移行には
moved
ブロックを使用しましょう。
Terraformの基本を押さえたら、次のセクションで実際のAWS環境構築例を見ていきましょう。
AWS環境をTerraformで構築する実践例
前のセクションで基本的な使い方を学んだので、ここではより実践的なAWS環境構築の例を紹介します。ウェブアプリケーションのための基本的なインフラを構築するシナリオを考えてみましょう。
マルチティア環境の設計
一般的なウェブアプリケーションは、以下のコンポーネントで構成されます:
- ネットワーク層: VPC、サブネット、ルートテーブル、セキュリティグループなど
- アプリケーション層: ECS/EKSクラスタ、EC2インスタンス、Auto Scalingグループなど
- データベース層: RDS、DynamoDBなど
- キャッシュ層: ElastiCacheなど
- ロードバランサー: ALB、NLBなど
今回はこれらを段階的にTerraformで構築していきます。
1. プロジェクト構成
大規模なTerraformプロジェクトでは、モジュール化が重要です。以下のようなディレクトリ構成を採用します:
aws-infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf # 開発環境の設定
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── main.tf # 本番環境の設定
│ ├── variables.tf
│ └── terraform.tfvars
├── modules/
│ ├── networking/ # ネットワーク層のモジュール
│ ├── compute/ # コンピューティング層のモジュール
│ ├── database/ # データベース層のモジュール
│ └── monitoring/ # モニタリング層のモジュール
└── global/
├── iam/ # IAM関連設定
└── s3/ # 共有S3バケット設定
2. ネットワーク層の構築
まず、基本的なネットワーク構成を作成するモジュールを見てみましょう。
modules/networking/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.environment}-vpc"
}
}
# パブリックサブネット(複数のAZ)
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 = {
Name = "${var.environment}-public-subnet-${count.index + 1}"
}
}
# プライベートサブネット(複数のAZ)
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]
map_public_ip_on_launch = false
tags = {
Name = "${var.environment}-private-subnet-${count.index + 1}"
}
}
# インターネットゲートウェイ
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
# NATゲートウェイ用のElastic IP
resource "aws_eip" "nat" {
count = length(var.public_subnet_cidrs)
domain = "vpc"
tags = {
Name = "${var.environment}-nat-eip-${count.index + 1}"
}
}
# NATゲートウェイ(各パブリックサブネットに配置)
resource "aws_nat_gateway" "nat" {
count = length(var.public_subnet_cidrs)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.environment}-nat-${count.index + 1}"
}
depends_on = [aws_internet_gateway.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.igw.id
}
tags = {
Name = "${var.environment}-public-rt"
}
}
# プライベートサブネット用ルートテーブル(各AZごと)
resource "aws_route_table" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat[count.index].id
}
tags = {
Name = "${var.environment}-private-rt-${count.index + 1}"
}
}
# ルートテーブルとサブネットの関連付け(パブリック)
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# ルートテーブルとサブネットの関連付け(プライベート)
resource "aws_route_table_association" "private" {
count = length(var.private_subnet_cidrs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
modules/networking/variables.tf
variable "environment" {
description = "Environment name"
type = string
}
variable "vpc_cidr" {
description = "CIDR block for the VPC"
type = string
}
variable "public_subnet_cidrs" {
description = "List of public subnet CIDR blocks"
type = list(string)
}
variable "private_subnet_cidrs" {
description = "List of private subnet CIDR blocks"
type = list(string)
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
}
modules/networking/outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "List of private subnet IDs"
value = aws_subnet.private[*].id
}
3. コンピューティング層の構築
次に、ECSクラスタとサービスを作成するモジュールを見てみましょう。
modules/compute/main.tf
# ECSクラスタの作成
resource "aws_ecs_cluster" "main" {
name = "${var.environment}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
tags = {
Environment = var.environment
}
}
# ECSタスク実行ロール
resource "aws_iam_role" "ecs_task_execution_role" {
name = "${var.environment}-ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}]
})
}
# タスク実行ロールにポリシーをアタッチ
resource "aws_iam_role_policy_attachments_exclusive" "ecs_task_execution_role" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arns = [
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
]
}
# ECSタスク定義
resource "aws_ecs_task_definition" "app" {
family = "${var.environment}-app"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.task_cpu
memory = var.task_memory
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([{
name = "app"
image = var.container_image
essential = true
portMappings = [{
containerPort = var.container_port
hostPort = var.container_port
protocol = "tcp"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/${var.environment}-app"
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "ecs"
}
}
}])
tags = {
Environment = var.environment
}
}
# CloudWatch Logsグループ
resource "aws_cloudwatch_log_group" "app" {
name = "/ecs/${var.environment}-app"
retention_in_days = 30
}
# セキュリティグループ
resource "aws_security_group" "ecs_tasks" {
name = "${var.environment}-ecs-tasks-sg"
description = "Allow inbound traffic to ECS tasks"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = var.container_port
to_port = var.container_port
security_groups = [var.alb_security_group_id]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Environment = var.environment
}
}
# ECSサービス
resource "aws_ecs_service" "app" {
name = "${var.environment}-app-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
platform_version = "LATEST"
health_check_grace_period_seconds = 60
deployment_minimum_healthy_percent = 100
deployment_maximum_percent = 200
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.ecs_tasks.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = var.target_group_arn
container_name = "app"
container_port = var.container_port
}
depends_on = [
aws_iam_role_policy_attachments_exclusive.ecs_task_execution_role
]
lifecycle {
ignore_changes = [task_definition]
}
tags = {
Environment = var.environment
}
}
4. 環境固有の設定と利用例
各環境固有の設定を行い、モジュールを利用します:
environments/dev/main.tf
provider "aws" {
region = var.aws_region
}
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.72.0"
}
}
# リモートバックエンド設定
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "dev/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# ネットワークモジュールの利用
module "networking" {
source = "../../modules/networking"
environment = var.environment
vpc_cidr = var.vpc_cidr
public_subnet_cidrs = var.public_subnet_cidrs
private_subnet_cidrs = var.private_subnet_cidrs
availability_zones = var.availability_zones
}
# ALBセキュリティグループ
resource "aws_security_group" "alb" {
name = "${var.environment}-alb-sg"
description = "Allow inbound traffic to ALB"
vpc_id = module.networking.vpc_id
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
protocol = "tcp"
from_port = 443
to_port = 443
cidr_blocks = ["0.0.0.0/0"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Environment = var.environment
}
}
# ALB
resource "aws_lb" "main" {
name = "${var.environment}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = module.networking.public_subnet_ids
enable_deletion_protection = false
tags = {
Environment = var.environment
}
}
# ALBターゲットグループ
resource "aws_lb_target_group" "app" {
name = "${var.environment}-app-tg"
port = 80
protocol = "HTTP"
vpc_id = module.networking.vpc_id
target_type = "ip"
health_check {
healthy_threshold = 3
unhealthy_threshold = 3
timeout = 5
interval = 30
path = "/health"
port = "traffic-port"
}
tags = {
Environment = var.environment
}
}
# ALBリスナー
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# コンピューティングモジュールの利用
module "compute" {
source = "../../modules/compute"
environment = var.environment
aws_region = var.aws_region
vpc_id = module.networking.vpc_id
private_subnet_ids = module.networking.private_subnet_ids
alb_security_group_id = aws_security_group.alb.id
target_group_arn = aws_lb_target_group.app.arn
container_image = var.container_image
container_port = var.container_port
task_cpu = var.task_cpu
task_memory = var.task_memory
desired_count = var.desired_count
}
# 出力
output "alb_dns_name" {
description = "The DNS name of the load balancer"
value = aws_lb.main.dns_name
}
environments/dev/terraform.tfvars
environment = "dev"
aws_region = "ap-northeast-1"
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.3.0/24", "10.0.4.0/24"]
availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
container_image = "your-ecr-repo/app:latest"
container_port = 80
task_cpu = "256"
task_memory = "512"
desired_count = 2
5. 実行プラン
環境構築の順序としては、次のステップを踏むと良いでしょう:
- リモートバックエンド用のS3バケットとDynamoDBを作成
- ネットワークリソース(VPC、サブネットなど)の作成
- コンピューティングリソース(ECSクラスタ、タスク定義など)の作成
- データベースリソース(必要に応じて)の作成
- モニタリングとアラート設定の追加
各ステップでterraform plan
とterraform apply
を行い、段階的に環境を構築します。
6. 環境の拡張
基本的な環境が構築できたら、以下のような拡張ができます:
- RDSデータベースの追加:プライベートサブネットにRDSインスタンスを配置
- ElastiCacheの追加:セッション管理やキャッシュ用途に
- WAF/Shield:セキュリティ強化のために
- CloudWatchアラームとLambda関数:自動アラートやスケーリング対応
- CI/CDパイプライン:AWS CodePipelineやGitHub Actionsとの連携
次のセクションでは、Terraformのベストプラクティスについて詳しく解説します。
Terraformのベストプラクティスと効率的なコード管理手法
Terraformでより大規模なインフラを管理する場合、適切なコード構成と管理手法が重要になります。ここでは、実務で役立つベストプラクティスを紹介します。
コード構成のベストプラクティス
1. モジュール化
再利用可能なコンポーネントとしてモジュールを作成することで、コードの重複を防ぎ、メンテナンス性を向上させることができます。
modules/
├── networking/ # ネットワークリソース用モジュール
├── database/ # データベースリソース用モジュール
├── compute/ # コンピューティングリソース用モジュール
└── security/ # セキュリティ設定用モジュール
理想的なモジュールは:
- 単一の責任を持つ
- 入力変数と出力変数が明確に定義されている
- 内部の実装を隠蔽している
- 再利用可能である
2. ワークスペースとディレクトリ構造
環境ごとに異なる設定を管理するには、以下の2つの方法があります:
(a) Terraformワークスペースを使用する
# 開発環境用ワークスペースを作成
terraform workspace new dev
# 本番環境用ワークスペースを作成
terraform workspace new prod
# ワークスペースの切り替え
terraform workspace select dev
# main.tf内でワークスペースに応じた設定を使い分ける
locals {
env_config = {
dev = {
instance_type = "t3.small"
instance_count = 1
}
prod = {
instance_type = "m5.large"
instance_count = 3
}
}
config = local.env_config[terraform.workspace]
}
resource "aws_instance" "app" {
count = local.config.instance_count
instance_type = local.config.instance_type
# 他の設定...
}
(b) 環境ごとに別々のディレクトリを用意する
environments/
├── dev/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
├── staging/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
├── variables.tf
└── terraform.tfvars
それぞれの環境ディレクトリで共通のモジュールを参照します:
# environments/dev/main.tf
module "vpc" {
source = "../../modules/networking"
vpc_cidr = "10.0.0.0/16"
environment = "dev"
}
3. 変数のレイヤー化
変数を階層化することで、設定の柔軟性と再利用性が向上します:
# terraform.tfvars - 環境共通の変数
region = "ap-northeast-1"
project = "example-app"
# dev.tfvars - 開発環境固有の変数
environment = "dev"
instance_type = "t3.small"
# prod.tfvars - 本番環境固有の変数
environment = "prod"
instance_type = "m5.large"
実行時に複数の変数ファイルを指定できます:
terraform apply -var-file="terraform.tfvars" -var-file="dev.tfvars"
状態管理のベストプラクティス
1. リモート状態管理
チーム開発では、Terraformの状態をリモートで管理することが重要です:
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "dev/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
DynamoDBを使用してステートロックを実装することで、複数の開発者が同時に変更を適用することを防止できます。
2. 状態の分離
大規模なインフラでは、状態を分割して管理すると、リスクとパフォーマンスが向上します:
terraform/
├── network/ # ネットワーク関連の状態
├── database/ # データベース関連の状態
├── application/ # アプリケーション関連の状態
└── monitoring/ # モニタリング関連の状態
各ディレクトリで独自の状態ファイルを管理し、依存関係は出力変数とリモート状態データソースを使用して連携させます:
# network/outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
}
# database/main.tf
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "terraform-state-bucket"
key = "network/terraform.tfstate"
region = "ap-northeast-1"
}
}
resource "aws_db_subnet_group" "main" {
subnet_ids = data.terraform_remote_state.network.outputs.private_subnet_ids
}
コード品質管理
1. コーディング規約
チーム開発では、一貫したコーディング規約を定めることが重要です:
- インデントは2スペース
- リソース名は
スネークケース_
で統一 - タグ付けの標準化
- コメント規約(何を、なぜ、どうやって)
2. 静的解析ツール
コードの品質を自動的にチェックするツールを活用しましょう:
# tflint - Terraformコード解析ツール
tflint
# tfsec - セキュリティチェックツール
tfsec .
# terraform fmt - コード整形
terraform fmt -recursive
3. 自動テスト
Terraformのコードも自動テストが可能です:
# terratest - Terraformのテストフレームワーク
go test -v ./test/
# terraform-compliance - BDDスタイルのテスト
terraform-compliance -f features/ -p plan.out
2025年現在、Terraformネイティブのテストフレームワークも利用可能です:
# tests/main.tftest.hcl
run "create_s3_bucket" {
command = apply
assert {
condition = aws_s3_bucket.example.bucket == "my-test-bucket"
error_message = "Bucket name does not match"
}
}
# ネイティブテストの実行
terraform test
CI/CDパイプラインとの統合
1. GitHub Actionsの例
# .github/workflows/terraform.yml
name: "Terraform"
on:
push:
branches: [ main ]
pull_request:
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.9.2
- name: Terraform Format
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
2. Atlantisの導入
大規模チームでは、Atlantisのようなツールを使用してPRベースのワークフローを確立できます:
# atlantis.yaml
version: 3
projects:
- name: dev
dir: environments/dev
workspace: default
autoplan:
when_modified: ["*.tf", "../modules/**/*.tf"]
enabled: true
apply_requirements: [approved, mergeable]
ドリフト検出と管理
インフラの実際の状態とTerraformの状態が乖離する「ドリフト」を検出し管理します:
# ドリフト検出
terraform plan
# 状態の更新(注意して使用すること)
terraform refresh
# インポート(Terraformの管理外だったリソースを取り込む)
terraform import aws_s3_bucket.example my-existing-bucket
2025年現在では、インポートブロックを使用することもできます:
import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}
resource "aws_instance" "web" {
ami = "ami-a1b2c3d4"
instance_type = "t3.micro"
# その他の設定...
lifecycle {
# インポート後に設定を揃える
ignore_changes = [ami]
}
}
秘密情報の管理
セキュリティを確保しながら秘密情報を管理するためのベストプラクティス:
秘密情報を直接コードに含めない
環境変数や外部システムの活用:
variable "database_password" { type = string sensitive = true description = "Database master password" }
HashiCorp Vaultとの統合:
provider "vault" {} data "vault_generic_secret" "db_creds" { path = "secret/database" } resource "aws_db_instance" "default" { username = data.vault_generic_secret.db_creds.data["username"] password = data.vault_generic_secret.db_creds.data["password"] }
以上のベストプラクティスを適用することで、Terraformコードの保守性、安全性、効率性が大幅に向上します。次のセクションでは、より高度なTerraformの応用テクニックについて解説します。
実務で役立つTerraformの応用テクニック
これまでの基礎知識と実践例を踏まえて、より高度なTerraformの活用テクニックを紹介します。実務で直面する課題を解決するためのヒントになるでしょう。
条件付きリソース作成
環境によって作成するリソースを切り替えたい場合は、countパラメータを使用します:
# 開発環境では作成せず、本番環境でのみ作成するリソース
resource "aws_backup_plan" "example" {
count = var.environment == "prod" ? 1 : 0
name = "backup-plan"
rule {
rule_name = "daily-backup"
target_vault_name = aws_backup_vault.example[0].name
schedule = "cron(0 12 * * ? *)"
}
}
# count使用時は配列参照になることに注意
resource "aws_backup_vault" "example" {
count = var.environment == "prod" ? 1 : 0
name = "example-backup-vault"
}
動的ブロック生成
リソース内の繰り返しブロックを動的に生成するには、dynamic
ブロックを使用します:
# 複数のインバウンドルールを持つセキュリティグループ
resource "aws_security_group" "example" {
name = "example-sg"
description = "Example security group"
vpc_id = aws_vpc.main.id
# インバウンドルールを動的に生成
dynamic "ingress" {
for_each = var.security_group_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# 使用例
variable "security_group_rules" {
description = "Security group rules"
type = list(object({
port = number
cidr_blocks = list(string)
description = string
}))
default = [
{
port = 80
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP"
},
{
port = 443
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS"
},
{
port = 22
cidr_blocks = ["10.0.0.0/8"]
description = "SSH (internal only)"
}
]
}
for_eachを使用した複数リソースの管理
関連するリソースを一貫した方法で管理するには、for_each
を使用します:
# ユーザー単位の設定をマップで定義
variable "users" {
description = "Map of users with their policies"
type = map(object({
permissions = list(string)
groups = list(string)
}))
default = {
"admin" = {
permissions = ["AdministratorAccess"]
groups = ["admins"]
},
"developer" = {
permissions = ["AmazonS3ReadOnlyAccess", "AmazonEC2ReadOnlyAccess"]
groups = ["developers"]
}
}
}
# ユーザーの作成
resource "aws_iam_user" "team" {
for_each = var.users
name = each.key
}
# ポリシーのアタッチ
resource "aws_iam_user_policy_attachment" "user_permissions" {
for_each = { for pair in flatten([
for user, config in var.users : [
for policy in config.permissions : {
user = user
policy = policy
}
]
]) : "${pair.user}-${pair.policy}" => pair }
user = aws_iam_user.team[each.value.user].name
policy_arn = "arn:aws:iam::aws:policy/${each.value.policy}"
}
ローカルモジュールと外部モジュール
モジュールには、ローカルモジュールと外部モジュールの2種類があります:
# ローカルモジュール(ファイルシステム上)
module "vpc" {
source = "../modules/vpc"
# モジュールの引数
cidr_block = "10.0.0.0/16"
}
# Terraform Registryからのモジュール
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.19.0"
# モジュールの引数
name = "my-vpc"
cidr = "10.0.0.0/16"
}
# GitHubからのモジュール
module "s3_bucket" {
source = "github.com/terraform-aws-modules/terraform-aws-s3-bucket?ref=v3.6.0"
# モジュールの引数
bucket = "my-s3-bucket"
}
データ変換と処理
Terraformではデータ変換のための関数が多数用意されています:
# リストをマップに変換
locals {
instance_tags_list = [
{ key = "Name", value = "web-server" },
{ key = "Environment", value = "prod" },
{ key = "Service", value = "api" }
]
# { Name = "web-server", Environment = "prod", ... } の形式に変換
instance_tags = { for item in local.instance_tags_list : item.key => item.value }
}
# マージと変換の例
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
}
# 特定リソース用にタグを追加
db_tags = merge(local.common_tags, {
Service = "database"
Backup = "daily"
Encryption = "enabled"
})
# CSVデータを解析
csv_data = csvdecode(file("${path.module}/data/instances.csv"))
# 命名規則の統一
resource_name_prefix = lower("${var.project_name}-${var.environment}")
}
出力の活用
モジュール間の連携や外部システムとの統合には出力を活用します:
# モジュールから必要な情報を出力
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "private_subnet_ids" {
description = "List of private subnet IDs"
value = aws_subnet.private[*].id
}
# 複雑なオブジェクト構造の出力
output "lambda_functions" {
description = "Map of Lambda functions with their configurations"
value = {
for name, lambda in aws_lambda_function.functions : name => {
arn = lambda.arn
invoke_arn = lambda.invoke_arn
version = lambda.version
role_arn = lambda.role
}
}
}
# 機密情報を含む出力の保護
output "database_connection_string" {
description = "Database connection string"
value = "postgresql://${aws_db_instance.main.username}:${aws_db_instance.main.password}@${aws_db_instance.main.endpoint}/${aws_db_instance.main.db_name}"
sensitive = true
}
プロバイダー間の連携
複数のプロバイダーを組み合わせて使用することも可能です:
# AWS プロバイダー設定
provider "aws" {
region = "ap-northeast-1"
}
# Google Cloud プロバイダー設定
provider "google" {
project = "my-gcp-project"
region = "asia-northeast1"
}
# Kubernetes クラスタプロバイダー
provider "kubernetes" {
host = aws_eks_cluster.main.endpoint
cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.main.token
}
# 複数リージョンのAWSリソース管理
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
provider "aws" {
alias = "ap_northeast_1"
region = "ap-northeast-1"
}
# CloudFront証明書(us-east-1のみ)
resource "aws_acm_certificate" "cloudfront" {
provider = aws.us_east_1
domain_name = "example.com"
validation_method = "DNS"
}
# 東京リージョンのALB証明書
resource "aws_acm_certificate" "alb" {
provider = aws.ap_northeast_1
domain_name = "app.example.com"
validation_method = "DNS"
}
カスタムプロバイダー
組織内で独自のプロバイダーを開発することもできます:
# カスタムプロバイダーの使用例
terraform {
required_providers {
internal = {
source = "example.com/internal/internal"
version = "~> 1.0"
}
}
}
provider "internal" {
api_endpoint = "https://api.internal.example.com"
api_token = var.internal_api_token
}
resource "internal_resource" "example" {
name = "example-resource"
type = "service"
}
2025年の最新機能:deferred actions
2025年のTerraformでは、deferred actions
機能が追加され、リソース作成後のアクションをより柔軟に制御できるようになりました:
# 実験的機能のため、-allow-deferralフラグで実行する必要があります
# terraform plan -allow-deferral
resource "aws_instance" "example" {
ami = "ami-12345678"
instance_type = "t3.micro"
deferred {
# インスタンス作成後に実行されるアクション
action "reboot" {
command = "reboot"
depends_on = [
aws_instance.example
]
}
}
}
Terraformを使用したマルチアカウント管理
大規模な組織では、複数のAWSアカウントを管理することがあります:
# アカウントごとのプロバイダー設定
provider "aws" {
alias = "security"
profile = "security-account"
region = "ap-northeast-1"
}
provider "aws" {
alias = "development"
profile = "development-account"
region = "ap-northeast-1"
}
provider "aws" {
alias = "production"
profile = "production-account"
region = "ap-northeast-1"
}
# セキュリティアカウントでの共通IAMロールの作成
resource "aws_iam_role" "cross_account_role" {
provider = aws.security
name = "CrossAccountRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
AWS = [
"arn:aws:iam::${var.development_account_id}:root",
"arn:aws:iam::${var.production_account_id}:root"
]
}
}
]
})
}
まとめ
Terraformは強力なツールであり、活用方法は無限に広がります。本記事で紹介した基本的な使い方から応用テクニックまでマスターすることで、効率的なインフラ自動化を実現しましょう。
Terraformによるインフラ自動化の主なメリットは以下の通りです:
- 一貫性と再現性:コードとして管理することで、常に同じ結果が得られます
- バージョン管理:インフラの変更履歴を追跡できます
- モジュール化と再利用:共通のパターンを再利用できます
- 自動化と効率化:手作業を減らし、効率を高めます
- ドキュメント化:コード自体がドキュメントとなります
最後に、Terraformは継続的に進化しているため、公式ドキュメントやコミュニティの最新情報をフォローしながら、自分の環境に合った方法で活用することが重要です。