Terraform

DevOpsInfrastructure as CodeIaCHCLマルチクラウド宣言的ツール

DevOpsツール

Terraform

概要

TerraformはHashiCorpが開発するオープンソースのInfrastructure as Code(IaC)ツールです。HCL(HashiCorp Configuration Language)という宣言的な設定言語を使用し、AWS、Azure、Google Cloud、Kubernetesなど複数のクラウドプロバイダーやサービスでインフラリソースを統一的に管理できます。

詳細

Terraformは2014年にHashiCorpによって開発され、現在IaC分野で最も広く使用されているツールです(市場シェア32.8%)。HCL(HashiCorp Configuration Language)と呼ばれる独自の宣言的言語を使用し、コードでインフラを定義します。1,200以上のプロバイダーが利用可能で、AWS、Azure、GCP、Kubernetes、DatadogからGitHubまで、あらゆるサービスを統一的に管理できます。

Terraformの主な特徴は、「宣言的」なアプローチです。「何を作りたいか」を記述すると、Terraformが現在の状態と比較して必要な変更を自動的に計画・実行します。ステート管理機能により、実際のインフラとコードの同期を保ち、チームでの協業を支援します。

プランニング機能により、実際に変更を適用する前に「何が変更されるか」を確認でき、安全なインフラ運用が可能です。また、豊富なモジュールエコシステムにより、再利用可能なインフラパターンを簡単に利用できます。

メリット・デメリット

メリット

  • マルチクラウド対応: 1つのツールで複数のクラウドプロバイダーを統一管理
  • 豊富なプロバイダー: 1,200以上のプロバイダーで幅広いサービスに対応
  • プランニング機能: 変更前に影響を確認できるため安全
  • 大規模なコミュニティ: 豊富なモジュールとドキュメント
  • 状態管理: インフラの現在状態を追跡し、設定と実際の差分を管理

デメリット

  • 学習コスト: HCLの習得が必要で、初心者には敷居が高い
  • 状態ファイル管理: ステートファイルの管理が複雑で、チーム開発時に課題
  • プロバイダー依存: プロバイダーの更新遅延により最新機能が使えない場合
  • ロールバック制限: 設定のロールバックが自動化されていない

参考ページ

公式リソース

学習リソース

書き方の例

基本的なリソース定義

# provider.tf - プロバイダー設定
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.0"
}

provider "aws" {
  region = var.aws_region
}

# variables.tf - 変数定義
variable "aws_region" {
  description = "AWS region for resources"
  type        = string
  default     = "ap-northeast-1"
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "development"
}

VPCとサブネット作成

# vpc.tf - VPCとネットワーク設定
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
  }
}

resource "aws_subnet" "public" {
  count = 2

  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.${count.index + 1}.0/24"
  
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.environment}-public-subnet-${count.index + 1}"
    Type = "Public"
  }
}

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

  tags = {
    Name = "${var.environment}-igw"
  }
}

# データソース - AZ一覧取得
data "aws_availability_zones" "available" {
  state = "available"
}

EC2インスタンス作成

# ec2.tf - EC2インスタンス定義
resource "aws_security_group" "web" {
  name_prefix = "${var.environment}-web-"
  vpc_id      = aws_vpc.main.id

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

  ingress {
    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 = "${var.environment}-web-sg"
  }
}

resource "aws_instance" "web" {
  count = 2

  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public[count.index].id
  
  vpc_security_group_ids = [aws_security_group.web.id]
  
  user_data = base64encode(<<-EOF
    #!/bin/bash
    apt update
    apt install -y nginx
    systemctl start nginx
    systemctl enable nginx
    echo "<h1>Web Server ${count.index + 1}</h1>" > /var/www/html/index.html
  EOF
  )

  tags = {
    Name = "${var.environment}-web-${count.index + 1}"
  }
}

# 最新のUbuntu AMI取得
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

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

ロードバランサー設定

# alb.tf - Application Load Balancer
resource "aws_lb" "main" {
  name               = "${var.environment}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = aws_subnet.public[*].id

  tags = {
    Environment = var.environment
  }
}

resource "aws_security_group" "alb" {
  name_prefix = "${var.environment}-alb-"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    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"]
  }
}

resource "aws_lb_target_group" "web" {
  name     = "${var.environment}-web-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    enabled             = true
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 5
    interval            = 30
    path                = "/"
    matcher             = "200"
  }
}

resource "aws_lb_target_group_attachment" "web" {
  count = length(aws_instance.web)

  target_group_arn = aws_lb_target_group.web.arn
  target_id        = aws_instance.web[count.index].id
  port             = 80
}

resource "aws_lb_listener" "web" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.web.arn
  }
}

状態管理(リモートバックエンド)

# backend.tf - S3バックエンド設定
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "infrastructure/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

# S3バケットとDynamoDBテーブル作成
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-terraform-state-bucket"
}

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_dynamodb_table" "terraform_state_lock" {
  name           = "terraform-state-lock"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

モジュール作成と利用

# modules/vpc/main.tf - VPCモジュール
variable "environment" {
  description = "Environment name"
  type        = string
}

variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
}

resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
  }
}

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

output "vpc_cidr_block" {
  description = "CIDR block of the VPC"
  value       = aws_vpc.this.cidr_block
}

# main.tf - モジュール利用
module "vpc" {
  source = "./modules/vpc"

  environment = var.environment
  vpc_cidr    = "10.0.0.0/16"
}

module "production_vpc" {
  source = "./modules/vpc"

  environment = "production"
  vpc_cidr    = "10.1.0.0/16"
}

Terraform基本コマンド

# 初期化(プロバイダーダウンロード)
terraform init

# フォーマット(コード整形)
terraform fmt

# 検証(構文チェック)
terraform validate

# プラン(変更内容確認)
terraform plan

# 適用(インフラ作成・更新)
terraform apply

# 特定リソースのみ適用
terraform apply -target=aws_instance.web

# 破棄(リソース削除)
terraform destroy

# 状態確認
terraform show

# リソース一覧
terraform state list

# 特定リソース詳細表示
terraform state show aws_instance.web

# インポート(既存リソースをTerraform管理に追加)
terraform import aws_instance.web i-1234567890abcdef0

条件分岐とループ

# 条件分岐の例
resource "aws_instance" "conditional" {
  count = var.create_instance ? 1 : 0

  ami           = data.aws_ami.ubuntu.id
  instance_type = var.environment == "production" ? "t3.medium" : "t3.micro"
}

# for_eachを使ったリソース作成
variable "users" {
  type = map(object({
    groups = list(string)
  }))
  default = {
    "alice" = {
      groups = ["developers", "admins"]
    }
    "bob" = {
      groups = ["developers"]
    }
  }
}

resource "aws_iam_user" "users" {
  for_each = var.users
  name     = each.key
}

resource "aws_iam_user_group_membership" "users" {
  for_each = var.users

  user   = aws_iam_user.users[each.key].name
  groups = each.value.groups
}

基本的なワークフロー

1. 初期設定

# 新しいプロジェクト作成
mkdir terraform-project && cd terraform-project

# 設定ファイル作成
cat > main.tf << EOF
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}
EOF

# 初期化
terraform init

2. 開発サイクル

# 1. コード作成・編集
vim resources.tf

# 2. フォーマット
terraform fmt

# 3. 検証
terraform validate

# 4. プラン確認
terraform plan

# 5. 適用
terraform apply

# 6. 状態確認
terraform show

3. チーム開発

# リモートバックエンド初期化
terraform init -backend-config=backend.conf

# ワークスペース管理
terraform workspace new production
terraform workspace select development
terraform workspace list

# 状態のロック確認
terraform force-unlock LOCK_ID