mcpField
T
terraform-infrastructure-as-code

terraform-infrastructure-as-code

github.com/manutejクラウド・DevOps62453 回インストール39 週間前に更新
リポジトリ

terraform-infrastructure-as-code について

宣言型のHCL設定でインフラを定義・プロビジョニングするTerraformのガイドです。リソース、再利用可能なモジュール、ステート、ワークスペース、プロバイダー設定を扱い、インフラチームを対象としています。

主な機能

  1. 01宣言型インフラ設定
  2. 02リソースとデータソースの定義
  3. 03再利用可能なモジュールと変数
  4. 04ローカルおよびリモートステート管理
  5. 05ワークスペースごとの設定インスタンス
  6. 06プロバイダーとマルチクラウド設定

活用シーン

  1. 01インフラをバージョン管理可能なコードで定義
  2. 02設定間でインフラコンポーネントを再利用
  3. 03開発環境と本番環境のステートを分離
  4. 04インフラ変更を確認して適用

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/manutej/luxor-claude-marketplace --skill terraform-infrastructure-as-code

インストール後、Claude Code で/terraform-infrastructure-as-codeを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.verification-summary.txt
EXAMPLES.md
README.md

詳細

Terraform による Infrastructure as Code ― 包括ガイド

目次

  1. Terraform の概要
  2. コアコンセプト
  3. リソース
  4. データソース
  5. 変数と出力
  6. モジュール
  7. 状態管理
  8. ワークスペース
  9. プロバイダーの設定
  10. 高度な機能
  11. 依存関係
  12. プロビジョナー
  13. ベストプラクティス
  14. CI/CD との統合

Terraform の概要

Terraform は、HashiCorp が開発したオープンソースの Infrastructure as Code(IaC)ツールです。HashiCorp Configuration Language(HCL)という宣言型の設定言語を使って、インフラストラクチャを定義し、プロビジョニングできます。Terraform は、パブリッククラウドやプライベートクラウドのインフラストラクチャ、ネットワーク機器、Software as a Service などの外部リソースを管理します。

主なメリット
  • 宣言型の設定:インフラストラクチャの作成方法ではなく、あるべき状態を定義します
  • クラウドに依存しない:複数のクラウドプロバイダーやサービスに対応しています
  • バージョン管理:インフラストラクチャのコードをバージョン管理し、レビューできます
  • 適用前の計画:変更を適用する前にプレビューできます
  • リソースグラフ:リソース間の依存関係を自動的に管理します
  • 状態管理:インフラストラクチャの現在の状態を追跡します
Terraform のワークフロー
# Initialize Terraform working directory
terraform init

# Initialize and upgrade provider versions
terraform init -upgrade

# Initialize with backend configuration
terraform init -backend-config="bucket=my-state-bucket"

# Generate a plan
terraform plan

# Save plan to file for later apply
terraform plan -out=tfplan

# Plan with specific variable values
terraform plan -var="region=us-west-2" -var="instance_type=t2.micro"

# Apply with interactive approval
terraform apply

# Auto-approve without confirmation
terraform apply -auto-approve

# Apply a saved plan file
terraform apply tfplan

# Destroy with confirmation prompt
terraform destroy

# Auto-approve destruction
terraform destroy -auto-approve

コアコンセプト

1. リソース

リソースは、Terraform の最も基本的な要素です。仮想マシン、ネットワーク、データベース、DNS レコードなどのインフラストラクチャオブジェクトを表します。

# Basic resource declaration
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name = "WebServer"
    Environment = "production"
  }
}
2. プロバイダー

プロバイダーは、Terraform がクラウドプラットフォーム、SaaS プロバイダー、その他の API と連携できるようにするプラグインです。

# AWS provider configuration
provider "aws" {
  region = "us-west-2"

  default_tags {
    tags = {
      ManagedBy = "Terraform"
      Project   = "MyApp"
    }
  }
}
3. 状態

Terraform は、インフラストラクチャに関する情報を状態ファイルに保存します。この状態を使って、実際のリソースを設定に対応付け、メタデータを追跡します。

4. 設定言語(HCL)

HCL は人間にとって読みやすく、機械でも扱いやすいように設計されているため、インフラストラクチャの設定に適しています。

リソース

リソースは、Terraform 設定の構成要素です。各リソースブロックには、1 つ以上のインフラストラクチャオブジェクトを記述します。

基本的なリソース構文
resource "resource_type" "resource_name" {
  argument1 = "value1"
  argument2 = "value2"

  nested_block {
    nested_argument = "nested_value"
  }
}
リソースの例
AWS EC2 インスタンス
resource "aws_instance" "app_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  key_name      = "my-keypair"

  vpc_security_group_ids = [aws_security_group.app.id]
  subnet_id              = aws_subnet.public.id

  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, World!" > index.html
              nohup busybox httpd -f -p 8080 &
              EOF

  tags = {
    Name        = "AppServer"
    Environment = "production"
    ManagedBy   = "Terraform"
  }
}
AWS VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "main-vpc"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-west-2a"
  map_public_ip_on_launch = true

  tags = {
    Name = "public-subnet"
  }
}

resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-west-2a"

  tags = {
    Name = "private-subnet"
  }
}
AWS S3 バケット
resource "aws_s3_bucket" "data" {
  bucket = "my-app-data-bucket-12345"

  tags = {
    Name        = "Data Bucket"
    Environment = "production"
  }
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}
AWS セキュリティグループ
resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "HTTP from anywhere"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTPS from anywhere"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "web-security-group"
  }
}
リソースのライフサイクル
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  lifecycle {
    create_before_destroy = true
    prevent_destroy       = true
    ignore_changes        = [tags]
  }
}

ライフサイクルのオプション:

  • create_before_destroy:古いリソースを破棄する前に、新しいリソースを作成します
  • prevent_destroy:リソースが誤って破棄されるのを防ぎます
  • ignore_changes:指定した属性への変更を無視します
  • replace_triggered_by:特定のリソースが変更されたときに置き換えを強制します
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  lifecycle {
    replace_triggered_by = [
      aws_iam_policy.example.id
    ]
  }
}

データソース

データソースを使うと、Terraform の外部で定義された情報や、別の Terraform 設定で定義された情報を利用できます。

データソースの構文
data "resource_type" "name" {
  # Query parameters
}
データソースの例
# Query existing AWS resources
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
}

# Use data source in resource
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
}

# Query availability zones
data "aws_availability_zones" "available" {
  state = "available"
}

# Query VPC
data "aws_vpc" "default" {
  default = true
}

# Query remote state
data "terraform_remote_state" "network" {
  backend = "s3"

  config = {
    bucket = "terraform-state"
    key    = "network/terraform.tfstate"
    region = "us-west-2"
  }
}

# Use remote state outputs
resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
  subnet_id     = data.terraform_remote_state.network.outputs.subnet_id

  availability_zone = data.aws_availability_zones.available.names[0]
}
一般的なデータソース
# AWS Account ID
data "aws_caller_identity" "current" {}

output "account_id" {
  value = data.aws_caller_identity.current.account_id
}

# AWS Region
data "aws_region" "current" {}

output "region" {
  value = data.aws_region.current.name
}

# Route53 Zone
data "aws_route53_zone" "primary" {
  name = "example.com"
}

# IAM Policy Document
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

変数と出力

入力変数

変数を使うと、設定をパラメーター化して再利用できるようになります。

# Simple variable
variable "region" {
  type        = string
  default     = "us-west-2"
  description = "AWS region for resources"
}

# Variable with validation
variable "instance_type" {
  type    = string
  default = "t2.micro"

  validation {
    condition     = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type)
    error_message = "Instance type must be t2.micro, t2.small, or t2.medium."
  }
}

# Complex type variable
variable "vpc_config" {
  type = object({
    cidr_block = string
    azs        = list(string)
    private_subnets = list(string)
    public_subnets  = list(string)
  })

  default = {
    cidr_block      = "10.0.0.0/16"
    azs             = ["us-west-2a", "us-west-2b"]
    private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
    public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
  }
}
変数の型
# String variable
variable "environment" {
  type    = string
  default = "development"
}

# Number variable
variable "instance_count" {
  type    = number
  default = 3
}

# Boolean variable
variable "enable_monitoring" {
  type    = bool
  default = true
}

# List variable
variable "availability_zones" {
  type    = list(string)
  default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}

# Map variable
variable "ami_ids" {
  type = map(string)
  default = {
    us-west-2 = "ami-0c55b159cbfafe1f0"
    us-east-1 = "ami-0b69ea66ff7391e80"
  }
}

# Object variable
variable "database_config" {
  type = object({
    engine         = string
    engine_version = string
    instance_class = string
    allocated_storage = number
  })

  default = {
    engine            = "postgres"
    engine_version    = "13.7"
    instance_class    = "db.t3.micro"
    allocated_storage = 20
  }
}
変数の設定
# Command line
terraform apply -var="region=us-east-1" -var="instance_count=5"

# Variable files
terraform apply -var-file="production.tfvars"

# Environment variables
export TF_VAR_region=us-east-1
terraform apply

terraform.tfvars:

region         = "us-west-2"
instance_count = 3
environment    = "production"
出力値

出力を使うと、インフラストラクチャに関する情報を他の設定から利用したり、ユーザーに表示したりできます。

# Simple output
output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "The public IP of the web server"
}

# Output with sensitive data
output "database_password" {
  value     = aws_db_instance.main.password
  sensitive = true
}

# Complex output
output "instance_details" {
  value = {
    id         = aws_instance.web.id
    public_ip  = aws_instance.web.public_ip
    private_ip = aws_instance.web.private_ip
    arn        = aws_instance.web.arn
  }
  description = "Complete instance information"
}

# Output with depends_on
output "vpc_ready" {
  value = "VPC and subnets are ready"
  depends_on = [
    aws_vpc.main,
    aws_subnet.private,
    aws_subnet.public
  ]
}
出力の使用
# Show all outputs
terraform output

# Get specific output value
terraform output instance_ip

# Output in JSON format
terraform output -json

# Use output in scripts
INSTANCE_IP=$(terraform output -raw instance_ip)
echo "Server IP: $INSTANCE_IP"
ローカル値

ローカル値は、モジュール内で再利用する式に名前を割り当てます。

locals {
  common_tags = {
    ManagedBy   = "Terraform"
    Environment = var.environment
    Project     = "MyApp"
  }

  name_prefix = "${var.project_name}-${var.environment}"

  availability_zones = slice(data.aws_availability_zones.available.names, 0, 3)
}

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = merge(
    local.common_tags,
    {
      Name = "${local.name_prefix}-web-server"
    }
  )
}

モジュール

モジュールは、複数のリソースをまとめたコンテナーです。再利用可能なコンポーネントを作成できます。

モジュールの構成
modules/
└── vpc/
    ├── main.tf
    ├── variables.tf
    ├── outputs.tf
    └── README.md
モジュールの作成

modules/vpc/main.tf:

resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = var.enable_dns_hostnames
  enable_dns_support   = var.enable_dns_support

  tags = merge(
    var.tags,
    {
      Name = var.name
    }
  )
}

resource "aws_subnet" "public" {
  count = length(var.public_subnet_cidrs)

  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = merge(
    var.tags,
    {
      Name = "${var.name}-public-${count.index + 1}"
      Type = "public"
    }
  )
}

resource "aws_subnet" "private" {
  count = length(var.private_subnet_cidrs)

  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = merge(
    var.tags,
    {
      Name = "${var.name}-private-${count.index + 1}"
      Type = "private"
    }
  )
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = merge(
    var.tags,
    {
      Name = "${var.name}-igw"
    }
  )
}

modules/vpc/variables.tf:

variable "name" {
  description = "Name prefix for VPC resources"
  type        = string
}

variable "cidr_block" {
  description = "CIDR block for VPC"
  type        = string
}

variable "public_subnet_cidrs" {
  description = "CIDR blocks for public subnets"
  type        = list(string)
}

variable "private_subnet_cidrs" {
  description = "CIDR blocks for private subnets"
  type        = list(string)
}

variable "availability_zones" {
  description = "Availability zones for subnets"
  type        = list(string)
}

variable "enable_dns_hostnames" {
  description = "Enable DNS hostnames in VPC"
  type        = bool
  default     = true
}

variable "enable_dns_support" {
  description = "Enable DNS support in VPC"
  type        = bool
  default     = true
}

variable "tags" {
  description = "Tags to apply to resources"
  type        = map(string)
  default     = {}
}

modules/vpc/outputs.tf:

output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "vpc_cidr" {
  description = "CIDR block of the VPC"
  value       = aws_vpc.main.cidr_block
}

output "public_subnet_ids" {
  description = "IDs of public subnets"
  value       = aws_subnet.public[*].id
}

output "private_subnet_ids" {
  description = "IDs of private subnets"
  value       = aws_subnet.private[*].id
}

output "internet_gateway_id" {
  description = "ID of the internet gateway"
  value       = aws_internet_gateway.main.id
}
モジュールの使用
# Using a module from local path
module "vpc" {
  source = "./modules/vpc"

  cidr_block = "10.0.0.0/16"
  region     = var.region

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

# Using a module from Terraform Registry
module "s3_bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "3.15.0"

  bucket = "my-application-bucket"
  acl    = "private"

  versioning = {
    enabled = true
  }
}

# Using module outputs
resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  subnet_id     = module.vpc.private_subnet_ids[0]

  tags = {
    Name = "App Server"
  }
}

# Module with count
module "web_servers" {
  count  = 3
  source = "./modules/web-server"

  name   = "web-${count.index}"
  subnet = module.vpc.public_subnet_ids[count.index]
}

# Module with for_each
module "environments" {
  for_each = toset(["dev", "staging", "prod"])
  source   = "./modules/environment"

  env_name = each.key
  vpc_cidr = "10.${index(["dev", "staging", "prod"], each.key)}.0.0/16"
}
モジュールソース
# Local path
module "vpc" {
  source = "./modules/vpc"
}

# Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"
}

# GitHub
module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc"
}

# GitHub with specific branch
module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.1.0"
}

# Git
module "vpc" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git"
}

# S3 bucket
module "vpc" {
  source = "s3::https://s3.amazonaws.com/my-bucket/vpc-module.zip"
}
モジュールのバージョン管理
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"  # Any version 5.x
}

module "s3" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = ">= 3.0, < 4.0"  # Between 3.0 and 4.0
}

状態管理

Terraform の状態は、構成を実際のリソースに対応付ける重要なコンポーネントです。

ローカル状態

デフォルトでは、Terraform は状態を terraform.tfstate という名前のファイルにローカルで保存します。

terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}
リモート状態

リモート状態を使用すると、チームでの共同作業が可能になり、セキュリティと信頼性も向上します。

S3 バックエンド
# S3 backend configuration
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

状態ロック用の DynamoDB のセットアップ:

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 Table"
  }
}
Azure バックエンド
terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-rg"
    storage_account_name = "tfstate"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
  }
}
Terraform Cloud バックエンド
terraform {
  cloud {
    organization = "my-organization"

    workspaces {
      name = "production"
    }
  }
}
Consul バックエンド
terraform {
  backend "consul" {
    address = "consul.example.com:8500"
    scheme  = "https"
    path    = "terraform/production"
  }
}
状態コマンド
# List all resources in state
terraform state list

# Show specific resource details
terraform state show aws_instance.example

# Remove resource from state (doesn't destroy)
terraform state rm aws_instance.example

# Move resource to new address
terraform state mv aws_instance.example aws_instance.web_server

# Show current state
terraform show

# Show specific plan file
terraform show tfplan

# Output state in JSON format
terraform show -json > state.json

# Pull remote state
terraform state pull > terraform.tfstate

# Push local state to remote
terraform state push terraform.tfstate
状態のロック

状態のロックにより、状態を破損させる可能性のある同時操作を防止できます。

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}
既存のリソースのインポート
# Import AWS instance
terraform import aws_instance.example i-0abc123def456

# Import with module
terraform import module.network.aws_vpc.main vpc-0123456789abcdef

インポート前に必要な構成:

resource "aws_instance" "example" {
  # Configuration will be populated after import
  # Define the basic structure matching the resource
}
状態の移行
# Initialize with backend
terraform init

# Migrate from local to remote
terraform init -migrate-state

# Backend configuration from CLI
terraform init -backend-config="bucket=my-other-bucket" \
               -backend-config="key=my-state"

ワークスペース

ワークスペースを使用すると、単一の構成の複数のインスタンスを管理できます。

ワークスペースコマンド
# List workspaces
terraform workspace list

# Create new workspace
terraform workspace new production

# Switch to workspace
terraform workspace select staging

# Show current workspace
terraform workspace show

# Delete workspace
terraform workspace delete development
ワークスペースのワークフロー
# Example workflow:
terraform workspace new development
terraform apply  # Creates resources in development workspace

terraform workspace new staging
terraform apply  # Creates separate resources in staging workspace

terraform workspace new production
terraform apply  # Creates separate resources in production workspace
構成でのワークスペースの使用
locals {
  environment = terraform.workspace
}

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = terraform.workspace == "production" ? "t3.large" : "t3.micro"

  tags = {
    Name        = "web-${terraform.workspace}"
    Environment = terraform.workspace
  }
}

# Workspace-specific variables
variable "instance_counts" {
  type = map(number)
  default = {
    development = 1
    staging     = 2
    production  = 5
  }
}

resource "aws_instance" "app" {
  count = var.instance_counts[terraform.workspace]

  ami           = var.ami_id
  instance_type = "t3.micro"

  tags = {
    Name = "app-${terraform.workspace}-${count.index + 1}"
  }
}
ワークスペース間の状態の分離

各ワークスペースには、独自の状態ファイルがあります。

  • terraform.tfstate.d/development/terraform.tfstate
  • terraform.tfstate.d/staging/terraform.tfstate
  • terraform.tfstate.d/production/terraform.tfstate

プロバイダーの構成

プロバイダーは、Terraform がクラウドプラットフォームやその他のサービスと連携できるようにするプラグインです。

単一のプロバイダー
terraform {
  required_version = ">= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region

  default_tags {
    tags = {
      ManagedBy = "Terraform"
      Project   = "MyApp"
    }
  }
}
複数のプロバイダーインスタンス(エイリアス)
provider "aws" {
  alias  = "east"
  region = "us-east-1"
}

provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

resource "aws_instance" "east_server" {
  provider = aws.east

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}

resource "aws_instance" "west_server" {
  provider = aws.west

  ami           = "ami-0123456789abcdef"
  instance_type = "t2.micro"
}
マルチクラウドのセットアップ
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

provider "azurerm" {
  features {}
  subscription_id = var.azure_subscription_id
}

provider "google" {
  project = var.gcp_project_id
  region  = "us-central1"
}

# AWS resources
resource "aws_s3_bucket" "data" {
  bucket = "my-data-bucket"
}

# Azure resources
resource "azurerm_storage_account" "data" {
  name                     = "mydatastorageacct"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = azurerm_resource_group.main.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

# GCP resources
resource "google_storage_bucket" "data" {
  name     = "my-data-bucket-gcp"
  location = "US"
}
モジュール内のプロバイダー構成
# Root module
provider "aws" {
  region = "us-west-2"
}

provider "aws" {
  alias  = "dr"
  region = "us-east-1"
}

module "app" {
  source = "./modules/app"

  providers = {
    aws    = aws
    aws.dr = aws.dr
  }
}

モジュールの構成:

# modules/app/main.tf
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      configuration_aliases = [aws.dr]
    }
  }
}

resource "aws_instance" "primary" {
  provider = aws
  # ...
}

resource "aws_instance" "dr" {
  provider = aws.dr
  # ...
}

高度な機能

動的ブロック

動的ブロックを使用すると、繰り返し使用するネストされたブロックを動的に構築できます。

variable "ingress_rules" {
  type = list(object({
    description = string
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
  }))

  default = [
    {
      description = "HTTP"
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      description = "HTTPS"
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  ]
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      description = ingress.value.description
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
For_Each メタ引数
resource "aws_s3_bucket" "buckets" {
  for_each = toset(["logs", "data", "backups"])

  bucket = "my-app-${each.key}"

  tags = {
    Purpose = each.key
  }
}

# With map
variable "instances" {
  type = map(object({
    ami           = string
    instance_type = string
  }))

  default = {
    web = {
      ami           = "ami-0c55b159cbfafe1f0"
      instance_type = "t3.micro"
    }
    app = {
      ami           = "ami-0c55b159cbfafe1f0"
      instance_type = "t3.small"
    }
  }
}

resource "aws_instance" "servers" {
  for_each = var.instances

  ami           = each.value.ami
  instance_type = each.value.instance_type

  tags = {
    Name = each.key
  }
}
Count メタ引数
resource "aws_instance" "servers" {
  count = 3

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name = "server-${count.index}"
  }
}

# Conditional resource creation
variable "create_db" {
  type    = bool
  default = true
}

resource "aws_db_instance" "database" {
  count = var.create_db ? 1 : 0

  engine            = "postgres"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
}
条件式
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.environment == "production" ? "t3.large" : "t3.micro"

  tags = {
    Name = var.environment == "production" ? "prod-web" : "dev-web"
  }
}

locals {
  instance_count = var.environment == "production" ? 5 : var.environment == "staging" ? 2 : 1
}
関数
locals {
  # String functions
  name_upper = upper(var.name)
  name_lower = lower(var.name)
  name_title = title(var.name)

  # Collection functions
  subnet_count = length(var.subnet_cidrs)
  first_az     = element(var.availability_zones, 0)
  all_azs      = join(",", var.availability_zones)

  # Numeric functions
  min_instances = min(var.instance_count, 10)
  max_instances = max(var.instance_count, 1)

  # Type conversion
  instance_count_string = tostring(var.instance_count)
  az_set                = toset(var.availability_zones)

  # Map functions
  merged_tags = merge(var.common_tags, var.specific_tags)

  # File functions
  user_data = file("${path.module}/scripts/init.sh")
  config    = templatefile("${path.module}/templates/config.tpl", {
    environment = var.environment
    region      = var.region
  })

  # Encoding functions
  encoded = base64encode("secret data")
  decoded = base64decode(var.encoded_data)

  # Date/Time functions
  timestamp = timestamp()

  # IP Network functions
  cidr_subnets = cidrsubnets("10.0.0.0/16", 8, 8, 8, 8)
}
Terraform 関数の実践
# Creating multiple subnets across availability zones
locals {
  subnet_cidrs = cidrsubnets(var.vpc_cidr, 8, 8, 8, 8)
}

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_subnet" "public" {
  count = 3

  vpc_id            = aws_vpc.main.id
  cidr_block        = local.subnet_cidrs[count.index]
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "public-${count.index + 1}"
  }
}

依存関係

暗黙的な依存関係

Terraform は、リソース参照から依存関係を自動的に推測します。

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id  # Implicit dependency
  cidr_block = "10.0.1.0/24"
}

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.public.id  # Implicit dependency
}
明示的な依存関係

依存関係を自動的に推測できない場合は、depends_on を使用します。

resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  depends_on = [aws_db_instance.database]
}

resource "aws_db_instance" "database" {
  engine            = "postgres"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
}
モジュールの依存関係
module "vpc" {
  source = "./modules/vpc"

  cidr_block = "10.0.0.0/16"
}

module "app" {
  source = "./modules/app"

  vpc_id    = module.vpc.vpc_id
  subnet_id = module.vpc.private_subnet_ids[0]

  depends_on = [module.vpc]
}

プロビジョナー

プロビジョナーは、リソースの作成または破棄の一環として、ローカルまたはリモートのマシンでスクリプトを実行するために使用します。

Local-Exec プロビジョナー
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t2.micro"

  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> private_ips.txt"
  }

  provisioner "local-exec" {
    when    = destroy
    command = "echo 'Instance ${self.id} destroyed' >> destroy_log.txt"
  }
}
Remote-Exec プロビジョナー
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t2.micro"
  key_name      = var.key_name

  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file(var.private_key_path)
    host        = self.public_ip
  }

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx",
      "sudo systemctl start nginx",
      "sudo systemctl enable nginx"
    ]
  }
}
File プロビジョナー
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t2.micro"
  key_name      = var.key_name

  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file(var.private_key_path)
    host        = self.public_ip
  }

  provisioner "file" {
    source      = "scripts/init.sh"
    destination = "/tmp/init.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/init.sh",
      "sudo /tmp/init.sh"
    ]
  }
}
プロビジョナーの失敗時の動作
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t2.micro"

  provisioner "local-exec" {
    command     = "./configure.sh"
    on_failure  = continue  # or fail (default)
  }
}

ベストプラクティス

1. 状態管理
  • チームでの共同作業には、常にリモート状態を使用します
  • 同時変更を防ぐため、状態のロックを有効にします
  • 機密データを保護するため、暗号化を有効にします
  • 災害復旧に備えて、状態を定期的にバックアップします
2. コードの構成
project/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   └── production/
├── modules/
│   ├── vpc/
│   ├── compute/
│   └── database/
└── global/
    └── s3/
3. 変数の管理
# Use meaningful descriptions
variable "instance_type" {
  description = "EC2 instance type for web servers"
  type        = string
  default     = "t3.micro"
}

# Use validation
variable "environment" {
  description = "Deployment environment"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment must be dev, staging, or production."
  }
}
4. 命名規則
# Resource naming
resource "aws_instance" "web_server" {  # Use descriptive names
  tags = {
    Name = "${var.project}-${var.environment}-web-${count.index + 1}"
  }
}

# Module naming
module "vpc_production" {
  source = "./modules/vpc"
}
5. DRY 原則
# Use locals for repeated values
locals {
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
    CostCenter  = var.cost_center
  }
}

resource "aws_instance" "web" {
  tags = merge(
    local.common_tags,
    {
      Name = "web-server"
      Role = "webserver"
    }
  )
}
6. セキュリティのベストプラクティス
# Never hardcode credentials
# BAD
provider "aws" {
  access_key = "AKIAIOSFODNN7EXAMPLE"
  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}

# GOOD - Use environment variables or IAM roles
provider "aws" {
  # Credentials from environment or IAM role
}

# Use sensitive flag for outputs
output "database_password" {
  value     = aws_db_instance.main.password
  sensitive = true
}

# Encrypt sensitive data
resource "aws_s3_bucket" "data" {
  bucket = "my-data-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}
7. テスト
# Validate syntax
terraform validate

# Format code
terraform fmt -recursive

# Plan before apply
terraform plan -out=tfplan

# Review plan
terraform show tfplan
8. バージョン管理

.gitignore:

# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude variable files that may contain sensitive data
*.tfvars
*.tfvars.json

# Ignore override files
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# CLI configuration files
.terraformrc
terraform.rc

# Plan files
*.tfplan
9. モジュールのベストプラクティス
# Module versioning
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"  # Pin to major version
}

# Module documentation
# Always include README.md with:
# - Description
# - Requirements
# - Providers
# - Inputs
# - Outputs
# - Example usage
10. リソースのタグ付け
# Consistent tagging strategy
locals {
  required_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
    Owner       = var.owner_email
    CostCenter  = var.cost_center
    Compliance  = var.compliance_level
  }
}

# Use default tags at provider level
provider "aws" {
  region = var.region

  default_tags {
    tags = local.required_tags
  }
}

CI/CD との統合

GitHub Actions

.github/workflows/terraform.yml:

name: Terraform

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  TF_VERSION: 1.5.0

jobs:
  terraform:
    name: Terraform
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}

      - name: Terraform Format
        run: terraform fmt -check -recursive

      - name: Terraform Init
        run: terraform init
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Terraform Validate
        run: terraform validate

      - name: Terraform Plan
        run: terraform plan -no-color
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Terraform Apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply -auto-approve
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GitLab CI

.gitlab-ci.yml:

image:
  name: hashicorp/terraform:1.5.0
  entrypoint: [""]

stages:
  - validate
  - plan
  - apply

before_script:
  - terraform init

validate:
  stage: validate
  script:
    - terraform validate
    - terraform fmt -check -recursive

plan:
  stage: plan
  script:
    - terraform plan -out=tfplan
  artifacts:
    paths:
      - tfplan
    expire_in: 1 week

apply:
  stage: apply
  script:
    - terraform apply -auto-approve tfplan
  dependencies:
    - plan
  only:
    - main
  when: manual
Jenkins パイプライン

Jenkinsfile:

pipeline {
    agent any

    environment {
        TF_VERSION = '1.5.0'
        AWS_CREDENTIALS = credentials('aws-credentials')
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Terraform Init') {
            steps {
                sh 'terraform init'
            }
        }

        stage('Terraform Validate') {
            steps {
                sh 'terraform validate'
                sh 'terraform fmt -check -recursive'
            }
        }

        stage('Terraform Plan') {
            steps {
                sh 'terraform plan -out=tfplan'
            }
        }

        stage('Terraform Apply') {
            when {
                branch 'main'
            }
            steps {
                input message: 'Apply Terraform changes?', ok: 'Apply'
                sh 'terraform apply -auto-approve tfplan'
            }
        }
    }

    post {
        always {
            cleanWs()
        }
    }
}
Azure DevOps

azure-pipelines.yml:

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

variables:
  - name: terraformVersion
    value: '1.5.0'

stages:
  - stage: Validate
    jobs:
      - job: ValidateTerraform
        steps:
          - task: TerraformInstaller@0
            inputs:
              terraformVersion: $(terraformVersion)

          - task: TerraformTaskV2@2
            displayName: 'Terraform Init'
            inputs:
              provider: 'aws'
              command: 'init'
              backendServiceAWS: 'AWS-Connection'

          - task: TerraformTaskV2@2
            displayName: 'Terraform Validate'
            inputs:
              provider: 'aws'
              command: 'validate'

          - script: terraform fmt -check -recursive
            displayName: 'Terraform Format Check'

  - stage: Plan
    dependsOn: Validate
    jobs:
      - job: PlanTerraform
        steps:
          - task: TerraformTaskV2@2
            displayName: 'Terraform Plan'
            inputs:
              provider: 'aws'
              command: 'plan'
              commandOptions: '-out=tfplan'

  - stage: Apply
    dependsOn: Plan
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: ApplyTerraform
        environment: 'production'
        strategy:
          runOnce:
            deploy:
              steps:
                - task: TerraformTaskV2@2
                  displayName: 'Terraform Apply'
                  inputs:
                    provider: 'aws'
                    command: 'apply'
                    commandOptions: '-auto-approve tfplan'
Pre-commit フック

.pre-commit-config.yaml:

repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.81.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_docs
      - id: terraform_tflint
      - id: terraform_tfsec
      - id: terraform_checkov
自動テスト

test/terraform_test.go:

package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestTerraformVPC(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../examples/vpc",

        Vars: map[string]interface{}{
            "cidr_block": "10.0.0.0/16",
            "region":     "us-west-2",
        },
    }

    defer terraform.Destroy(t, terraformOptions)

    terraform.InitAndApply(t, terraformOptions)

    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.NotEmpty(t, vpcId)
}

その他のコマンドとツール

Terraform コンソール
# Start console
terraform console

# Example session:
> aws_instance.example.public_ip
"54.183.22.100"
> var.region
"us-west-2"
> length(aws_instance.example.tags)
3
Terraform グラフ
# Generate graph in DOT format
terraform graph > graph.dot

# Convert to image with Graphviz
terraform graph | dot -Tpng > graph.png

# Generate graph for specific plan
terraform graph -type=plan > plan-graph.dot
Terraform Taint
# Taint a resource (mark for recreation)
terraform taint aws_instance.example

# Untaint a resource
terraform untaint aws_instance.example
Terraform プロバイダー
# List required providers
terraform providers

# Show provider schemas
terraform providers schema -json > schemas.json

# Lock provider versions
terraform providers lock

まとめ

この包括的なガイドでは、Terraform Infrastructure as Code の基本概念から高度なパターン、CI/CD との統合まで、Terraform の重要な側面を取り上げました。これらのプラクティスとパターンに従うことで、あらゆるクラウドプロバイダーにおいて、保守性、拡張性、安全性に優れたインフラストラクチャのデプロイを構築できます。

主なポイント:

  • 再利用可能なコンポーネントにはモジュールを使用する
  • チームでのコラボレーションには、ロック機能を備えたリモートステートを実装する
  • 環境を分離するにはワークスペースを活用する
  • セキュリティのベストプラクティスに従い、認証情報をハードコードしない
  • 自動デプロイのために Terraform を CI/CD パイプラインに統合する
  • 検証、フォーマット、テストのツールを使用する
  • 明確なドキュメントと一貫した命名規則を維持する
  • リソース管理のために適切なタグ付け戦略を実装する

Terraform を使用すると、インフラストラクチャチームは DevOps のプラクティスを取り入れ、コラボレーションを改善し、手作業によるミスを減らし、デプロイのサイクルを短縮しながら、インフラストラクチャへの変更を完全に制御し、可視化できます。

コメント