Tại sao dùng Managed Database?
Bạn có thể tự cài MySQL trên EC2, nhưng bạn sẽ phải:
- Tự patch OS và database
- Tự setup backup
- Tự cấu hình replication
- Thức đêm khi database down
RDS (Relational Database Service) làm tất cả cho bạn. Bạn chỉ cần focus vào application.
So sánh: Self-managed vs RDS
| Công việc | Self-managed (EC2) | RDS |
|---|---|---|
| Install database | Bạn | AWS |
| OS patching | Bạn | AWS |
| Database patching | Bạn | AWS |
| Backup | Bạn phải setup | Tự động |
| High availability | Bạn phải setup | 1 click |
| Read replicas | Phức tạp | 1 click |
| Monitoring | Tự setup | Tích hợp sẵn |
| Scaling | Manual | Vài click |
Databases được hỗ trợ
RDS hỗ trợ nhiều database engines:
| Engine | Use Case | Notes |
|---|---|---|
| MySQL | General web apps | Phổ biến nhất |
| PostgreSQL | Advanced features | JSON, PostGIS |
| MariaDB | MySQL alternative | Community fork |
| Oracle | Enterprise | License riêng |
| SQL Server | Microsoft stack | Windows apps |
| Aurora | AWS-native | 5x faster MySQL |
Single-AZ vs Multi-AZ
Single-AZ
┌─────────────────────────────────────┐
│ Availability Zone A │
│ ┌─────────────────────────────┐ │
│ │ RDS Instance │ │
│ │ ┌─────────────────┐ │ │
│ │ │ EBS Storage │ │ │
│ │ └─────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
Risk: Nếu AZ down, database down!
Multi-AZ
┌──────────────────┐ ┌──────────────────┐
│ Availability │ │ Availability │
│ Zone A │ │ Zone B │
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ Primary │──┼────┼──│ Standby │ │
│ │ Instance │ │sync│ │ Instance │ │
│ └────────────┘ │ │ └────────────┘ │
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ EBS │ │ │ │ EBS │ │
│ └────────────┘ │ │ └────────────┘ │
└──────────────────┘ └──────────────────┘
Failover: Automatic (60-120 seconds)
Multi-AZ là synchronous replication - data được write đến cả primary và standby trước khi confirm.
Read Replicas
Khác với Multi-AZ (dùng cho HA), Read Replicas dùng để scale reads:
Writes
│
▼
┌──────────────────────────────────────┐
│ Primary │
└──────────────────────────────────────┘
│ async │ async
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Read Replica 1 │ │ Read Replica 2 │
└────────────────┘ └────────────────┘
▲ ▲
│ │
Reads Reads
Use cases:
- Reporting queries (không ảnh hưởng production)
- Read-heavy workloads
- Cross-region replicas cho low latency
Terraform: RDS Instance
Basic Single-AZ
# DB Subnet Group - RDS cần biết deploy vào subnets nào
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db-subnet"
description = "DB subnet group for ${var.project_name}"
subnet_ids = var.private_subnet_ids # Luôn đặt DB trong private subnets!
tags = {
Name = "${var.project_name}-db-subnet"
}
}
# Security Group
resource "aws_security_group" "rds" {
name = "${var.project_name}-rds-sg"
description = "Security group for RDS"
vpc_id = var.vpc_id
# Chỉ cho phép từ app servers
ingress {
from_port = 5432 # PostgreSQL
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
description = "PostgreSQL from app servers"
}
tags = {
Name = "${var.project_name}-rds-sg"
}
}
# RDS Instance
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-db"
# Engine
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.micro" # Free tier eligible
# Storage
allocated_storage = 20
max_allocated_storage = 100 # Auto scaling
storage_type = "gp3"
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
# Database
db_name = var.db_name
username = var.db_username
password = random_password.db.result # Không hardcode!
# Network
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
publicly_accessible = false # Luôn false cho production!
# Backup
backup_retention_period = 7 # days
backup_window = "03:00-04:00" # UTC
maintenance_window = "Mon:04:00-Mon:05:00"
# Monitoring
performance_insights_enabled = true
performance_insights_kms_key_id = aws_kms_key.rds.arn
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
# Other settings
auto_minor_version_upgrade = true
deletion_protection = var.environment == "prod"
skip_final_snapshot = var.environment != "prod"
final_snapshot_identifier = "${var.project_name}-final-snapshot"
tags = {
Name = "${var.project_name}-db"
Environment = var.environment
}
}
# Random password
resource "random_password" "db" {
length = 32
special = true
override_special = "!#$%^&*()-_=+[]{}|:,.<>?"
}
# Store password in Secrets Manager
resource "aws_secretsmanager_secret" "db" {
name = "${var.project_name}/db/credentials"
description = "Database credentials for ${var.project_name}"
kms_key_id = aws_kms_key.secrets.arn
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({
username = var.db_username
password = random_password.db.result
host = aws_db_instance.main.endpoint
port = aws_db_instance.main.port
database = var.db_name
})
}
Multi-AZ
resource "aws_db_instance" "main" {
# ... same as above ...
# Enable Multi-AZ
multi_az = true
# Automatic failover enabled by default
}
With Read Replicas
# Primary instance (phải enable backup cho replicas)
resource "aws_db_instance" "primary" {
identifier = "${var.project_name}-primary"
backup_retention_period = 7 # Required for replicas
# ... other settings ...
}
# Read Replica
resource "aws_db_instance" "replica" {
identifier = "${var.project_name}-replica"
replicate_source_id = aws_db_instance.primary.identifier
# Replica settings
instance_class = "db.t3.micro"
# Replica có thể ở region khác
# availability_zone = "ap-southeast-1b"
# Không cần specify database settings - inherited từ primary
# username, password, db_name sẽ copy từ primary
tags = {
Name = "${var.project_name}-replica"
}
}
Amazon Aurora
Aurora là AWS-native database, tương thích MySQL và PostgreSQL nhưng:
- 5x faster than standard MySQL
- 3x faster than standard PostgreSQL
- Storage auto-scales (10GB - 128TB)
- 6 copies of data across 3 AZs
Aurora Architecture
┌─────────────────────────────────────┐
│ Aurora Cluster │
│ │
┌────────────────┐ │ ┌────────────┐ ┌────────────┐ │
│ Writer │────┼────│ Reader │ │ Reader │ │
│ Instance │ │ │ Instance 1 │ │ Instance 2 │ │
└────────────────┘ │ └────────────┘ └────────────┘ │
│ │ │
│ │ ┌────────────────────────────┐ │
└─────────────┼────│ Shared Storage Layer │ │
│ │ (6 copies across 3 AZs) │ │
│ └────────────────────────────┘ │
└─────────────────────────────────────┘
Terraform: Aurora Cluster
# Aurora Cluster
resource "aws_rds_cluster" "main" {
cluster_identifier = "${var.project_name}-aurora"
engine = "aurora-postgresql"
engine_version = "15.4"
engine_mode = "provisioned" # or "serverless"
database_name = var.db_name
master_username = var.db_username
master_password = random_password.db.result
# Network
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
# Storage encryption
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
# Backup
backup_retention_period = 7
preferred_backup_window = "03:00-04:00"
# Deletion
deletion_protection = var.environment == "prod"
skip_final_snapshot = var.environment != "prod"
final_snapshot_identifier = "${var.project_name}-final"
# Enable Serverless v2 scaling (optional)
serverlessv2_scaling_configuration {
min_capacity = 0.5
max_capacity = 4
}
tags = {
Name = "${var.project_name}-aurora"
}
}
# Writer Instance
resource "aws_rds_cluster_instance" "writer" {
identifier = "${var.project_name}-aurora-writer"
cluster_identifier = aws_rds_cluster.main.id
instance_class = "db.serverless" # For Serverless v2
# instance_class = "db.r6g.large" # For provisioned
engine = aws_rds_cluster.main.engine
engine_version = aws_rds_cluster.main.engine_version
performance_insights_enabled = true
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
tags = {
Name = "${var.project_name}-aurora-writer"
}
}
# Reader Instances
resource "aws_rds_cluster_instance" "readers" {
count = 2
identifier = "${var.project_name}-aurora-reader-${count.index + 1}"
cluster_identifier = aws_rds_cluster.main.id
instance_class = "db.serverless"
engine = aws_rds_cluster.main.engine
engine_version = aws_rds_cluster.main.engine_version
tags = {
Name = "${var.project_name}-aurora-reader-${count.index + 1}"
}
}
Aurora Endpoints
Aurora có nhiều endpoints:
output "cluster_endpoint" {
description = "Writer endpoint - dùng cho writes"
value = aws_rds_cluster.main.endpoint
}
output "reader_endpoint" {
description = "Reader endpoint - load balanced to all readers"
value = aws_rds_cluster.main.reader_endpoint
}
output "instance_endpoints" {
description = "Individual instance endpoints"
value = aws_rds_cluster_instance.readers[*].endpoint
}
Parameter Groups
Customize database settings:
resource "aws_db_parameter_group" "main" {
family = "postgres15"
name = "${var.project_name}-params"
parameter {
name = "log_statement"
value = "all"
}
parameter {
name = "log_min_duration_statement"
value = "1000" # Log queries > 1 second
}
parameter {
name = "max_connections"
value = "200"
}
lifecycle {
create_before_destroy = true
}
}
# Apply to instance
resource "aws_db_instance" "main" {
parameter_group_name = aws_db_parameter_group.main.name
# ...
}
Backup và Restore
Automated Backups
resource "aws_db_instance" "main" {
backup_retention_period = 14 # Keep 14 days
backup_window = "03:00-04:00" # Backup lúc ít traffic
# Point-in-time recovery được enable tự động
}
Manual Snapshots
# CLI
aws rds create-db-snapshot \
--db-instance-identifier my-db \
--db-snapshot-identifier my-snapshot-$(date +%Y%m%d)
# Restore from snapshot
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier my-restored-db \
--db-snapshot-identifier my-snapshot-20250130
Point-in-Time Recovery
# Restore đến một thời điểm cụ thể
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier my-db \
--target-db-instance-identifier my-restored-db \
--restore-time 2025-01-30T10:30:00Z
Monitoring RDS
Performance Insights
resource "aws_db_instance" "main" {
performance_insights_enabled = true
performance_insights_retention_period = 7 # days, up to 731
performance_insights_kms_key_id = aws_kms_key.rds.arn
}
Enhanced Monitoring
resource "aws_iam_role" "rds_monitoring" {
name = "${var.project_name}-rds-monitoring"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "monitoring.rds.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "rds_monitoring" {
role = aws_iam_role.rds_monitoring.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}
resource "aws_db_instance" "main" {
monitoring_interval = 60 # seconds
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
}
Best Practices
1. Không public accessible
resource "aws_db_instance" "main" {
publicly_accessible = false # Luôn false!
}
2. Encrypt everywhere
resource "aws_db_instance" "main" {
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
}
3. Sử dụng Secrets Manager
# Không bao giờ:
password = "MyPassword123!"
# Luôn luôn:
password = random_password.db.result
# Sau đó lưu vào Secrets Manager
4. Multi-AZ cho production
resource "aws_db_instance" "prod" {
multi_az = true
}
5. Theo dõi connections
resource "aws_cloudwatch_metric_alarm" "db_connections" {
alarm_name = "${var.project_name}-db-connections"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "DatabaseConnections"
namespace = "AWS/RDS"
period = 300
statistic = "Average"
threshold = 100
dimensions = {
DBInstanceIdentifier = aws_db_instance.main.identifier
}
alarm_actions = [aws_sns_topic.alerts.arn]
}
Outputs
output "db_endpoint" {
value = aws_db_instance.main.endpoint
}
output "db_port" {
value = aws_db_instance.main.port
}
output "secret_arn" {
description = "Secrets Manager ARN chứa credentials"
value = aws_secretsmanager_secret.db.arn
}
🎉 Chúc mừng! Bạn đã hoàn thành series AWS từ cơ bản đến nâng cao.