今回は、AWSインフラをコードで管理できる「Terraform」について、基礎から実践的な使い方まで詳しく解説していきます。AWSのマネジメントコンソールでの手作業に慣れている方にとって、Infrastructure as Code(IaC)の世界は最初は戸惑うかもしれません。しかし、一度Terraformの基本を理解すれば、インフラ構築の効率が劇的に向上し、チーム開発での管理も格段に楽になります。
本記事では、Terraformの基本概念から、EC2やRDS、ELBといった代表的なAWSリソースの構築方法、そして実務でよく遭遇するエラーとその対処法まで、実践的な内容を網羅していきます。
Terraformとは?
Infrastructure as Code(IaC)の概念
Terraformは、HashiCorp社が開発したオープンソースのインフラ構築ツールです。Infrastructure as Code(IaC)という考え方に基づいており、インフラの構成をコードとして記述・管理できます。
従来のマネジメントコンソールでの手作業と比較すると、以下のようなメリットがあります:
- 再現性の確保: 同じコードを実行すれば、常に同じ環境が構築される
- バージョン管理: Gitなどでインフラの変更履歴を管理できる
- レビュープロセス: コードレビューを通じて、インフラ変更の品質を担保できる
- ドキュメント化: コード自体が最新のインフラ構成のドキュメントになる
- 自動化: CI/CDパイプラインに組み込んで、デプロイを自動化できる
Terraformの特徴
Terraformには以下のような特徴があります:
- マルチクラウド対応: AWS、Azure、GCPなど、複数のクラウドプロバイダーに対応
- 宣言的な記述: 「どうやって作るか」ではなく「何を作るか」を記述
- 依存関係の自動解決: リソース間の依存関係を自動的に判断して、適切な順序で作成
- 変更の事前確認: 実際に適用する前に、どのような変更が行われるかをプレビュー可能
- State管理: 現在のインフラ状態を管理し、差分を検出
AWSでTerraformを使うメリット
AWSには公式のIaCツールとしてCloudFormationがありますが、Terraformを選択するメリットも多くあります:
- シンプルな構文: HCL(HashiCorp Configuration Language)は読みやすく、学習コストが低い
- 豊富なコミュニティ: 世界中で広く使われており、情報が豊富
- マルチクラウド戦略: 将来的に他のクラウドも使う可能性がある場合、統一されたツールで管理できる
- モジュール化: 再利用可能なモジュールを作成・共有しやすい
Terraformの環境構築
インストール方法
Terraformのインストールは非常にシンプルです。各OSごとの手順を紹介します。
Linux(Ubuntu/Debian)の場合:
# HashiCorpの公式リポジトリを追加
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
# バージョン確認
terraform version
実行結果例:
Terraform v1.7.0
on linux_amd64
macOSの場合:
# Homebrewを使用
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# バージョン確認
terraform version
Windowsの場合:
Chocolateyを使用する方法が便利です:
choco install terraform
# バージョン確認
terraform version
AWS認証情報の設定
TerraformからAWSリソースを操作するには、適切な認証情報が必要です。主な方法は以下の3つです:
1. 環境変数を使用する方法(推奨):
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_DEFAULT_REGION="ap-northeast-1"
2. AWS CLIの認証情報を使用する方法:
# AWS CLIで認証情報を設定
aws configure
# Terraformは自動的に ~/.aws/credentials を参照
3. IAMロールを使用する方法(EC2上で実行する場合):
EC2インスタンスにIAMロールをアタッチすることで、認証情報を明示的に指定する必要がなくなります。本番環境ではこの方法が最もセキュアです。
注意点:
- 本番環境では、最小権限の原則に従い、必要な権限のみを付与したIAMユーザーまたはロールを使用してください
- アクセスキーをコードに直接記述することは絶対に避けましょう
.gitignoreに認証情報ファイルを必ず追加してください
参考: AWS Provider – Authentication and Configuration
Terraformの基本概念
Provider
Providerは、Terraformが特定のクラウドプロバイダーやサービスと対話するためのプラグインです。AWSを使用する場合は、AWS Providerを指定します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.7.0"
}
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
Environment = "production"
ManagedBy = "Terraform"
}
}
}
ポイント:
required_versionでTerraformのバージョンを指定すると、チーム全体で同じバージョンを使用できますdefault_tagsを設定すると、すべてのリソースに自動的にタグが付与されます- バージョン指定には
~>(ペシミスティック制約)を使うと、マイナーバージョンアップデートは許可しつつ、メジャーバージョンアップは防げます
Resource
Resourceは、作成・管理する実際のインフラリソースを定義します。
resource "aws_instance" "web" {
ami = "ami-0d52744d6551d851e"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}
基本的な構文は以下の通りです:
resource "リソースタイプ" "ローカル名" {
設定パラメータ = 値
}
- リソースタイプ:
aws_instance、aws_s3_bucketなど - ローカル名: Terraform内で参照するための名前(任意)
- 設定パラメータ: リソース固有の設定項目
Data Source
Data Sourceは、既存のリソース情報を参照するために使用します。Terraform外で作成されたリソースの情報を取得できます。
# 最新の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"]
}
}
# EC2インスタンスで使用
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = "t3.micro"
}
実務での活用例:
- 既存のVPCやサブネットを参照してリソースを作成
- 最新のAMI IDを動的に取得(ハードコードを避ける)
- 既存のセキュリティグループを参照
Variable(変数)
変数を使うことで、コードの再利用性と柔軟性が向上します。
variable "instance_type" {
description = "EC2インスタンスのタイプ"
type = string
default = "t3.micro"
}
variable "environment" {
description = "環境名"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "環境名はdev、staging、productionのいずれかである必要があります"
}
}
# 使用例
resource "aws_instance" "web" {
ami = "ami-0d52744d6551d851e"
instance_type = var.instance_type
tags = {
Environment = var.environment
}
}
変数の値は以下の方法で指定できます:
terraform.tfvarsファイル- コマンドライン引数:
terraform apply -var="instance_type=t3.small" - 環境変数:
TF_VAR_instance_type=t3.small
Output(出力値)
Outputは、Terraform実行後に特定の値を出力するために使用します。
output "instance_public_ip" {
description = "EC2インスタンスのパブリックIP"
value = aws_instance.web.public_ip
}
output "instance_id" {
description = "EC2インスタンスID"
value = aws_instance.web.id
}
実行結果例:
Outputs:
instance_id = "i-0123456789abcdef0"
instance_public_ip = "54.123.45.67"
Outputは以下のような用途で活用できます:
- 他のTerraformプロジェクトへの情報受け渡し
- CI/CDパイプラインでの値の取得
- チーム内での情報共有
State(状態管理)
Stateファイル(terraform.tfstate)は、Terraformが管理するインフラの現在の状態を記録します。
重要な注意点:
- Stateファイルには機密情報(パスワード、秘密鍵など)が含まれる可能性があります
- ローカル環境で管理する場合、絶対にGitにコミットしないでください
- チーム開発では、S3などのリモートバックエンドを使用してください
リモートバックエンドの設定例:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
DynamoDBテーブルを指定することで、複数人が同時にTerraformを実行するのを防ぐロック機能が有効になります。
参考: Terraform State
実践:代表的なAWSリソースの構築
ここからは、実際によく使うAWSリソースをTerraformで構築する方法を見ていきます。
VPCとネットワークの構築
まずは基本となるVPCとサブネットを構築します。
# VPCの作成
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
# パブリックサブネット
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-1a"
}
}
# プライベートサブネット
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "private-subnet-1a"
}
}
# インターネットゲートウェイ
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-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 = "public-rt"
}
}
# ルートテーブルとサブネットの関連付け
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
ポイント:
aws_vpc.main.idのように、他のリソースを参照することで依存関係が自動的に解決されますmap_public_ip_on_launch = trueを設定すると、このサブネットで起動したEC2に自動的にパブリックIPが割り当てられます- 本番環境では、マルチAZ構成を考慮して複数のサブネットを作成してください
EC2インスタンスの構築
# セキュリティグループの作成
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web server"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from anywhere"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH from specific IP"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["your-ip-address/32"] # 自分のIPアドレスに変更
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-sg"
}
}
# EC2インスタンスの作成
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = "your-key-pair" # 事前に作成したキーペア名
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
root_block_device {
volume_type = "gp3"
volume_size = 20
delete_on_termination = true
encrypted = true
}
tags = {
Name = "web-server"
}
}
# Elastic IPの割り当て(オプション)
resource "aws_eip" "web" {
instance = aws_instance.web.id
domain = "vpc"
tags = {
Name = "web-eip"
}
}
注意点:
- SSHのポート22は、セキュリティ上、自分のIPアドレスのみに制限してください
user_dataでインスタンス起動時の初期化スクリプトを指定できます- EBSボリュームは必ず暗号化することを推奨します(
encrypted = true) - キーペアは事前にAWSコンソールまたはCLIで作成しておく必要があります
RDSインスタンスの構築
# DBサブネットグループの作成
resource "aws_db_subnet_group" "main" {
name = "main-db-subnet-group"
subnet_ids = [aws_subnet.private.id, aws_subnet.private_2.id] # マルチAZ用に2つ以上必要
tags = {
Name = "main-db-subnet-group"
}
}
# RDS用セキュリティグループ
resource "aws_security_group" "rds" {
name = "rds-sg"
description = "Security group for RDS"
vpc_id = aws_vpc.main.id
ingress {
description = "MySQL from web server"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
tags = {
Name = "rds-sg"
}
}
# RDSインスタンスの作成
resource "aws_db_instance" "main" {
identifier = "main-db"
engine = "mysql"
engine_version = "8.0.35"
instance_class = "db.t3.micro"
allocated_storage = 20
storage_type = "gp3"
storage_encrypted = true
db_name = "myapp"
username = "admin"
password = var.db_password # 変数として定義し、機密情報として管理
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
multi_az = true
publicly_accessible = false
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
skip_final_snapshot = false
final_snapshot_identifier = "main-db-final-snapshot-${formatdate("YYYYMMDD-hhmm", timestamp())}"
enabled_cloudwatch_logs_exports = ["error", "general", "slowquery"]
tags = {
Name = "main-db"
}
}
# RDSエンドポイントの出力
output "rds_endpoint" {
description = "RDS instance endpoint"
value = aws_db_instance.main.endpoint
sensitive = true
}
重要な注意点:
- パスワードは絶対にコードに直接書かないでください。変数や AWS Secrets Managerを使用します
publicly_accessible = falseで、インターネットからの直接アクセスを防ぎますskip_final_snapshot = falseで、削除時に最終スナップショットが作成されますmulti_az = trueで高可用性構成になります(本番環境推奨)- バックアップとメンテナンスウィンドウは、業務への影響が少ない時間帯に設定してください
Elastic Network Interface (ENI) の構築
ENIは、EC2インスタンスに複数のネットワークインターフェースを追加したい場合に使用します。
# ENIの作成
resource "aws_network_interface" "management" {
subnet_id = aws_subnet.private.id
private_ips = ["10.0.2.10"]
security_groups = [aws_security_group.management.id]
tags = {
Name = "management-eni"
}
}
# EC2インスタンスにENIをアタッチ
resource "aws_network_interface_attachment" "management" {
instance_id = aws_instance.web.id
network_interface_id = aws_network_interface.management.id
device_index = 1
}
使用例:
- 管理用ネットワークと業務用ネットワークを分離
- 複数のプライベートIPアドレスを持つインスタンス
- ライセンスサーバーなど、固定IPが必要な場合
Application Load Balancer (ALB) の構築
# ALB用セキュリティグループ
resource "aws_security_group" "alb" {
name = "alb-sg"
description = "Security group for ALB"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from anywhere"
from_port = 443
to_port = 443
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 = "alb-sg"
}
}
# Application Load Balancer
resource "aws_lb" "main" {
name = "main-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = [aws_subnet.public.id, aws_subnet.public_2.id] # 2つ以上のAZ必要
enable_deletion_protection = true # 本番環境では有効化推奨
tags = {
Name = "main-alb"
}
}
# ターゲットグループ
resource "aws_lb_target_group" "web" {
name = "web-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 5
interval = 30
path = "/"
matcher = "200"
}
tags = {
Name = "web-tg"
}
}
# ターゲットの登録
resource "aws_lb_target_group_attachment" "web" {
target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web.id
port = 80
}
# リスナー
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.web.arn
}
}
# ALBのDNS名を出力
output "alb_dns_name" {
description = "DNS name of the load balancer"
value = aws_lb.main.dns_name
}
ポイント:
- ALBは最低2つのAZにサブネットが必要です
- ヘルスチェックの設定は、アプリケーションの特性に合わせて調整してください
- 本番環境では、HTTPS リスナーとSSL証明書の設定も行いましょう
S3バケットの構築
# S3バケットの作成
resource "aws_s3_bucket" "logs" {
bucket = "my-app-logs-${random_id.bucket_suffix.hex}"
tags = {
Name = "Application Logs"
Environment = "production"
}
}
# ランダムなサフィックス生成(バケット名の重複を避ける)
resource "random_id" "bucket_suffix" {
byte_length = 4
}
# バージョニングの有効化
resource "aws_s3_bucket_versioning" "logs" {
bucket = aws_s3_bucket.logs.id
versioning_configuration {
status = "Enabled"
}
}
# 暗号化の設定
resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# パブリックアクセスのブロック
resource "aws_s3_bucket_public_access_block" "logs" {
bucket = aws_s3_bucket.logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# ライフサイクルポリシー
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
id = "archive-old-logs"
status = "Enabled"
transition {
days = 90
storage_class = "GLACIER"
}
expiration {
days = 365
}
}
}
セキュリティのベストプラクティス:
- 必ずパブリックアクセスをブロックする
- サーバーサイド暗号化を有効にする
- バージョニングを有効にして、誤削除に備える
- 適切なライフサイクルポリシーでコストを最適化
参考: AWS S3 Bucket
Terraformの基本ワークフロー
Terraformの実行は、基本的に以下の4つのコマンドで行います。
1. terraform init(初期化)
terraform init
実行結果例:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0 (signed by HashiCorp)
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
このコマンドは以下を実行します:
- プロバイダープラグインのダウンロード
- バックエンドの初期化
- モジュールのダウンロード(使用している場合)
実行タイミング:
- 新しいTerraformプロジェクトを開始する時
- プロバイダーやモジュールを追加・変更した時
- バックエンド設定を変更した時
2. terraform plan(実行計画の確認)
terraform plan
実行結果例:
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0d52744d6551d851e"
+ arn = (known after apply)
+ instance_type = "t3.micro"
+ public_ip = (known after apply)
+ subnet_id = "subnet-0123456789abcdef0"
...
}
Plan: 1 to add, 0 to change, 0 to destroy.
このコマンドで確認できること:
+: 新規作成されるリソース~: 変更されるリソース-: 削除されるリソース-/+: 再作成(削除してから作成)されるリソース
重要: 必ず terraform apply の前に terraform plan を実行し、意図した変更内容かを確認してください。
3. terraform apply(変更の適用)
terraform apply
実行結果例:
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 3s [id=vpc-0123456789abcdef0]
aws_subnet.public: Creating...
aws_subnet.public: Creation complete after 1s [id=subnet-0123456789abcdef0]
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Creation complete after 32s [id=i-0123456789abcdef0]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
instance_id = "i-0123456789abcdef0"
instance_public_ip = "54.123.45.67"
オプション:
terraform apply -auto-approve: 確認なしで実行(CI/CDで使用)terraform apply -target=aws_instance.web: 特定のリソースのみ適用
注意点:
- 本番環境では必ず
terraform planの結果を十分に確認してから実行してください - 大規模な変更の場合は、段階的に適用することを検討してください
4. terraform destroy(リソースの削除)
terraform destroy
実行結果例:
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_instance.web: Destroying... [id=i-0123456789abcdef0]
aws_instance.web: Still destroying... [id=i-0123456789abcdef0, 10s elapsed]
aws_instance.web: Destruction complete after 32s
aws_subnet.public: Destroying... [id=subnet-0123456789abcdef0]
aws_subnet.public: Destruction complete after 1s
aws_vpc.main: Destroying... [id=vpc-0123456789abcdef0]
aws_vpc.main: Destruction complete after 1s
Destroy complete! Resources: 3 destroyed.
警告: このコマンドは、Terraformで管理しているすべてのリソースを削除します。本番環境では細心の注意を払って使用してください。
その他の便利なコマンド
terraform fmt(フォーマット):
terraform fmt
Terraformファイルを標準的な形式に自動整形します。チーム開発では、コミット前に必ず実行しましょう。
terraform validate(構文チェック):
terraform validate
実行結果例:
Success! The configuration is valid.
構文エラーをチェックします。terraform planの前に実行すると、素早く問題を発見できます。
terraform show(現在の状態表示):
terraform show
現在のStateファイルの内容を人間が読める形式で表示します。
terraform state list(リソース一覧):
terraform state list
実行結果例:
aws_vpc.main
aws_subnet.public
aws_instance.web
aws_security_group.web
管理しているすべてのリソースの一覧を表示します。
よくあるエラーとトラブルシューティング
実際にTerraformを使っていると、様々なエラーに遭遇します。ここでは、よく発生するエラーとその対処法を紹介します。
エラー1: 認証エラー
エラーメッセージ:
Error: error configuring Terraform AWS Provider: no valid credential sources for Terraform AWS Provider found.
Please see https://registry.terraform.io/providers/hashicorp/aws
for more information about providing credentials.
原因: AWS認証情報が正しく設定されていない。
対処法:
- 環境変数を確認
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY
echo $AWS_DEFAULT_REGION
- AWS CLIの設定を確認
aws configure list
- IAMロールが正しくアタッチされているか確認(EC2上で実行している場合)
エラー2: リソースが既に存在する
エラーメッセージ:
Error: Error creating VPC: VpcLimitExceeded: The maximum number of VPCs has been reached.
または
Error: Error creating S3 bucket: BucketAlreadyExists: The requested bucket name is not available.
原因:
- リソースの作成上限に達している
- S3バケット名が既に使用されている(グローバルで一意である必要がある)
対処法:
- 既存リソースを確認し、不要なものを削除
- S3バケット名にランダムなサフィックスを追加
resource "random_id" "bucket_suffix" {
byte_length = 8
}
resource "aws_s3_bucket" "main" {
bucket = "my-bucket-${random_id.bucket_suffix.hex}"
}
- AWS Service Quotasで上限緩和を申請
エラー3: 依存関係エラー
エラーメッセージ:
Error: Error launching source instance: InvalidSubnetID.NotFound: The subnet ID 'subnet-xxxxx' does not exist
原因: リソース間の依存関係が正しく定義されていない、または参照しているリソースが存在しない。
対処法:
- リソースの参照方法を確認
# 正しい参照方法
resource "aws_instance" "web" {
subnet_id = aws_subnet.public.id # リソース参照
}
depends_onで明示的に依存関係を定義
resource "aws_instance" "web" {
# ...
depends_on = [aws_internet_gateway.main]
}
エラー4: State ロックエラー
エラーメッセージ:
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Path: bucket/path/terraform.tfstate
Operation: OperationTypeApply
Who: user@hostname
Version: 1.7.0
Created: 2024-01-15 10:30:00.000000 UTC
原因: 別のTerraform実行が進行中、または前回の実行が異常終了してロックが残っている。
対処法:
- 他のユーザーが実行中でないか確認
- 本当にロックが不要な場合のみ、強制的に解除
terraform force-unlock xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
警告: 他のユーザーが実行中の場合、force-unlockは絶対に使用しないでください。State ファイルが破損する可能性があります。
エラー5: プランと実際の変更の不一致
エラーメッセージ:
Error: Provider produced inconsistent result after apply
When applying changes to aws_instance.web, provider "aws" produced an unexpected new value:
.tags: was null, but now cty.ObjectVal(...)
原因:
- プロバイダーのバグ
default_tagsと個別のtagsの競合- 外部からのリソース変更
対処法:
- プロバイダーを最新バージョンに更新
terraform init -upgrade
default_tagsと個別のtagsの設定を見直す- リソースがTerraform外で変更されていないか確認
terraform refresh
terraform plan
エラー6: タイムアウトエラー
エラーメッセージ:
Error: timeout while waiting for state to become 'available' (last state: 'creating', timeout: 10m0s)
原因: リソースの作成に予想以上の時間がかかっている。
対処法:
- タイムアウト時間を延長
resource "aws_db_instance" "main" {
# ...
timeouts {
create = "60m"
update = "60m"
delete = "60m"
}
}
- AWSコンソールで実際のリソース状態を確認
- ネットワークやAWS APIの問題がないか確認
エラー7: 権限不足エラー
エラーメッセージ:
Error: error creating EC2 Instance: UnauthorizedOperation: You are not authorized to perform this operation.
原因: 使用しているIAMユーザー/ロールに必要な権限がない。
対処法:
- IAMポリシーを確認し、必要な権限を追加
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:RunInstances",
"ec2:DescribeInstances",
"ec2:TerminateInstances"
],
"Resource": "*"
}
]
}
- AWS CloudTrailでどの操作が失敗しているか確認
- 最小権限の原則を守りつつ、必要な権限を付与
トラブルシューティングのベストプラクティス
- ログを確認する:
TF_LOG=DEBUG terraform apply
環境変数TF_LOGを設定すると、詳細なログが出力されます。
- 段階的に適用する:
terraform apply -target=aws_vpc.main
terraform apply -target=aws_subnet.public
terraform apply
大規模な変更の場合、リソースごとに段階的に適用すると問題の切り分けが容易になります。
- State ファイルのバックアップ:
terraform state pull > terraform.tfstate.backup
重要な操作の前には、必ずState ファイルをバックアップしてください。
- drift検出:
terraform plan -refresh-only
Terraform外で行われた変更を検出できます。
まとめ
今回は、TerraformでAWSインフラを構築するための基礎から実践的な内容までを解説しました。
重要なポイントのおさらい:
- Infrastructure as Codeの利点
- 再現性、バージョン管理、レビュープロセス、ドキュメント化、自動化
- 基本的なワークフロー
terraform init→terraform plan→terraform applyの流れを徹底
- セキュリティのベストプラクティス
- 認証情報をコードに含めない
- State ファイルの適切な管理
- 最小権限の原則を守る
- リソースの暗号化を有効にする
- 実務での注意点
- 本番環境では必ず
terraform planで変更内容を確認 - リモートバックエンドとState ロックを使用
- タグ付けを徹底して、リソース管理を容易にする
- 適切なモジュール化で再利用性を高める
- 本番環境では必ず
Terraformは、最初は学習コストがありますが、一度習得すればインフラ構築の効率が大幅に向上します。特にチーム開発や複数環境の管理では、その真価を発揮します。
本記事で紹介した内容を基に、まずは開発環境で小規模なリソースから始めて、徐々に複雑な構成に挑戦していくことをお勧めします。実際に手を動かすことで、Terraformの便利さと強力さを実感できるはずです。
