- Published on
Terraform IaC完全ガイド2025:AWS/GCPインフラをコードで管理する全て
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 1. なぜIaCが重要なのか
- 2. Terraform基礎
- 3. State管理
- 4. モジュール(Modules)
- 5. AWS実践インフラ
- 6. GCP実践インフラ
- 7. 高度なパターン
- 8. Import & マイグレーション
- 9. Terragrunt
- 10. CI/CD自動化
- 11. コスト管理(Infracost)
- 12. セキュリティ(tfsec、Checkov)
- 13. Terraform資格ガイド
- 14. インタビュー質問15選
- 15. クイズ
- 参考資料
はじめに
インフラを手動で管理する時代は終わりました。クラウドリソースが数十、数百に増えると、コンソールクリックでは再現性、監査追跡、コラボレーションが不可能になります。TerraformはHashiCorpが作ったオープンソースIaC(Infrastructure as Code)ツールで、AWS、GCP、Azureを含む3,000以上のプロバイダーをサポートしています。
このガイドでは、Terraformの基礎から実践まで全てをカバーします。HCL構文、State管理、モジュール設計、AWS/GCP実践インフラ、Terragrunt、CI/CD自動化、セキュリティスキャン、コスト見積もりまで — 本番レベルのIaCを構築するために必要な全ての知識を体系的に整理します。
1. なぜIaCが重要なのか
1.1 手動管理 vs IaC
| 観点 | 手動(コンソール) | IaC(Terraform) |
|---|---|---|
| 再現性 | 不可能 | コードで100%再現 |
| 監査追跡 | 限定的 | Git履歴 |
| コラボレーション | 不可能 | PRレビュー |
| 環境複製 | 数時間 | 数分 |
| ロールバック | 手動復旧 | terraform apply |
| ドキュメント | 別途管理 | コードがドキュメント |
1.2 Terraform vs 他のIaCツール
| ツール | 言語 | クラウド | 状態管理 | 学習コスト |
|---|---|---|---|---|
| Terraform | HCL | マルチクラウド | Stateファイル | 中 |
| CloudFormation | JSON/YAML | AWS専用 | スタック | 低 |
| Pulumi | Python/TS/Go | マルチクラウド | Stateファイル | 高 |
| AWS CDK | Python/TS/Go | AWS専用 | CloudFormation | 高 |
| OpenTofu | HCL | マルチクラウド | Stateファイル | 中 |
Terraformを選ぶ理由:
- マルチクラウドサポート(AWS + GCP + Azure)
- 最大のコミュニティとモジュールレジストリ
- 宣言的構文で意図が明確
- Plan -> Applyワークフローで安全な変更
2. Terraform基礎
2.1 HCL基本構文
# プロバイダー設定
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
ManagedBy = "terraform"
Project = var.project_name
}
}
}
2.2 変数(Variables)
# variables.tf
variable "aws_region" {
description = "AWS region to deploy resources"
type = string
default = "ap-northeast-2"
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
description = "EC2 instance configuration"
type = object({
instance_type = string
volume_size = number
enable_monitoring = bool
})
default = {
instance_type = "t3.medium"
volume_size = 50
enable_monitoring = true
}
}
variable "allowed_cidrs" {
description = "List of CIDR blocks allowed for ingress"
type = list(string)
default = ["10.0.0.0/8"]
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
2.3 リソース(Resources)
# main.tf
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "main-vpc-${var.environment}"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-${count.index + 1}"
Type = "public"
}
}
2.4 データソース(Data Sources)
# 最新Amazon Linux 2023 AMI検索
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# 現在のAWSアカウント情報
data "aws_caller_identity" "current" {}
# 現在のリージョン
data "aws_region" "current" {}
2.5 出力値(Outputs)
# outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = aws_subnet.public[*].id
}
output "db_password" {
description = "Database password"
value = random_password.db.result
sensitive = true
}
2.6 Locals
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = merge(var.tags, {
Environment = var.environment
ManagedBy = "terraform"
Project = var.project_name
})
env_config = {
dev = {
instance_type = "t3.small"
min_size = 1
max_size = 2
}
staging = {
instance_type = "t3.medium"
min_size = 2
max_size = 4
}
prod = {
instance_type = "t3.large"
min_size = 3
max_size = 10
}
}
config = local.env_config[var.environment]
}
3. State管理
3.1 Stateとは?
Terraform Stateは実際のインフラとコード間のマッピング情報を保存します。terraform.tfstateファイルに全リソースの現在の状態がJSONで記録されます。
Stateがないと:
- Terraformがどのリソースを管理しているか分からない
- Planで全リソースを新規作成しようとする
- リソース間の依存関係を追跡できない
3.2 リモートState(S3 + DynamoDB)
# backend.tf
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "environments/prod/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
S3バケットとDynamoDBテーブルの作成(ブートストラップ):
# bootstrap/main.tf
resource "aws_s3_bucket" "terraform_state" {
bucket = "mycompany-terraform-state"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
3.3 Stateコマンド
# State一覧確認
terraform state list
# 特定リソースの詳細情報
terraform state show aws_vpc.main
# リソース名変更(コードリファクタリング時)
terraform state mv aws_instance.old aws_instance.new
# Stateからリソース削除(実際のインフラは維持)
terraform state rm aws_instance.legacy
4. モジュール(Modules)
4.1 モジュール作成
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = merge(var.tags, {
Name = "${var.name_prefix}-vpc"
})
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.this.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 = "${var.name_prefix}-public-${count.index + 1}"
Type = "public"
})
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.name_prefix}-private-${count.index + 1}"
Type = "private"
})
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(var.tags, {
Name = "${var.name_prefix}-igw"
})
}
resource "aws_nat_gateway" "this" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.tags, {
Name = "${var.name_prefix}-nat-${count.index + 1}"
})
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
domain = "vpc"
}
4.2 モジュール使用
# environments/prod/main.tf
module "vpc" {
source = "../../modules/vpc"
name_prefix = "prod"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
availability_zones = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
enable_nat_gateway = true
tags = local.common_tags
}
4.3 Terraform Registryモジュール使用
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false
tags = local.common_tags
}
5. AWS実践インフラ
5.1 VPC + EC2 + ALB + RDS
resource "aws_launch_template" "app" {
name_prefix = "${local.name_prefix}-app-"
image_id = data.aws_ami.amazon_linux.id
instance_type = local.config.instance_type
vpc_security_group_ids = [aws_security_group.app.id]
user_data = base64encode(templatefile("userdata.sh", {
environment = var.environment
db_host = aws_db_instance.main.endpoint
}))
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = var.instance_config.volume_size
volume_type = "gp3"
encrypted = true
}
}
}
resource "aws_autoscaling_group" "app" {
name = "${local.name_prefix}-app-asg"
desired_capacity = local.config.min_size
min_size = local.config.min_size
max_size = local.config.max_size
vpc_zone_identifier = module.vpc.private_subnet_ids
target_group_arns = [aws_lb_target_group.app.arn]
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
health_check_type = "ELB"
health_check_grace_period = 300
}
5.2 ALB設定
resource "aws_lb" "app" {
name = "${local.name_prefix}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = module.vpc.public_subnet_ids
enable_deletion_protection = var.environment == "prod"
tags = local.common_tags
}
resource "aws_lb_target_group" "app" {
name = "${local.name_prefix}-app-tg"
port = 8080
protocol = "HTTP"
vpc_id = module.vpc.vpc_id
health_check {
path = "/actuator/health"
port = "traffic-port"
healthy_threshold = 3
unhealthy_threshold = 3
timeout = 5
interval = 30
matcher = "200"
}
deregistration_delay = 30
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.app.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.main.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
5.3 RDS(PostgreSQL)
resource "aws_db_instance" "main" {
identifier = "${local.name_prefix}-db"
engine = "postgres"
engine_version = "15.5"
instance_class = "db.r6g.large"
allocated_storage = 100
max_allocated_storage = 500
storage_type = "gp3"
storage_encrypted = true
db_name = "myapp"
username = "admin"
password = random_password.db.result
multi_az = var.environment == "prod"
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
backup_retention_period = 7
skip_final_snapshot = var.environment != "prod"
performance_insights_enabled = true
tags = local.common_tags
}
6. GCP実践インフラ
6.1 VPC + GKE
provider "google" {
project = var.gcp_project_id
region = var.gcp_region
}
resource "google_compute_network" "main" {
name = "${local.name_prefix}-vpc"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "main" {
name = "${local.name_prefix}-subnet"
ip_cidr_range = "10.0.0.0/20"
region = var.gcp_region
network = google_compute_network.main.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.1.0.0/16"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.2.0.0/20"
}
}
resource "google_container_cluster" "primary" {
name = "${local.name_prefix}-gke"
location = var.gcp_region
remove_default_node_pool = true
initial_node_count = 1
network = google_compute_network.main.name
subnetwork = google_compute_subnetwork.main.name
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
workload_identity_config {
workload_pool = "${var.gcp_project_id}.svc.id.goog"
}
release_channel {
channel = "REGULAR"
}
}
resource "google_container_node_pool" "primary" {
name = "${local.name_prefix}-node-pool"
location = var.gcp_region
cluster = google_container_cluster.primary.name
node_count = 3
node_config {
machine_type = "e2-standard-4"
disk_size_gb = 100
disk_type = "pd-ssd"
}
autoscaling {
min_node_count = 3
max_node_count = 10
}
}
6.2 Cloud SQL + Cloud Run
resource "google_sql_database_instance" "main" {
name = "${local.name_prefix}-db"
database_version = "POSTGRES_15"
region = var.gcp_region
settings {
tier = "db-custom-4-16384"
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.main.id
}
backup_configuration {
enabled = true
point_in_time_recovery_enabled = true
start_time = "03:00"
}
availability_type = var.environment == "prod" ? "REGIONAL" : "ZONAL"
}
deletion_protection = var.environment == "prod"
}
resource "google_cloud_run_v2_service" "app" {
name = "${local.name_prefix}-app"
location = var.gcp_region
template {
containers {
image = "gcr.io/${var.gcp_project_id}/myapp:latest"
ports {
container_port = 8080
}
resources {
limits = {
cpu = "2"
memory = "1Gi"
}
}
env {
name = "SPRING_PROFILES_ACTIVE"
value = var.environment
}
startup_probe {
http_get {
path = "/actuator/health"
}
initial_delay_seconds = 10
period_seconds = 3
}
}
scaling {
min_instance_count = var.environment == "prod" ? 2 : 0
max_instance_count = 10
}
}
}
7. 高度なパターン
7.1 Workspaces
# ワークスペース作成
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# ワークスペース切り替え
terraform workspace select prod
locals {
env_config = {
dev = { instance_type = "t3.small", db_instance = "db.t3.medium" }
staging = { instance_type = "t3.medium", db_instance = "db.r6g.large" }
prod = { instance_type = "t3.large", db_instance = "db.r6g.xlarge" }
}
config = local.env_config[terraform.workspace]
}
7.2 Dynamic Blocks
resource "aws_security_group" "app" {
name = "${local.name_prefix}-app-sg"
description = "Application security group"
vpc_id = module.vpc.vpc_id
dynamic "ingress" {
for_each = var.ingress_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"]
}
}
7.3 for_each vs count
# count -- インデックス基盤(削除時の再作成問題)
resource "aws_subnet" "example" {
count = 3
cidr_block = "10.0.${count.index}.0/24"
vpc_id = aws_vpc.main.id
}
# for_each -- キー基盤(安全な削除/追加)
resource "aws_subnet" "example" {
for_each = {
"public-a" = { cidr = "10.0.1.0/24", az = "ap-northeast-2a" }
"public-b" = { cidr = "10.0.2.0/24", az = "ap-northeast-2b" }
"private-a" = { cidr = "10.0.11.0/24", az = "ap-northeast-2a" }
}
cidr_block = each.value.cidr
availability_zone = each.value.az
vpc_id = aws_vpc.main.id
tags = {
Name = "${local.name_prefix}-${each.key}"
}
}
7.4 Lifecycle ルール
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux.id
instance_type = local.config.instance_type
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [ami]
}
}
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}
8. Import & マイグレーション
8.1 terraform importコマンド
terraform import aws_vpc.main vpc-0123456789abcdef0
terraform import aws_instance.web i-0123456789abcdef0
8.2 Import Block(Terraform 1.5+)
import {
to = aws_vpc.main
id = "vpc-0123456789abcdef0"
}
import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}
# コード自動生成
terraform plan -generate-config-out=generated.tf
# レビュー後適用
terraform apply
8.3 既存インフラマイグレーション戦略
段階的アプローチ:
- インベントリ作成 — 現在のリソースをリスト化
- コード作成 — リソースごとにHCLコードを作成
- Import — 既存リソースをStateに取り込み
- Plan検証 —
terraform planで変更がないことを確認 - 段階的管理 — 新しいリソースからTerraformで作成
9. Terragrunt
9.1 DRY環境管理
# infrastructure/terragrunt.hcl(ルート)
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "mycompany-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "ap-northeast-2"
}
EOF
}
# infrastructure/environments/prod/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/vpc"
}
inputs = {
name_prefix = "prod"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]
availability_zones = ["ap-northeast-2a", "ap-northeast-2b"]
enable_nat_gateway = true
}
9.2 依存関係管理
# infrastructure/environments/prod/rds/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/rds"
}
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
name_prefix = "prod"
vpc_id = dependency.vpc.outputs.vpc_id
subnet_ids = dependency.vpc.outputs.private_subnet_ids
}
10. CI/CD自動化
10.1 GitHub Actions + Terraform
# .github/workflows/terraform.yml
name: Terraform CI/CD
on:
pull_request:
paths:
- 'infrastructure/**'
push:
branches:
- main
paths:
- 'infrastructure/**'
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-ci
aws-region: ap-northeast-2
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0
- name: Terraform Init
working-directory: infrastructure/environments/prod
run: terraform init
- name: Terraform Plan
working-directory: infrastructure/environments/prod
run: terraform plan -no-color -out=tfplan
apply:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-ci
aws-region: ap-northeast-2
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init & Apply
working-directory: infrastructure/environments/prod
run: |
terraform init
terraform apply -auto-approve
11. コスト管理(Infracost)
# Infracost実行
infracost breakdown --path=infrastructure/environments/prod
# JSON出力
infracost breakdown \
--path=infrastructure/environments/prod \
--format=json \
--out-file=infracost.json
タギング戦略
locals {
required_tags = {
Environment = var.environment
Project = var.project_name
Team = var.team_name
CostCenter = var.cost_center
ManagedBy = "terraform"
}
}
12. セキュリティ(tfsec、Checkov)
12.1 tfsec静的分析
tfsec ./infrastructure/
12.2 Checkov
checkov -d ./infrastructure/ --framework terraform
12.3 シークレット管理(Vault)
data "vault_generic_secret" "db" {
path = "secret/data/myapp/database"
}
resource "aws_db_instance" "main" {
username = data.vault_generic_secret.db.data["username"]
password = data.vault_generic_secret.db.data["password"]
}
13. Terraform資格ガイド
HashiCorp Certified: Terraform Associate (003)
試験概要:
- 時間:60分
- 問題数:57問(選択式+穴埋め)
- 合格基準:70%
- 有効期間:2年
- 費用:約70 USD
コアドメイン:
- IaCコンセプト理解(15%)— IaCのメリット、Terraformワークフロー
- Terraform基礎(20%)— プロバイダー、リソース、変数、出力
- State管理(15%)— リモートState、ロック、コマンド
- モジュール(15%)— モジュール作成、使用、レジストリ
- Terraform CLI(15%)— init、plan、apply、destroy、import
- ワークフロー(10%)— チームワークフロー、Terraform Cloud
- 実装と保守(10%)— デバッグ、アップグレード
14. インタビュー質問15選
基本(1-5)
Q1. Terraformのコアワークフローを説明してください。
Write(コード作成)-> Plan(変更プレビュー)-> Apply(適用)の3段階です。terraform planは実際の変更なしに何が変わるかを表示し、terraform applyが実際にインフラを変更します。
Q2. Terraform Stateの役割と重要性は?
Stateは実際のインフラとコード間のマッピングを保存します。Stateがないと、Terraformはどのリソースを管理しているか分かりません。リモートバックエンド(S3+DynamoDB)でチーム共有とロックを実装する必要があります。
Q3. ProviderとResourceの違いは?
Providerはクラウド/サービスとのAPI接続を担当するプラグイン(AWS、GCP等)で、Resourceはプロバイダーを通じて管理する実際のインフラオブジェクト(VPC、EC2等)です。
Q4. terraform planとterraform applyの違いは?
planは変更をプレビューで表示し、実際のインフラを変更しません。applyは実際にインフラを変更します。applyは内部的にplanを先に実行します。
Q5. 変数の優先順位(Variable Precedence)を説明してください。
環境変数TF_VAR_xxx、terraform.tfvars、terraform.tfvars.json、auto.tfvars、-varまたは-var-fileコマンドラインオプションの順で優先順位が高くなります。
深掘り(6-10)
Q6. countとfor_eachの違いと、いつどちらを使うべきですか?
countはインデックス基盤で、中間要素を削除するとそれ以降の全リソースが再作成されます。for_eachはキー基盤で、特定の要素だけを安全に削除/追加できます。一般的にfor_eachを推奨します。
Q7. State Lockingが必要な理由は?
2人が同時にterraform applyを実行するとStateが競合し、インフラが損傷する可能性があります。DynamoDBロックテーブルで同時実行を防止します。
Q8. モジュール(Module)のメリットと設計原則は?
再利用性、DRY原則、カプセル化がメリットです。単一責任原則で設計し、変数で設定を外部化し、出力値で他のモジュールと接続します。
Q9. terraform importとImport Blockの違いは?
terraform importはコマンドラインで1つずつリソースをインポートし、コードを自動生成しません。Import Block(1.5+)は宣言的に複数リソースを一括インポートでき、-generate-config-outオプションでコード自動生成が可能です。
Q10. Terraformでシークレットを安全に管理する方法は?
Stateファイルに機密情報が保存されるため、リモートバックエンドの暗号化必須。sensitive = true変数を使用し、HashiCorp VaultやAWS Secrets Managerと連携し、.tfvarsファイルは.gitignoreに追加し、環境変数で注入します。
実践(11-15)
Q11. Drift Detectionとは何で、どう対処しますか?
Driftは実際のインフラがTerraformコードと異なる状態です。terraform planで検知し、terraform applyでコードの状態に戻すかコードを修正します。定期的なplan実行でドリフトを監視する必要があります。
Q12. Terragruntを使う理由は?
TerraformのDRY原則の不足を補完します。環境別設定を継承構造で管理し、リモートState設定を自動化し、モジュール間の依存関係を管理します。
Q13. Terraform CI/CDパイプラインのベストプラクティスは?
PRでplan実行後、結果をコメントとして投稿。mainブランチmerge時にapply実行。OIDCでAWS認証(シークレットキー不要)。terraform fmt + tfsecでコード品質検証。環境別承認プロセス(GitHub Environments)を適用します。
Q14. TerraformとCloudFormationを併用すべき場合は?
AWS専用でCloudFormation StackSets、Service Catalog等のAWSネイティブ機能が必要な場合はCFNを、マルチクラウドやSaaSリソース管理が必要な場合はTerraformを使用します。
Q15. Infracostでコスト管理する方法は?
PRでインフラ変更のコスト影響を自動的に見積もり、コメントとして表示します。月間コストの増減を可視化し、チームがコストを認識した状態でインフラ変更を承認します。
15. クイズ
Q1. Terraform Stateのリモートバックエンドとして S3を使用する際、State Lockingに必要なAWSサービスは?
DynamoDBです。S3はStateファイルの保存、DynamoDBはロック(Lock)テーブルとして同時実行を防止します。dynamodb_table属性で設定します。
Q2. for_eachがcountより推奨される理由は?
countはインデックス基盤のため、中間要素を削除するとそれ以降の全リソースが再作成されます。for_eachはキー基盤のため、特定の要素だけを安全に追加/削除できます。
Q3. terraform plan -generate-config-outオプションの用途は?
Import Blockで宣言したリソースのHCLコードを自動生成します。既存インフラをTerraformにマイグレーションする際、コードを最初から書く必要なく、自動生成されたコードをベースに整理すればよいです。
Q4. Terragruntのdependencyブロックの役割は?
モジュール間の実行順序とデータ受け渡しを管理します。例えばRDSモジュールがVPCモジュールのoutput(subnet_ids)に依存する場合、TerragruntがVPCを先に適用し、そのoutputをRDSモジュールに渡します。
Q5. tfsecとCheckovの違いは?
tfsecはTerraform専用のセキュリティスキャナーで、HCLコードのセキュリティ脆弱性を検査します。CheckovはTerraformだけでなくCloudFormation、Kubernetes、Docker等、様々なIaCツールをサポートする汎用ポリシー検査ツールです。
参考資料
- Terraform 公式ドキュメント
- Terraform Registry
- Terraform AWS Provider
- Terraform GCP Provider
- Terragrunt 公式ドキュメント
- Infracost ドキュメント
- tfsec ガイド
- Checkov ドキュメント
- HashiCorp Learn Terraform
- Terraform Up and Running (O'Reilly)
- AWS VPC Terraform Module
- Terraform Best Practices
- Atlantis ドキュメント
- OpenTofu
- HashiCorp Vault Terraform Provider
- Terraform Associate 試験ガイド
- AWS IAM Terraform ベストプラクティス
- GKE Terraform モジュール