基礎LESSON 05
この教材で学ぶこと インフラを宣言的に管理する基本を学びます。Terraform — IaC でインフラをコード管理
前提知識: クラウドの基本。本教材ではインフラをコードで表現する感覚を身につけます。
1. なぜ Terraform か
| 手段 | 再現性 | ドリフト検知 | マルチクラウド |
|---|---|---|---|
| マネコン手動 | ❌ | ❌ | - |
| AWS CLI / SDK | △ | ❌ | △ |
| CloudFormation | ✅ | △ | ❌ AWS のみ |
| Terraform | ✅ | ✅ | ✅ |
Desired State 思想: 「あるべき状態」を宣言 → Terraform が差分を計算して適用(Kubernetes の Deployment と同じ考え方)
2. 基本的な使い方
terraform init # プロバイダーのダウンロード(初回・provider変更時)
terraform plan # 差分確認(実際には何も変えない)
terraform apply # 適用(plan の内容を実行)
terraform destroy # 全リソースを削除
terraform fmt # コードフォーマット
terraform validate # 構文チェック
terraform show # 現在のstateを確認
3. 基本構文(HCL)
# main.tf
# プロバイダー設定
terraform {
required_version = ">= 1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # 5.x 系を使用
}
}
# 本番ではstateをS3に保存
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
dynamodb_table = "terraform-lock" # ステートロック
}
}
provider "aws" {
region = "ap-northeast-1"
default_tags { # 全リソースにタグを付ける
tags = {
ManagedBy = "Terraform"
Environment = var.environment
}
}
}
4. 変数・出力・データソース
# variables.tf
variable "environment" {
description = "デプロイ環境"
type = string
validation {
condition = contains(["prod", "stg", "dev"], var.environment)
error_message = "environment は prod/stg/dev のいずれか"
}
}
variable "instance_type" {
type = string
default = "t3.medium"
}
# outputs.tf
output "alb_dns_name" {
description = "ALBのDNS名"
value = aws_lb.main.dns_name
}
output "rds_endpoint" {
description = "RDSエンドポイント(機密なのでsensitive)"
value = aws_db_instance.main.endpoint
sensitive = true # terraform output では表示されない
}
# data.tf — 既存リソースの参照
data "aws_vpc" "existing" {
tags = {
Name = "existing-vpc"
}
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
5. 実践的な構成例(EC2 + ALB + RDS)
# ec2.tf
resource "aws_instance" "app" {
count = var.environment == "prod" ? 2 : 1 # 環境で数を変える
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = element(aws_subnet.private[*].id, count.index)
vpc_security_group_ids = [aws_security_group.app.id]
iam_instance_profile = aws_iam_instance_profile.app.name
user_data = base64encode(templatefile("${path.module}/templates/userdata.sh.tpl", {
db_host = aws_db_instance.main.endpoint
app_env = var.environment
}))
tags = {
Name = "${var.environment}-app-${count.index + 1}"
}
}
# セキュリティグループ
resource "aws_security_group" "app" {
name = "${var.environment}-app-sg"
vpc_id = data.aws_vpc.existing.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.alb.id] # ALBからのみ
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# RDS
resource "aws_db_instance" "main" {
identifier = "${var.environment}-db"
engine = "postgres"
engine_version = "16.1"
instance_class = var.environment == "prod" ? "db.t3.medium" : "db.t3.micro"
allocated_storage = 20
db_name = "myapp"
username = "admin"
password = random_password.db.result # random プロバイダーで生成
multi_az = var.environment == "prod" # 本番のみMulti-AZ
skip_final_snapshot = var.environment != "prod"
deletion_protection = var.environment == "prod"
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.main.name
}
resource "random_password" "db" {
length = 32
special = false
}
# パスワードを Secrets Manager に保存
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({
username = aws_db_instance.main.username
password = random_password.db.result
host = aws_db_instance.main.endpoint
})
}
6. モジュール化
# modules/vpc/main.tf に VPC の定義を切り出す
# 呼び出し側
module "vpc" {
source = "./modules/vpc"
environment = var.environment
cidr_block = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
public_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]
}
# 出力の参照
resource "aws_instance" "app" {
subnet_id = module.vpc.private_subnet_ids[0]
}
7. CI/CD での Terraform 運用
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths: ['terraform/**']
push:
branches: [main]
paths: ['terraform/**']
jobs:
plan:
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -out=tfplan
- name: PRにplan結果をコメント
uses: actions/github-script@v7
# ... plan出力をPRにコメント
apply:
if: github.ref == 'refs/heads/main'
steps:
- run: terraform apply -auto-approve tfplan
🔗 次のステップ
- 02_Terraform応用.md — Workspace・Atlantis・Terragrunt
- ../CICD/01_GitHub_Actions.md — CI/CD パイプラインへの組み込み
- ../../03_Cloud/AWS/01_SAA以上の設計.md — Well-Architected な設計を Terraform で実装