Terraform
HashiCorp Terraform 完全ガイド: Infrastructure as Code の全容
第1章 序論とTerraformの基礎概念
1.1 Infrastructure as Code (IaC) とは
Infrastructure as Code(IaC)は、インフラストラクチャの構成・管理をコードで定義し、バージョン管理・自動デプロイメント可能にするアプローチです。従来のコマンドラインやUIでのマニュアル操作ではなく、プログラミング言語またはマークアップ言語でインフラを定義することで、以下のメリットが得られます。
- 再現性: 同じコードから何度でも同じ環境を構築可能
- バージョン管理: Gitなどで変更履歴を追跡・監査可能
- 自動化: デプロイメント・スケーリング・更新を自動化
- 文書化: コードそのものがインフラの仕様となる
- コスト削減: 環境のプロビジョニング時間短縮、ミス削減
1.2 Terraformとは
HashiCorp Terraformは、宣言型のInfrastructure as Codeツールで、複数のクラウドプロバイダー・オンプレミスシステムをサポートします。HCL(HashiCorp Configuration Language)という独自のDSL(Domain-Specific Language)でインフラを定義し、state fileを通じてリソースのライフサイクルを管理します。
Terraformの主要な特徴:
- マルチクラウド対応: AWS、Azure、GCP、Kubernetes、VMware等多数のプロバイダをサポート
- 宣言型: 「あるべき状態」を定義し、Terraformが現在の状態との差分を計算して適用
- State Management: リソース状態をstate fileで一元管理
- Modular Design: 再利用可能なモジュール機構で複雑な構成を管理
- Plan/Apply: 変更を事前に確認してから適用
1.3 Terraformの対応プロバイダ
Terraformは以下の主要なプロバイダをサポートしています:
| カテゴリ | プロバイダ例 |
|---|---|
| パブリッククラウド | AWS、Google Cloud、Azure、Alibaba Cloud |
| コンテナ・Kubernetes | Docker、Kubernetes、ECS |
| ネットワーク・DNS | Cloudflare、Route53、Azure DNS |
| データベース | MySQL、PostgreSQL、MongoDB |
| CI/CD | GitHub、GitLab、Jenkins |
| 監視・ロギング | Datadog、New Relic、Splunk |
第2章 Terraformのアーキテクチャと内部動作
2.1 Terraformのコアコンポーネント
Terraformの動作は以下の4つのコアコンポーネントで構成されます:
1. Parser (HCL Parser)
- HCL記述のconfigurationを解析してAST(Abstract Syntax Tree)に変換
- Terraform 0.12以降、HCL2という改良版HCLを採用
2. Graph Builder
- リソース間の依存関係を分析
- Directed Acyclic Graph(DAG)を構築して、実行順序を決定
- 並列実行可能なリソースを特定
3. State Manager
- state fileを読み込み/書き込み
- 現在の実インフラの状態をJSON形式で記録
- 差分検出と競合解決を管理
4. Provider Execution Engine
- プロバイダプラグインとの通信
- API呼び出しを通じてリソース作成・変更・削除を実行
- エラーハンドリングとリトライロジック
2.2 State File の役割と構造
State fileはTerraformの最重要コンポーネントです。Terraformは「現在の実装状態」を把握するため、state fileを参照し、設定ファイルの「期待状態」との差分を計算します。
{
"version": 4,
"terraform_version": "1.5.0",
"serial": 42,
"lineage": "a1b2c3d4",
"outputs": {
"instance_ip": {
"value": "192.0.2.1",
"type": "string"
}
},
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "web",
"instances": [
{
"schema_version": 1,
"attributes": {
"id": "i-1234567890abcdef0",
"ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t3.micro",
"tags": {
"Name": "web-server"
}
}
}
]
}
]
}
State fileの重要なフィールド:
version: state format versionserial: state変更のシーケンス番号lineage: state履歴の識別子resources: 管理下のリソースと属性
2.3 Execution Flow (実行フロー)
terraform init
↓
├─ .terraform ディレクトリ作成
├─ プロバイダプラグインダウンロード
└─ backend初期化
↓
terraform plan
↓
├─ Configuration Parse (HCL解析)
├─ Dependency Graph構築
├─ State Load
├─ Provider Query (実リソース状態取得)
├─ Diff Calculate (差分計算)
└─ Plan Output生成
↓
terraform apply
↓
├─ Plan実行
├─ Graph Traversal (DAG順序で実行)
├─ Resource Create/Update/Delete
├─ State Update
└─ Output表示
第3章 HCL (HashiCorp Configuration Language) 詳解
3.1 HCLの基本構文
HCLは宣言型の記述形式で、以下の基本要素で構成されます:
ブロック (Block) ブロックはリソースやデータソース、設定を定義する単位です:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}
3.2 主要なTerraformブロック
resource ブロック 実際のインフラリソースを定義:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "main-vpc"
}
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
}
data ブロック 既存リソースのデータを参照:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}
variable ブロック 入力変数を定義:
variable "environment" {
description = "Environment name"
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_count" {
description = "Number of instances"
type = number
default = 1
validation {
condition = var.instance_count > 0 && var.instance_count <= 10
error_message = "Instance count must be between 1 and 10."
}
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {
Terraform = "true"
Team = "Platform"
}
}
output ブロック 計算結果や属性を出力:
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
sensitive = false
}
output "instance_ips" {
description = "Public and private IPs"
value = {
public = aws_instance.web.public_ip
private = aws_instance.web.private_ip
}
}
output "database_endpoint" {
description = "RDS endpoint"
value = aws_db_instance.main.endpoint
sensitive = true
}
local ブロック ローカル変数を定義:
locals {
common_tags = {
Environment = var.environment
ManagedBy = "Terraform"
CreatedAt = "2024-01-15"
}
resource_prefix = "${var.project}-${var.environment}"
}
resource "aws_instance" "example" {
tags = merge(
local.common_tags,
{
Name = "${local.resource_prefix}-instance"
}
)
}
terraform ブロック Terraform自体の動作設定:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.20"
}
}
backend "s3" {
bucket = "terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider ブロック プロバイダの設定:
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Terraform = "true"
}
}
assume_role {
role_arn = "arn:aws:iam::123456789012:role/TerraformRole"
}
}
provider "kubernetes" {
host = aws_eks_cluster.main.endpoint
cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)
token = data.aws_eks_auth.main.token
}
3.3 データ型と式
基本データ型:
variable "string_var" {
type = string
default = "hello"
}
variable "number_var" {
type = number
default = 42
}
variable "bool_var" {
type = bool
default = true
}
variable "list_var" {
type = list(string)
default = ["a", "b", "c"]
}
variable "map_var" {
type = map(string)
default = {
key1 = "value1"
key2 = "value2"
}
}
variable "tuple_var" {
type = tuple([string, number, bool])
default = ["name", 123, true]
}
variable "object_var" {
type = object({
name = string
age = number
email = string
})
default = {
name = "John"
age = 30
email = "john@example.com"
}
}
組み込み関数:
locals {
# String functions
upper_env = upper(var.environment) # "DEV"
lower_env = lower(var.environment) # "dev"
env_length = length(var.environment) # 3
split_result = split(",", "a,b,c") # ["a", "b", "c"]
joined = join("-", ["dev", "api", "server"]) # "dev-api-server"
# List/Map functions
list_concat = concat([1, 2], [3, 4]) # [1, 2, 3, 4]
list_slice = slice([0, 1, 2, 3, 4], 1, 4) # [1, 2, 3]
map_merge = merge({a = 1}, {b = 2}) # {a = 1, b = 2}
contains_val = contains([1, 2, 3], 2) # true
# Conditional
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
# For expression
subnet_ids = [for subnet in aws_subnet.all : subnet.id]
subnet_map = { for subnet in aws_subnet.all : subnet.tags.Name => subnet.id }
}
第4章 リソース管理とライフサイクル
4.1 リソース作成・更新・削除のライフサイクル
Terraformはリソースのライフサイクルを以下の段階で管理します:
┌─────────────────────┐
│ terraform apply │
└──────────┬──────────┘
│
┌──────▼─────────┐
│ Create │ (リソース新規作成)
└──────┬─────────┘
│
┌──────▼─────────┐
│ Update-in-place│ (リソース更新・置き換え)
└──────┬─────────┘
│
┌──────▼─────────┐
│ Delete │ (リソース削除)
└──────┬─────────┘
│
┌──────▼─────────┐
│ State Update │
└─────────────────┘
4.2 lifecycle メタアルギュメント
リソースのライフサイクル動作をカスタマイズ:
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
lifecycle {
# create_before_destroy: 置き換え時、新リソース作成後に旧リソースを削除
create_before_destroy = true
# prevent_destroy: 削除を防止 (タイムアウト後に削除可能)
prevent_destroy = false
# ignore_changes: 指定フィールドの変更を無視
ignore_changes = [
tags["LastModifiedDate"],
ami
]
}
tags = {
Name = "web-server"
}
}
resource "aws_autoscaling_group" "web" {
name = "web-asg"
min_size = 1
max_size = 5
desired_capacity = 2
lifecycle {
# ignore_changes でmin_size/max_sizeの外部変更を無視
ignore_changes = [desired_capacity]
}
}
resource "aws_db_instance" "main" {
identifier = "main-db"
lifecycle {
# protect_destroy = true で削除を完全に防止
prevent_destroy = true
}
}
4.3 リソースの依存関係管理
Terraformは自動的に依存関係を検出しますが、明示的に指定することも可能です:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
# 暗黙的な依存関係: aws_vpc.main
}
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
depends_on = [aws_subnet.private] # 明示的な依存関係
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.private.id
vpc_security_group_ids = [aws_security_group.web.id]
# 他のリソース完成後に実行する必要がある場合
depends_on = [aws_nat_gateway.main]
user_data = base64encode(<<-EOF
#!/bin/bash
echo "Initialized at $(date)" > /var/log/init.log
EOF
)
}
第5章 Modulesによる再利用性と構成管理
5.1 Moduleの基本概念
Module は Terraform configuration を再利用可能な単位にカプセル化します。複雑なインフラ構成を管理しやすくします。
Module の構成:
modules/
├── vpc/
│ ├── main.tf # リソース定義
│ ├── variables.tf # 入力変数
│ ├── outputs.tf # 出力
│ └── terraform.tf # Terraform設定
├── security_group/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── eks/
├── main.tf
├── variables.tf
└── outputs.tf
root/
├── main.tf # Module呼び出し
├── variables.tf
├── outputs.tf
└── terraform.tf
5.2 VPCモジュールの実装例
modules/vpc/variables.tf:
variable "cidr_block" {
description = "CIDR block for VPC"
type = string
}
variable "environment" {
description = "Environment name"
type = string
}
variable "availability_zones" {
description = "AZs for subnets"
type = list(string)
}
variable "enable_nat_gateway" {
description = "Enable NAT Gateway"
type = bool
default = true
}
modules/vpc/main.tf:
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet-${count.index + 1}"
Type = "Public"
}
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 4, count.index + length(var.availability_zones))
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.environment}-private-subnet-${count.index + 1}"
Type = "Private"
}
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.availability_zones) : 0
domain = "vpc"
tags = {
Name = "${var.environment}-eip-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? length(var.availability_zones) : 0
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.main]
}
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" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
count = var.enable_nat_gateway ? length(var.availability_zones) : 0
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.environment}-private-rt-${count.index + 1}"
}
}
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
subnet_id = aws_subnet.private[count.index].id
route_table_id = var.enable_nat_gateway ? aws_route_table.private[count.index].id : null
}
modules/vpc/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 "private_subnet_ids" {
description = "Private subnet IDs"
value = aws_subnet.private[*].id
}
output "nat_gateway_ips" {
description = "NAT Gateway EIPs"
value = aws_eip.nat[*].public_ip
}
5.3 ルート configurationからのModule呼び出し
root/main.tf:
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
environment = var.environment
availability_zones = ["ap-northeast-1a", "ap-northeast-1b", "ap-northeast-1c"]
enable_nat_gateway = var.environment == "prod"
}
module "security_group" {
source = "./modules/security_group"
vpc_id = module.vpc.vpc_id
environment = var.environment
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = module.vpc.public_subnet_ids[0]
vpc_security_group_ids = [module.security_group.web_sg_id]
tags = {
Name = "${var.environment}-web-server"
}
}
第6章 State Management (状態管理)
6.1 State Backendの種類
Terraformのstate fileはローカルまたはリモートに保存できます。本番環境ではリモートbackendの使用が推奨されます。
Local Backend (開発・テスト用):
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
S3 Backend (AWS推奨):
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
S3 backend の構成例:
# backend-setup/main.tf (初回のみ)
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state"
tags = {
Name = "Terraform State"
}
}
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 = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform State Lock"
}
}
Azure Backend:
terraform {
backend "azurerm" {
resource_group_name = "my-rg"
storage_account_name = "mytfstate"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
Terraform Cloud Backend:
terraform {
cloud {
organization = "my-org"
workspaces {
name = "my-workspace"
}
}
}
6.2 State Lock メカニズム
複数ユーザーの同時アクセスによる競合を防ぐため、Terraform は state lock を実装します。
# S3 + DynamoDB による state lock
terraform {
backend "s3" {
bucket = "terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Lock の動作:
terraform apply実行時、DynamoDB に LockID レコードを作成- 他のユーザーは状態ロック中のため、操作を待機
apply完了後、lock を削除
Deadlock 解放:
# 何らかの理由で lock が残った場合
terraform force-unlock <LOCK_ID>
6.3 State Inspection と Migration
State の内容確認:
terraform state list # リソース一覧表示
terraform state show aws_instance.web # 特定リソースの詳細表示
terraform state pull > backup.tfstate # State ダウンロード
State Migration:
# Local から S3 への移行
# 1. backend 設定を S3 に変更
# 2. terraform init を実行すると、migration するか確認される
terraform init
# または明示的に移行
terraform init -migrate-state
第7章 計画・実行・検証 (Plan/Apply/Destroy)
7.1 terraform plan の詳細
terraform plan はドライラン実行し、変更内容を事前確認できます:
terraform plan -out=tfplan
terraform plan -var="environment=prod"
terraform plan -var-file="prod.tfvars"
terraform plan -target=aws_instance.web # 特定リソースのみplan
Plan 出力例:
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0c55b159cbfafe1f0"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t3.micro"
+ ipv6_address_count = (known after apply)
+ key_name = "my-key"
+ primary_network_interface_id = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ security_groups = ["sg-12345678"]
+ subnet_id = "subnet-12345678"
+ tags = {
+ "Name" = "web-server"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
7.2 terraform apply の実行
計画内容を確認して適用:
# 対話的実行 (yes/no 確認)
terraform apply
# Planファイルから実行 (確認スキップ)
terraform apply tfplan
# 自動承認 (CI/CD用)
terraform apply -auto-approve
# 特定リソースのみ適用
terraform apply -target=aws_instance.web
7.3 terraform destroy による削除
管理下のすべてのリソースを削除:
# 対話的削除
terraform destroy
# 自動承認で削除
terraform destroy -auto-approve
# 特定リソースのみ削除
terraform destroy -target=aws_instance.web
# 削除予定の確認
terraform plan -destroy
リソース削除の前に:
- prevent_destroy = true で保護
- 重要なデータベースはバックアップ
- 削除権限の制限
第8章 Variables・Outputs・Data Sources
8.1 Variables の活用パターン
基本的な variable 定義:
variable "aws_region" {
description = "AWS region"
type = string
default = "ap-northeast-1"
}
variable "instance_count" {
description = "Number of instances"
type = number
validation {
condition = var.instance_count > 0
error_message = "instance_count must be positive."
}
}
variable "allowed_cidr_blocks" {
description = "Allowed CIDR blocks"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "tags" {
description = "Common tags"
type = map(string)
default = {
Environment = "dev"
Terraform = "true"
}
}
.tfvars ファイル:
# dev.tfvars
aws_region = "ap-northeast-1"
instance_count = 1
tags = {
Environment = "dev"
Team = "Platform"
}
# prod.tfvars
aws_region = "ap-northeast-1"
instance_count = 5
tags = {
Environment = "prod"
Team = "Platform"
CostCenter = "Engineering"
}
実行:
terraform apply -var-file="dev.tfvars"
terraform apply -var-file="prod.tfvars"
terraform apply -var="instance_count=3"
8.2 Outputs の設計
Outputs は他のモジュールや外部システムが参照する値を提供します:
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "database_endpoint" {
description = "RDS database endpoint"
value = aws_db_instance.main.endpoint
sensitive = true # 値をコンソール出力で隠す
}
output "load_balancer_dns" {
description = "ALB DNS name"
value = aws_lb.main.dns_name
}
output "ec2_instances" {
description = "EC2 instance details"
value = {
for instance in aws_instance.web :
instance.id => {
private_ip = instance.private_ip
public_ip = instance.public_ip
az = instance.availability_zone
}
}
}
# リモートstateから参照可能
output "all_instance_ips" {
value = [
for i in aws_instance.web :
i.public_ip if i.public_ip != null
]
}
リモート state reference:
# infrastructure モジュール内
output "vpc_id" {
value = aws_vpc.main.id
}
---
# application モジュール内
data "terraform_remote_state" "infrastructure" {
backend = "s3"
config = {
bucket = "terraform-state"
key = "infrastructure/terraform.tfstate"
region = "ap-northeast-1"
}
}
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.infrastructure.outputs.vpc_id
}
8.3 Data Sources による外部データ参照
Existing リソース情報を参照:
# 既存 AMI を検索
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}
}
# 既存 VPC 参照
data "aws_vpc" "default" {
default = true
}
# 既存 subnet を フィルター検索
data "aws_subnets" "private" {
filter {
name = "tag:Type"
values = ["Private"]
}
filter {
name = "vpc-id"
values = [aws_vpc.main.id]
}
}
# AWS アカウント情報取得
data "aws_caller_identity" "current" {}
# AWS 利用可能 AZ 取得
data "aws_availability_zones" "available" {
state = "available"
}
# 参照
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "web-${data.aws_caller_identity.current.account_id}"
}
}
resource "aws_subnet" "distributed" {
count = length(data.aws_availability_zones.available.names)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet("10.0.0.0/16", 4, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
}
第9章 Advanced Patterns (高度な使用パターン)
9.1 Count と For_each による複数リソース管理
Count パターン:
variable "subnet_count" {
type = number
default = 3
}
resource "aws_subnet" "main" {
count = var.subnet_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet("10.0.0.0/16", 4, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index % length(data.aws_availability_zones.available.names)]
tags = {
Name = "subnet-${count.index + 1}"
}
}
# 出力: subnet-0, subnet-1, subnet-2
output "subnet_ids" {
value = aws_subnet.main[*].id
}
For_each パターン (推奨):
variable "subnet_config" {
type = map(object({
cidr_block = string
az = string
}))
default = {
public-1a = {
cidr_block = "10.0.1.0/24"
az = "ap-northeast-1a"
}
public-1b = {
cidr_block = "10.0.2.0/24"
az = "ap-northeast-1b"
}
private-1a = {
cidr_block = "10.0.10.0/24"
az = "ap-northeast-1a"
}
}
}
resource "aws_subnet" "main" {
for_each = var.subnet_config
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr_block
availability_zone = each.value.az
tags = {
Name = each.key
}
}
# 参照
output "subnet_map" {
value = {
for name, subnet in aws_subnet.main :
name => subnet.id
}
}
9.2 Conditional Logic と Dynamic Blocks
条件分岐:
variable "enable_nat_gateway" {
type = bool
default = false
}
variable "environment" {
type = string
}
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? 1 : 0
allocation_id = aws_eip.nat[0].id
subnet_id = aws_subnet.public[0].id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = var.enable_nat_gateway ? aws_nat_gateway.main[0].id : null
internet_gateway_id = !var.enable_nat_gateway ? aws_internet_gateway.main.id : null
}
}
# Conditional outputs
output "nat_gateway_ip" {
value = var.enable_nat_gateway ? aws_eip.nat[0].public_ip : null
}
Dynamic Blocks:
variable "ingress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
]
}
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
dynamic "tags" {
for_each = {
Name = "web-sg"
Env = var.environment
}
content {
key = tags.key
value = tags.value
}
}
}
9.3 Splat と複雑な式
Splat expressions:
# すべての instances の ID を取得
output "all_instance_ids" {
value = aws_instance.web[*].id
}
# 複数の属性を同時取得
output "instance_details" {
value = aws_instance.web[*].[id, private_ip, public_ip]
}
# Map と組み合わせ
output "instance_map" {
value = {
for i, instance in aws_instance.web :
instance.id => instance.public_ip
}
}
# Filter と組み合わせ
output "public_ips" {
value = [
for instance in aws_instance.web :
instance.public_ip if instance.public_ip != null
]
}
第10章 ベストプラクティスとアンチパターン
10.1 推奨される構成
ディレクトリ構成:
terraform/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ └── (同様)
│ └── prod/
│ └── (同様)
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── eks/
│ │ └── (同様)
│ ├── rds/
│ │ └── (同様)
│ └── security_group/
│ └── (同様)
├── shared/
│ ├── variables.tf
│ └── locals.tf
└── backend/
└── main.tf # State backend 構成
10.2 Naming Convention
# リソース命名規則
locals {
resource_prefix = "${var.project}-${var.environment}-${var.region_short}"
}
# 一貫性のある命名
resource "aws_instance" "web" {
tags = {
Name = "${local.resource_prefix}-web-server"
}
}
resource "aws_security_group" "web" {
name_prefix = "${local.resource_prefix}-web-sg-"
}
resource "aws_rds_cluster" "main" {
cluster_identifier_prefix = "${local.resource_prefix}-db-"
}
10.3 バージョン管理とロック
terraform {
required_version = ">= 1.0, < 2.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.20"
}
}
}
.terraform.lock.hcl:
# Generated by terraform lock file
# Manual changes will be overwritten
provider "registry.terraform.io/hashicorp/aws" {
version = "5.0.1"
constraints = "~> 5.0"
hashes = [
"h1:...",
]
}
10.4 Local Values による DRY 原則
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
CreatedAt = formatdate("YYYY-MM-DD", timestamp())
}
naming_convention = {
separator = "-"
project = var.project_name
env = var.environment
}
}
resource "aws_instance" "web" {
tags = merge(local.common_tags, {
Name = "${local.naming_convention.project}-${local.naming_convention.env}-web"
})
}
10.5 アンチパターン
❌ 避けるべきパターン:
- Hardcoding 値:
# Bad
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # Hardcoded
instance_type = "t3.micro"
}
# Good
variable "ami_id" {
type = string
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
}
- State の手動操作:
# Bad: terraform.tfstate を直接編集
# Good: terraform state コマンドを使用
terraform state rm aws_instance.web
- 大規模な単一ファイル:
# Bad: すべて main.tf に書く
# Good: 責務ごとにファイルを分割
# main.tf, variables.tf, outputs.tf, locals.tf
- Module の過度な入れ子:
# Bad: module の中の module の中の module...
# Good: 2-3 レベルまでに留める
第11章 Testing と Validation
11.1 terraform validate
構文チェック:
terraform validate # 構文とロジックを検証
terraform fmt # コード整形
terraform fmt -check # 整形が必要か確認のみ
11.2 tflint による静的解析
# インストール
brew install tflint
# 実行
tflint
# AWS プロバイダチェック
tflint --init
tflint --config=.tflint.hcl
.tflint.hcl:
plugin "aws" {
enabled = true
version = "0.24.0"
}
rule "aws_instance_invalid_type" {
enabled = true
}
rule "aws_autoscaling_group_invalid_launch_configuration" {
enabled = true
}
11.3 Terratest による統合テスト
Go言語ベースのテストフレームワーク:
// test/terraform_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestTerraformExample(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/dev",
VarFiles: []string{"../dev.tfvars"},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
subnetIDs := terraform.OutputList(t, terraformOptions, "subnet_ids")
assert.Equal(t, 3, len(subnetIDs))
}
第12章 Terraform Cloud と CI/CD 統合
12.1 Terraform Cloud の概要
Terraform Cloud は HashiCorp が提供するホスト型バックエンド:
terraform {
cloud {
organization = "my-organization"
workspaces {
name = "production"
}
}
}
12.2 GitHub Actions との統合
.github/workflows/terraform.yml:
name: 'Terraform'
on:
push:
branches: [ main ]
paths: [ 'terraform/**' ]
pull_request:
branches: [ main ]
paths: [ 'terraform/**' ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Terraform Init
run: terraform init
working-directory: terraform
- name: Terraform Format
run: terraform fmt -check
working-directory: terraform
- name: Terraform Validate
run: terraform validate
working-directory: terraform
- name: Terraform Plan
run: terraform plan -no-color
working-directory: terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Apply
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: terraform apply -auto-approve
working-directory: terraform
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
12.3 GitLab CI との統合
.gitlab-ci.yml:
stages:
- validate
- plan
- apply
terraform:validate:
stage: validate
image: hashicorp/terraform:latest
script:
- terraform init
- terraform validate
- terraform fmt -check
terraform:plan:
stage: plan
image: hashicorp/terraform:latest
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
only:
- merge_requests
terraform:apply:
stage: apply
image: hashicorp/terraform:latest
script:
- terraform init
- terraform apply -auto-approve tfplan
dependencies:
- terraform:plan
only:
- main
第13章 トラブルシューティングと運用
13.1 一般的な問題と解決策
問題1: State Lock が解放されない
# 原因: 前回の apply が異常終了
# 解決策
terraform force-unlock <LOCK_ID>
問題2: 予期しないリソース削除
# 原因: plan の確認ミス
# 回避策: state backup から復元
terraform state pull > backup.tfstate
# 手動で状態を修正
terraform state push modified.tfstate
問題3: リソースが state に存在しない
# 原因: Terraform 外で削除されたリソース
# 解決策: 再度 import
terraform import aws_instance.web i-1234567890abcdef0
13.2 Terraform リソースのインポート
既存リソースを Terraform 管理下に追加:
# 既存 EC2 インスタンスをインポート
terraform import aws_instance.web i-1234567890abcdef0
# インポート後、resource ブロックを定義
resource "aws_instance" "web" {
tags = {
Name = "web-server"
}
}
複数リソースのバルクインポート:
#!/bin/bash
# import_instances.sh
INSTANCE_IDS="i-111111 i-222222 i-333333"
for id in $INSTANCE_IDS; do
terraform import aws_instance.web $id
done
13.3 ログとデバッグ
# Verbose ログ出力
TF_LOG=DEBUG terraform apply
# ログレベル
# TRACE, DEBUG, INFO, WARN, ERROR
TF_LOG=TRACE terraform plan
# ログをファイルに保存
TF_LOG_PATH=terraform.log TF_LOG=DEBUG terraform apply
# Graph 確認
terraform graph > graph.dot
第14章 セキュリティと運用のベストプラクティス
14.1 State File のセキュリティ
重要ポイント:
- State file には sensitive 情報が平文で含まれる
- リモート backend + encryption を必須
- Access Control を設定
# S3 Backend with Encryption
terraform {
backend "s3" {
bucket = "terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true # Server-side encryption 有効
dynamodb_table = "terraform-locks"
}
}
# Sensitive outputs
output "database_password" {
value = aws_db_instance.main.password
sensitive = true # Console 出力では隠される
}
14.2 認証情報の管理
❌ Bad:
provider "aws" {
access_key = "AKIAIOSFODNN7EXAMPLE" # Hardcoded
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
✅ Good:
provider "aws" {
region = var.aws_region
# IAM Role や AWS CLI credentials を使用
}
# 環境変数から認証情報読み込み
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
terraform apply
14.3 RBAC と権限管理
IAM Policy 例:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::terraform-state/*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:*:*:table/terraform-locks"
},
{
"Effect": "Allow",
"Action": [
"ec2:*",
"rds:*",
"elasticloadbalancing:*"
],
"Resource": "*"
}
]
}
14.4 監査とコンプライアンス
# CloudTrail ログの有効化
resource "aws_cloudtrail" "main" {
name = "terraform-audit"
s3_bucket_name = aws_s3_bucket.audit_logs.id
include_global_service_events = true
is_multi_region_trail = true
enable_log_file_validation = true
depends_on = [aws_s3_bucket_policy.audit_logs]
}
# Config による Compliance 監視
resource "aws_config_configuration_aggregator" "main" {
name = "terraform-compliance"
account_aggregation_sources {
all_regions = true
account_ids = [data.aws_caller_identity.current.account_id]
}
}
第15章 まとめと将来の展望
15.1 Terraform の学習パス
Level 1 - 基礎:
- HCL 基本構文
- 単純なリソース作成
- Variables/Outputs
Level 2 - 中級:
- Modules の設計と実装
- State Management
- Plan/Apply のベストプラクティス
Level 3 - 上級:
- 複雑な Modules 構成
- CI/CD 統合
- Terraform Cloud/Enterprise
- カスタムプロバイダ開発
Level 4 - エキスパート:
- マルチクラウド運用
- 大規模 State 管理
- パフォーマンスチューニング
- カスタム Providers/Modules 開発
15.2 Terraform エコシステム
関連ツール:
- Terragrunt: Terraform ラッパー、DRY 原則の強化
- Atlantis: Terraform の PR Review 自動化
- Checkov: IaC セキュリティスキャン
- Snyk IaC: Infrastructure as Code 脆弱性検査
- Sentinel: Policy as Code (Terraform Cloud Enterprise)
15.3 Terraform 1.5 の新機能
Removed Block:
removed {
from = aws_instance.old_server
}
Import Blocks:
import {
to = aws_instance.example
id = "i-1234567890abcdef0"
}
Test Framework:
run "test_vpc_creation" {
command = apply
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "VPC CIDR block mismatch"
}
}
15.4 実践的な Tips
1. 効率的な开発フロー:
# 開発時は Local State 使用
terraform init
# 検証
terraform validate && terraform fmt && tflint
# Plan 確認
terraform plan -out=tfplan
# Apply
terraform apply tfplan
# 削除
terraform destroy -auto-approve
2. 本番環境での運用:
- Remote Backend (S3 + DynamoDB)
- State Locking 有効化
- Sensitive Variables 管理
- terraform.tfvars.example でサンプル提供
- GitHub Actions/GitLab CI で自動化
- Approval workflow 設定
3. チーム開発のポイント:
- .gitignore に .terraform/ や .tfstate を追加
- terraform.lock.hcl は version control に含める
- Module 再利用で一貫性確保
- Code Review プロセス確立
- Documentation を充実させる
参考資料
- 公式ドキュメント: https://www.terraform.io/docs
- Terraform Registry: https://registry.terraform.io
- HashiCorp Learn: https://learn.hashicorp.com/terraform
- AWS Provider Documentation: https://registry.terraform.io/providers/hashicorp/aws
- Terraform CLI Reference: https://www.terraform.io/cli
- Terraform Best Practices: https://www.terraform.io/cloud-docs/best-practices
付録: よく使用されるコマンド一覧
# 初期化
terraform init # Backend & Provider 初期化
# 計画・実行
terraform plan # 変更計画表示
terraform plan -out=tfplan # Plan をファイルに保存
terraform apply # 変更適用
terraform apply tfplan # 保存された plan から適用
terraform destroy # リソース削除
# State 管理
terraform state list # State 内のリソース一覧
terraform state show <resource> # リソース詳細表示
terraform state pull > backup.json # State バックアップ
terraform state push backup.json # State復元
terraform state rm <resource> # State からリソース削除
terraform import <type>.<name> <id> # リソースをインポート
# Inspection
terraform validate # 構文チェック
terraform fmt # コード整形
terraform output # 出力値表示
terraform graph # 依存関係グラフ表示
# Workspace 管理
terraform workspace list # Workspace 一覧
terraform workspace new <name> # 新規 Workspace 作成
terraform workspace select <name> # Workspace 切り替え
terraform workspace delete <name> # Workspace 削除
# ログ & デバッグ
terraform console # Terraform REPL (式テスト)
TF_LOG=DEBUG terraform plan # Debug ログ出力
terraform -version # バージョン確認
結論
Terraform は Infrastructure as Code を実現する強力なツールです。本ガイドで紹介した概念・パターン・ベストプラクティスを理解することで、スケーラブルで保守性の高いインフラストラクチャを構築できます。
複雑性が増すにつれて、Module 設計、State Management、CI/CD 統合が重要になります。段階的に学習を進め、組織にあわせた Terraform ワークフローを確立することが成功の鍵です。
本日は以上です。ご閲覧ありがとうございました。