06. RDS / DynamoDB
AWS のデータベース。ざっくりは 「リレーショナル=RDS」「NoSQL=DynamoDB」。それぞれ Terraform でどう書くか、最低限のセキュリティ設定込みで見ます。
この章の目次
どっちを使う?
| RDS | DynamoDB | |
|---|---|---|
| データモデル | 表(リレーショナル) | キー・値(NoSQL) |
| クエリ | SQL(柔軟) | キー指定 / GSI(限定的) |
| 料金 | インスタンス常時稼働 | 従量(リクエスト数 + ストレージ) |
| 運用 | スケール手動/停止可 | サーバ管理ゼロ |
| 得意 | 複雑なクエリ、トランザクション | セッション / TTL / 高 QPS |
RDS 最小例(PostgreSQL)
resource "aws_db_subnet_group" "main" {
name = "main"
subnet_ids = [for s in aws_subnet.private : s.id]
}
resource "aws_security_group" "rds" {
name_prefix = "rds-"
vpc_id = aws_vpc.main.id
}
resource "aws_vpc_security_group_ingress_rule" "rds_from_app" {
security_group_id = aws_security_group.rds.id
referenced_security_group_id = aws_security_group.app.id
from_port = 5432
to_port = 5432
ip_protocol = "tcp"
}
resource "aws_db_instance" "main" {
identifier = "myapp-prd"
engine = "postgres"
engine_version = "16.4"
instance_class = "db.t4g.micro"
allocated_storage = 20
storage_type = "gp3"
storage_encrypted = true
db_name = "myapp"
username = "postgres"
password = var.db_password # 後で Secrets Manager に置き換え
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
publicly_accessible = false
skip_final_snapshot = false
final_snapshot_identifier = "myapp-prd-final"
}
本番向け RDS の必須設定
resource "aws_db_instance" "prd" {
# ... 基本項目は上と同じ ...
multi_az = true # 別 AZ にスタンバイ
backup_retention_period = 30 # バックアップ 30 日保持
backup_window = "16:00-17:00" # JST 1:00-2:00 想定
maintenance_window = "Sun:17:00-Sun:18:00"
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
performance_insights_enabled = true
performance_insights_retention_period = 7
enabled_cloudwatch_logs_exports = ["postgresql"]
deletion_protection = true # destroy がエラーに
copy_tags_to_snapshot = true
lifecycle {
prevent_destroy = true # Terraform 側でも保護
ignore_changes = [password] # 外部で変える運用に
}
}
本番 DB の保護
deletion_protection = true(AWS 側)と lifecycle.prevent_destroy = true(Terraform 側)の二重保護が定石。terraform destroy で一発消去事故を防ぎます。
パスワードを Secrets Manager で管理
resource "random_password" "db" {
length = 32
special = true
}
resource "aws_secretsmanager_secret" "db" {
name = "myapp/db/master"
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({
username = "postgres"
password = random_password.db.result
host = aws_db_instance.main.address
port = aws_db_instance.main.port
})
}
resource "aws_db_instance" "main" {
# ...
username = "postgres"
password = random_password.db.result
}
アプリは aws_secretsmanager_secret_version から接続情報を取得します。Terraform 経由でパスワードを生成すれば、.tfvars に書く必要すらありません。
DynamoDB
テーブル定義のキモは 「key(hash_key・range_key)」と「attribute」の宣言。attribute は「key で使う列の型を予告」するだけで、他の列は自由に書き込めます(スキーマレス)。
resource "aws_dynamodb_table" "sessions" {
name = "sessions"
billing_mode = "PAY_PER_REQUEST" # 従量制(PROVISIONED ではなく)
hash_key = "session_id"
attribute {
name = "session_id"
type = "S" # String
}
ttl {
attribute_name = "expires_at"
enabled = true
}
point_in_time_recovery {
enabled = true
}
server_side_encryption {
enabled = true
}
tags = { Name = "sessions" }
}
billing_mode の選び方
| PAY_PER_REQUEST | PROVISIONED | |
|---|---|---|
| 料金 | リクエストごと | 容量予約 + Auto Scaling |
| 突発負荷 | 強い | 容量超過でスロットリング |
| 低負荷時のコスト | ほぼ 0 | 予約分は常に発生 |
| 初学者向け | こちら推奨 | 定常負荷が読めてから |
GSI(グローバルセカンダリインデックス)
主キー以外で検索したい時に追加するインデックス。
resource "aws_dynamodb_table" "users" {
name = "users"
billing_mode = "PAY_PER_REQUEST"
hash_key = "user_id"
attribute {
name = "user_id"
type = "S"
}
attribute {
name = "email"
type = "S"
}
global_secondary_index {
name = "by-email"
hash_key = "email"
projection_type = "ALL"
}
}
# 検索: aws dynamodb query --table-name users --index-name by-email \
# --key-condition-expression "email = :e" \
# --expression-attribute-values '{":e":{"S":"alice@example.com"}}'
tip
DynamoDB は「アクセスパターンを先に決めてからスキーマを設計する」のが鉄則。RDS のように「あとで JOIN すればいい」とはいきません。クエリ要件 → key と GSI、の順で考える。