RDS và Aurora: Managed Databases cho người mới

Từ single database đến Multi-AZ, Read Replicas. So sánh RDS và Aurora, backup strategies với Terraform.

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ệcSelf-managed (EC2)RDS
Install databaseBạnAWS
OS patchingBạnAWS
Database patchingBạnAWS
BackupBạn phải setupTự động
High availabilityBạn phải setup1 click
Read replicasPhức tạp1 click
MonitoringTự setupTích hợp sẵn
ScalingManualVài click

Databases được hỗ trợ

RDS hỗ trợ nhiều database engines:

EngineUse CaseNotes
MySQLGeneral web appsPhổ biến nhất
PostgreSQLAdvanced featuresJSON, PostGIS
MariaDBMySQL alternativeCommunity fork
OracleEnterpriseLicense riêng
SQL ServerMicrosoft stackWindows apps
AuroraAWS-native5x 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.