LESSON 40分

AWSリソースをTerraformで構築

ストーリー

「Terraformの基礎は固まった。ここからは実践だ」

木村先輩がアーキテクチャ図を広げた。

「先月AWS上に手動で構築した3層アーキテクチャを覚えているか? あれをTerraformでコード化する。同じ構成を何度でも 再現可能にするんだ」

「手動だと1時間以上かかりましたよね」

「Terraformなら terraform apply 一発で15分だ。 しかも100%同じ構成が保証される」


プロジェクト構成

infrastructure/
├── environments/
│   ├── staging/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   ├── backend.tf
│   │   └── terraform.tfvars
│   └── production/
│       ├── main.tf
│       ├── variables.tf
│       ├── outputs.tf
│       ├── backend.tf
│       └── terraform.tfvars
├── modules/
│   ├── vpc/
│   ├── ec2/
│   ├── rds/
│   └── s3/
└── .gitignore

.gitignore

# Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfplan
*.tfvars       # 機密情報を含む場合
!terraform.tfvars.example

S3バケットの構築

基本的なS3バケット

hcl
# modules/s3/main.tf
resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name

  tags = var.tags
}

resource "aws_s3_bucket_versioning" "this" {
  bucket = aws_s3_bucket.this.id
  versioning_configuration {
    status = var.enable_versioning ? "Enabled" : "Suspended"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
  bucket = aws_s3_bucket.this.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "this" {
  bucket = aws_s3_bucket.this.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
hcl
# modules/s3/variables.tf
variable "bucket_name" {
  description = "S3バケット名"
  type        = string
}

variable "enable_versioning" {
  description = "バージョニングを有効にするか"
  type        = bool
  default     = true
}

variable "tags" {
  description = "タグ"
  type        = map(string)
  default     = {}
}
hcl
# modules/s3/outputs.tf
output "bucket_id" {
  value = aws_s3_bucket.this.id
}

output "bucket_arn" {
  value = aws_s3_bucket.this.arn
}

EC2インスタンスの構築

hcl
# modules/ec2/main.tf
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

resource "aws_instance" "this" {
  count = var.instance_count

  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = var.instance_type
  subnet_id              = var.subnet_ids[count.index % length(var.subnet_ids)]
  vpc_security_group_ids = var.security_group_ids
  key_name               = var.key_name

  root_block_device {
    volume_size = var.root_volume_size
    volume_type = "gp3"
    encrypted   = true
  }

  user_data = var.user_data

  tags = merge(var.tags, {
    Name = "${var.name_prefix}-${count.index + 1}"
  })
}
hcl
# modules/ec2/variables.tf
variable "name_prefix" {
  description = "インスタンス名のプレフィックス"
  type        = string
}

variable "instance_type" {
  description = "インスタンスタイプ"
  type        = string
  default     = "t3.micro"
}

variable "instance_count" {
  description = "インスタンス数"
  type        = number
  default     = 1
}

variable "subnet_ids" {
  description = "サブネットID"
  type        = list(string)
}

variable "security_group_ids" {
  description = "セキュリティグループID"
  type        = list(string)
}

variable "key_name" {
  description = "SSHキーペア名"
  type        = string
  default     = null
}

variable "root_volume_size" {
  description = "ルートボリュームサイズ(GB)"
  type        = number
  default     = 20
}

variable "user_data" {
  description = "ユーザーデータスクリプト"
  type        = string
  default     = null
}

variable "tags" {
  description = "タグ"
  type        = map(string)
  default     = {}
}

RDSの構築

hcl
# modules/rds/main.tf
resource "aws_db_subnet_group" "this" {
  name       = "${var.name_prefix}-db-subnet"
  subnet_ids = var.subnet_ids

  tags = var.tags
}

resource "aws_db_instance" "this" {
  identifier     = "${var.name_prefix}-db"
  engine         = var.engine
  engine_version = var.engine_version
  instance_class = var.instance_class

  allocated_storage     = var.allocated_storage
  max_allocated_storage = var.max_allocated_storage
  storage_type          = "gp3"
  storage_encrypted     = true

  db_name  = var.database_name
  username = var.master_username
  password = var.master_password

  multi_az               = var.multi_az
  db_subnet_group_name   = aws_db_subnet_group.this.name
  vpc_security_group_ids = var.security_group_ids

  backup_retention_period = var.backup_retention_period
  backup_window           = "03:00-04:00"
  maintenance_window      = "mon:04:00-mon:05:00"

  deletion_protection = var.deletion_protection
  skip_final_snapshot = var.environment != "production"
  final_snapshot_identifier = var.environment == "production" ? "${var.name_prefix}-final-snapshot" : null

  tags = var.tags
}

モジュールの組み立て

hcl
# environments/staging/main.tf
terraform {
  required_version = ">= 1.7.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Environment = var.environment
      ManagedBy   = "terraform"
      Project     = var.project_name
    }
  }
}

# VPC モジュール
module "vpc" {
  source = "../../modules/vpc"

  name       = "${var.project_name}-${var.environment}"
  cidr_block = var.vpc_cidr
  public_subnets  = var.public_subnet_cidrs
  private_subnets = var.private_subnet_cidrs
}

# EC2 モジュール
module "web" {
  source = "../../modules/ec2"

  name_prefix        = "${var.project_name}-${var.environment}-web"
  instance_type      = var.web_instance_type
  instance_count     = var.web_instance_count
  subnet_ids         = module.vpc.public_subnet_ids
  security_group_ids = [module.vpc.web_security_group_id]

  user_data = <<-EOF
    #!/bin/bash
    yum update -y
    yum install -y httpd
    systemctl start httpd
    systemctl enable httpd
  EOF
}

# S3 モジュール
module "assets" {
  source = "../../modules/s3"

  bucket_name       = "${var.project_name}-${var.environment}-assets"
  enable_versioning = true
}

# RDS モジュール
module "database" {
  source = "../../modules/rds"

  name_prefix        = "${var.project_name}-${var.environment}"
  environment        = var.environment
  engine             = "mysql"
  engine_version     = "8.0"
  instance_class     = var.db_instance_class
  allocated_storage  = 20
  database_name      = "app"
  master_username    = "admin"
  master_password    = var.db_password
  multi_az           = var.environment == "production"
  subnet_ids         = module.vpc.private_subnet_ids
  security_group_ids = [module.vpc.db_security_group_id]
  deletion_protection = var.environment == "production"
}

まとめ

ポイント内容
S3バケット + 暗号化 + バージョニング + パブリックアクセスブロック
EC2AMIデータソース + count による複数台構築
RDSサブネットグループ + マルチAZ + バックアップ設定
組み立てモジュールを組み合わせて環境を構築

チェックリスト

  • S3バケットをTerraformで構築し、セキュリティ設定を適用できる
  • EC2インスタンスをモジュール化して複数台構築できる
  • RDSをTerraformで構築し、環境に応じた設定を適用できる
  • モジュールを組み合わせたプロジェクト構成を理解した

次のステップへ

次のセクションでは、VPCネットワーク全体をTerraformでコード化する方法を学びます。


推定読了時間: 40分