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
コンテナ・KubernetesDocker、Kubernetes、ECS
ネットワーク・DNSCloudflare、Route53、Azure DNS
データベースMySQL、PostgreSQL、MongoDB
CI/CDGitHub、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 version
  • serial: 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 の動作:

  1. terraform apply 実行時、DynamoDB に LockID レコードを作成
  2. 他のユーザーは状態ロック中のため、操作を待機
  3. 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 アンチパターン

❌ 避けるべきパターン:

  1. 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"
}
  1. State の手動操作:
# Bad: terraform.tfstate を直接編集
# Good: terraform state コマンドを使用
terraform state rm aws_instance.web
  1. 大規模な単一ファイル:
# Bad: すべて main.tf に書く
# Good: 責務ごとにファイルを分割
# main.tf, variables.tf, outputs.tf, locals.tf
  1. 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 を充実させる

参考資料


付録: よく使用されるコマンド一覧

# 初期化
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 ワークフローを確立することが成功の鍵です。

本日は以上です。ご閲覧ありがとうございました。