前提条件
- 第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タスク(コンテナ)からデータベースに接続する際、以下のフローで認証情報を取得します。
- アプリケーションがSecrets Manager APIを呼び出し
- IAMロールで権限を確認
- 権限があればパスワードを返却
- 取得した認証情報で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サイズまで) |
開発環境でのコスト削減方法
- Multi-AZを無効にする(デフォルト設定)
- 使用しない時間帯はRDSを停止する
- 不要になったら
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の作成とイメージビルド
- ローカル環境での動作確認