【AWSハンズオン】第5回 RDSデータベース構築

【AWSハンズオン】第5回 RDSデータベース構築

前回構築したECSクラスターとIAM設定の基盤の上に、RDS PostgreSQLデータベースを追加します。マネージドデータベースサービスを活用し、高可用性を実現するMulti-AZ構成で構築します。

AWS #AWS#terraform#初学者向け

【AWSハンズオン】第5回 RDSデータベース構築

サムネイル

前回構築したECSクラスターとIAM設定の基盤の上に、RDS PostgreSQLデータベースを追加します。マネージドデータベースサービスを活用し、高可用性を実現するMulti-AZ構成で構築します。

更新日: 8/12/2025

今回作業対象のブランチ

前提条件

  • 第1回から第4回までの構築が完了していること
  • こちらからDockerコンテナでの実行環境が準備できていること

Terraform実行環境の起動

前回と同様に、Dockerコンテナ内で作業を進めます。

# コンテナを起動してbashに入る
docker-compose run --rm terraform

これ以降のコマンドは、すべてこのコンテナ内で実行します。

基礎知識

RDSは、AWSが提供するマネージドリレーショナルデータベースサービスです。

WEBアプリケーション開発者の視点から見ると

ローカル開発では、PostgreSQLをDockerやHomebrewでインストールして使いますが、本番環境では以下の運用が必要になります。

  • データベースのバックアップ
  • セキュリティパッチの適用
  • 障害時の復旧
  • パフォーマンスチューニング

RDSはこれらの運用タスクを自動化してくれるサービスでもあります。

Multi-AZ構成

Multi-AZ(Multiple Availability Zones)は、安定稼働する状態を継続的に稼働させる状態のことです。
高可用性と言われますが、難しい説明が必要な場合はここから。

仕組み

項目 説明
プライマリDB メインのデータベースインスタンス
スタンバイDB 別のAZに自動作成されるレプリカ
同期レプリケーション データの変更が即座にスタンバイDBに反映
自動フェイルオーバー プライマリ障害時に自動でスタンバイに切り替え

Secrets Manager

データベースの認証情報(ユーザー名、パスワード)を安全に管理するサービスです。

なぜ必要か

  • 環境変数やコード内にパスワードを直接記述するのは危険
  • パスワードの定期的なローテーションが可能
  • アプリケーションからAPIで認証情報を取得

今回作成するネットワーク構成

リソース 説明 数量
RDS Instance PostgreSQL 15.x 1個
DB Subnet Group RDS用のサブネットグループ 1個
Secrets Manager Secret DB認証情報 1個
DB Parameter Group PostgreSQL設定 1個

Terraformコードの実装

変数の追加

RDS用の変数を追加します。

# terraform/variables.tf に追加

variable "db_instance_class" {
  description = "RDSインスタンスクラス"
  type        = string
  default     = "db.t3.micro"  # 開発環境用の最小構成
}

variable "db_allocated_storage" {
  description = "RDSストレージサイズ(GB)"
  type        = number
  default     = 20
}

variable "db_engine_version" {
  description = "PostgreSQLのバージョン"
  type        = string
  default     = "15.8"
}

variable "db_username" {
  description = "マスターユーザー名"
  type        = string
  default     = "postgres"
}

variable "enable_db_multi_az" {
  description = "Multi-AZを有効にするかどうか"
  type        = bool
  default     = false  # 開発環境ではコスト削減のためfalse
}

DB Subnet Group

RDSインスタンスを配置するサブネットグループを作成します。

# terraform/rds.tf

# DB Subnet Group
resource "aws_db_subnet_group" "main" {
  name       = "${var.project_name}-${var.environment}-db-subnet-group"
  subnet_ids = aws_subnet.private[*].id

  tags = {
    Name = "${var.project_name}-${var.environment}-db-subnet-group"
  }
}

なぜDB Subnet Groupが必要か

RDSは単一のサブネットには配置できず、必ず複数のサブネットにまたがるDB Subnet Groupが必要です。これはAWSの仕様によるもので、以下の理由があります。

理由 説明
高可用性に対応する 障害時に別AZのサブネットでDBを起動できるよう準備
Multi-AZ対応 スタンバイDBを別AZに配置するための要件
メンテナンス時の稼働 パッチ適用時に別AZで新インスタンスを起動してから切り替え

プライベートサブネットを使用することで、インターネットから直接アクセスできない安全な環境にデータベースを配置します。

Secrets Manager

データベースのパスワードを安全に管理します。

# terraform/rds.tf

# ランダムパスワードの生成
resource "random_password" "db_password" {
  length  = 32
  special = true
  # RDSで使用できない文字を除外
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

# Secrets Manager Secret
resource "aws_secretsmanager_secret" "db_credentials" {
  name = "${var.project_name}-${var.environment}-db-credentials"

  # 削除時の復旧期間を0日に設定(即座に削除)
  # これはハンズオンで毎回destroyするため
  recovery_window_in_days = 0

  tags = {
    Name = "${var.project_name}-${var.environment}-db-credentials"
  }
}

# Secret Version
resource "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = aws_secretsmanager_secret.db_credentials.id
  secret_string = jsonencode({
    username = var.db_username
    password = random_password.db_password.result
    engine   = "postgres"
    host     = aws_db_instance.main.address
    port     = aws_db_instance.main.port
    dbname   = "${var.project_name}_${var.environment}_db"
  })
}

なぜSecrets Managerで管理するのか

データベースのパスワード管理には以下の課題があり、Secrets Managerによって解決させようということです。

課題 Secrets Managerだと
ハードコーディングの危険性 コードとは別にAWS内で安全に保管
環境変数の漏洩リスク IAMロールベースのアクセス制御
パスワード変更の手間 自動ローテーション機能(本番環境で活用)
監査ログの不足 すべてのアクセスがCloudTrailに記録

パスワード生成の設定説明

設定項目 説明
length 32 十分なセキュリティ強度を確保
special true 特殊文字を含める
override_special “!#$%&*()-_=+[]{}<>:?” RDSで問題となる文字(@など)を除外

RDSインスタンス

PostgreSQLデータベースインスタンスを作成します。

# terraform/rds.tf

# DB Parameter Group
resource "aws_db_parameter_group" "main" {
  name   = "${var.project_name}-${var.environment}-pg15"
  family = "postgres15"

  parameter {
    name  = "shared_preload_libraries"
    value = "pg_stat_statements"
  }

  parameter {
    name  = "log_statement"
    value = "all"
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-pg15"
  }
}

# RDS Instance
resource "aws_db_instance" "main" {
  identifier = "${var.project_name}-${var.environment}-db"

  # エンジン設定
  engine         = "postgres"
  engine_version = var.db_engine_version

  # インスタンス設定
  instance_class        = var.db_instance_class
  allocated_storage     = var.db_allocated_storage
  max_allocated_storage = var.db_allocated_storage * 2  # 自動スケーリング上限

  # データベース設定
  db_name  = replace("${var.project_name}_${var.environment}_db", "-", "_")
  username = var.db_username
  password = random_password.db_password.result

  # ネットワーク設定
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  publicly_accessible    = false

  # 高可用性設定
  multi_az = var.enable_db_multi_az

  # バックアップ設定
  backup_retention_period = 7
  backup_window          = "03:00-04:00"  # UTC
  maintenance_window     = "sun:04:00-sun:05:00"  # UTC

  # パラメータグループ
  parameter_group_name = aws_db_parameter_group.main.name

  # その他の設定
  skip_final_snapshot       = true  # 開発環境では削除時のスナップショットをスキップ
  deletion_protection       = false # 開発環境では削除保護を無効化
  enabled_cloudwatch_logs_exports = ["postgresql"]

  tags = {
    Name = "${var.project_name}-${var.environment}-db"
  }
}

なぜDB Parameter Groupが必要か

DB Parameter Groupは、データベースエンジンの動作を細かく制御するための設定です。デフォルトのパラメータグループは変更できないため、カスタマイズには独自のParameter Groupが必要です。

設定の目的 理由
パフォーマンス分析 pg_stat_statementsでSQLの実行統計を収集
デバッグ支援 開発環境では全SQLをログ出力して問題を追跡
チューニング アプリケーションに合わせたメモリ設定などを調整可能

DB Parameter Groupの設定説明

パラメータ なぜ必要か
shared_preload_libraries pg_stat_statements スロークエリの特定やパフォーマンス改善に必須
log_statement all 開発環境でSQLの実行内容を確認(本番では’none’推奨)

RDSインスタンスの主要設定説明

カテゴリ 設定項目 なぜこの設定にするのか
データベース名 db_name PostgreSQLはハイフンを許可しないため、アンダースコアに変換
ストレージ max_allocated_storage データ増加に自動対応し、手動拡張の手間を削減
バックアップ backup_window 日本の一般的な営業時間外(深夜3-4時)に設定
メンテナンス maintenance_window 週末の影響が少ない時間帯に設定
セキュリティ publicly_accessible = false データベースへの直接的な攻撃を防ぐ
ログ出力 enabled_cloudwatch_logs_exports トラブルシューティングに必要なログを収集

IAMロールの更新

ECS Task RoleにSecrets Managerへのアクセス権限を追加します。

# terraform/ecs.tf のECS Task Role用ポリシーを更新

resource "aws_iam_role_policy" "ecs_task_role_policy" {
  name = "${var.project_name}-${var.environment}-ecs-task-policy"
  role = aws_iam_role.ecs_task_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Resource = "${aws_cloudwatch_log_group.ecs.arn}:*"
      },
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = aws_secretsmanager_secret.db_credentials.arn
      }
    ]
  })
}

なぜIAMロールの更新が必要か

ECSタスク(コンテナ)からデータベースに接続する際、以下のフローで認証情報を取得します。

  1. アプリケーションがSecrets Manager APIを呼び出し
  2. IAMロールで権限を確認
  3. 権限があればパスワードを返却
  4. 取得した認証情報でRDSに接続
追加する権限 理由
secretsmanager:GetSecretValue アプリがDB接続情報を取得するため
特定のSecretのみに限定 最小権限の原則に従い、必要なSecretのみアクセス可能に

この仕組みで、ソースコードにパスワードを書く必要がなくなります。

出力の追加

作成したRDSリソースの情報をterraform/outputs.tfに追加します。

# terraform/outputs.tf(既存の出力に追加)

# RDS関連の出力
output "rds_endpoint" {
  description = "RDS エンドポイント"
  value       = aws_db_instance.main.endpoint
}

output "rds_address" {
  description = "RDS アドレス(ホスト名)"
  value       = aws_db_instance.main.address
}

output "rds_port" {
  description = "RDS ポート番号"
  value       = aws_db_instance.main.port
}

output "rds_database_name" {
  description = "RDS データベース名"
  value       = aws_db_instance.main.db_name
}

output "db_subnet_group_id" {
  description = "DB Subnet Group ID"
  value       = aws_db_subnet_group.main.id
}

output "db_secret_arn" {
  description = "Secrets Manager Secret ARN"
  value       = aws_secretsmanager_secret.db_credentials.arn
}

output "db_secret_name" {
  description = "Secrets Manager Secret Name"
  value       = aws_secretsmanager_secret.db_credentials.name
}

リソースを作成

Dockerコンテナ内で以下のコマンドを実行します。

# terraformディレクトリへ移動
cd terraform

# 実行計画の確認
terraform plan

# リソースの作成
terraform apply

RDSインスタンスの作成には10〜15分程度かかります。作成中は以下のコマンドで進捗を確認できます。

# RDSインスタンスの状態確認
aws rds describe-db-instances \
  --db-instance-identifier $(terraform output -raw rds_endpoint | cut -d: -f1) \
  --query 'DBInstances[0].DBInstanceStatus' \
  --output text

ステータスがavailableになれば作成完了です。

作成結果の確認

# RDS エンドポイントの確認
terraform output rds_endpoint

# Secrets Manager Secret ARNの確認
terraform output db_secret_arn

# データベース名の確認
terraform output rds_database_name

AWS Management Consoleでの確認

RDS > データベース

作成されたRDSインスタンスを選択し、以下を確認します。

カテゴリ 項目 設定値
基本設定 エンジン PostgreSQL
インスタンスクラス db.t3.micro
ストレージ 20 GB (gp2)
接続とセキュリティ VPC 作成したVPC
サブネット プライベートサブネット
セキュリティグループ RDS用Security Group
パブリックアクセス なし
可用性 Multi-AZ なし(開発環境)
バックアップ 保持期間 7日間
バックアップウィンドウ 03:00-04:00 UTC

Secrets Manager > シークレット

作成されたシークレットを選択し、以下を確認します。

  • シークレット名: {project_name}-{environment}-db-credentials
  • シークレットの値を取得してキー/値の形式で認証情報が保存されていることを確認

データベース接続確認

ECSタスクからの接続は次回実装しますが、ここではSecrets Managerから認証情報を取得する方法を確認します。

# Secrets Managerから認証情報を取得
aws secretsmanager get-secret-value \
  --secret-id $(terraform output -raw db_secret_name) \
  --query 'SecretString' \
  --output text | jq .

出力例

{
  "dbname": "theblueback-aws_dev_db",
  "engine": "postgres",
  "host": "theblueback-aws-dev-db.xxxx.ap-northeast-1.rds.amazonaws.com",
  "password": "xxxxxxxxxxxxxx",
  "port": 5432,
  "username": "postgres"
}

コスト管理

RDSは常時起動している間、料金が発生します。

リソース 料金
db.t3.micro (Single-AZ) 約$0.017/時間
db.t3.micro (Multi-AZ) 約$0.034/時間
ストレージ (gp2) $0.115/GB・月
バックアップストレージ 無料(DBサイズまで)

開発環境でのコスト削減方法

  1. Multi-AZを無効にする(デフォルト設定)
  2. 使用しない時間帯はRDSを停止する
  3. 不要になったらterraform destroyでリソースを削除

RDS設定のベストプラクティス

パフォーマンス設定

開発環境では最小構成ですが、本番環境では以下を検討します。

設定項目 開発環境 本番環境推奨
インスタンスクラス db.t3.micro db.m6i.large以上
ストレージタイプ gp2 gp3またはio1
IOPS - 3000以上
Multi-AZ 無効 有効

セキュリティ設定

  • VPC内のプライベートサブネットに配置
  • Security Groupで必要最小限のアクセスのみ許可
  • 認証情報はSecrets Managerで管理
  • 暗号化を有効化(本番環境)

次のステップ

次回は、Honoを使用したAPIを作成し、今回構築したRDSデータベースに接続します。

  • Honoを使用したAPIアプリケーション作成
  • TypeScriptでのCRUD操作実装
  • Dockerfileの作成とイメージビルド
  • ローカル環境での動作確認

検索

検索条件に一致する記事が見つかりませんでした