Deploying AWS RDS Instances Using Terraform

A comprehensive guide to creating and managing Amazon RDS databases with Terraform, including security, backups, and monitoring

Deploying AWS RDS Instances Using Terraform

Amazon RDS (Relational Database Service) provides managed database services for various database engines. This guide demonstrates how to deploy and manage RDS instances using Terraform, incorporating security best practices and operational considerations.

Video Tutorial

Learn more about managing AWS RDS with Terraform in this comprehensive video tutorial:

View Source Code

Prerequisites

  • AWS CLI installed and configured
  • Terraform installed (version 1.0.0 or later)
  • Basic understanding of RDS and database concepts
  • Text editor of your choice

Project Structure

rds-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars

Setting Up the Infrastructure

Create main.tf:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# VPC for RDS
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

# Private subnets for RDS
resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "${var.project_name}-private-${count.index + 1}"
  }
}

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

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

# Security group for RDS
resource "aws_security_group" "rds" {
  name        = "${var.project_name}-rds-sg"
  description = "Security group for RDS instance"
  vpc_id      = aws_vpc.main.id

  ingress {
    description     = "Database access from application"
    from_port       = var.db_port
    to_port         = var.db_port
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }

  tags = {
    Name = "${var.project_name}-rds-sg"
  }
}

# KMS key for encryption
resource "aws_kms_key" "rds" {
  description             = "KMS key for RDS encryption"
  deletion_window_in_days = 7
  enable_key_rotation     = true

  tags = {
    Name = "${var.project_name}-rds-key"
  }
}

# Parameter group
resource "aws_db_parameter_group" "main" {
  name   = "${var.project_name}-pg"
  family = var.parameter_group_family

  parameter {
    name  = "character_set_server"
    value = "utf8"
  }

  parameter {
    name  = "character_set_client"
    value = "utf8"
  }
}

# Option group
resource "aws_db_option_group" "main" {
  name                 = "${var.project_name}-og"
  engine_name          = var.engine
  major_engine_version = var.engine_version

  option {
    option_name = "MARIADB_AUDIT_PLUGIN"
  }
}

# RDS instance
resource "aws_db_instance" "main" {
  identifier        = "${var.project_name}-db"
  engine            = var.engine
  engine_version    = var.engine_version
  instance_class    = var.instance_class
  allocated_storage = var.allocated_storage
  storage_type      = "gp3"

  db_name  = var.database_name
  username = var.database_username
  password = var.database_password

  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  parameter_group_name   = aws_db_parameter_group.main.name
  option_group_name      = aws_db_option_group.main.name

  multi_az               = var.multi_az
  publicly_accessible    = false
  skip_final_snapshot    = false
  copy_tags_to_snapshot  = true
  deletion_protection    = true

  backup_retention_period = 7
  backup_window          = "03:00-04:00"
  maintenance_window     = "Mon:04:00-Mon:05:00"

  storage_encrypted = true
  kms_key_id       = aws_kms_key.rds.arn

  performance_insights_enabled    = true
  performance_insights_retention_period = 7

  monitoring_interval = 60
  monitoring_role_arn = aws_iam_role.rds_monitoring.arn

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

# Enhanced monitoring role
resource "aws_iam_role" "rds_monitoring" {
  name = "${var.project_name}-rds-monitoring"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "monitoring.rds.amazonaws.com"
        }
      }
    ]
  })
}

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"
}

Variables Configuration

Create variables.tf:

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "project_name" {
  description = "Name of the project"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "vpc_cidr" {
  description = "CIDR block for VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "private_subnet_cidrs" {
  description = "CIDR blocks for private subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "engine" {
  description = "Database engine"
  type        = string
  default     = "mysql"
}

variable "engine_version" {
  description = "Database engine version"
  type        = string
  default     = "8.0"
}

variable "instance_class" {
  description = "RDS instance class"
  type        = string
  default     = "db.t3.medium"
}

variable "allocated_storage" {
  description = "Allocated storage in GB"
  type        = number
  default     = 20
}

variable "database_name" {
  description = "Name of the database"
  type        = string
}

variable "database_username" {
  description = "Database master username"
  type        = string
}

variable "database_password" {
  description = "Database master password"
  type        = string
  sensitive   = true
}

variable "multi_az" {
  description = "Enable Multi-AZ deployment"
  type        = bool
  default     = false
}

variable "parameter_group_family" {
  description = "Database parameter group family"
  type        = string
  default     = "mysql8.0"
}

variable "db_port" {
  description = "Database port"
  type        = number
  default     = 3306
}

Output Configuration

Create outputs.tf:

output "db_endpoint" {
  description = "The connection endpoint for the database"
  value       = aws_db_instance.main.endpoint
}

output "db_name" {
  description = "The name of the database"
  value       = aws_db_instance.main.db_name
}

output "db_username" {
  description = "The master username for the database"
  value       = aws_db_instance.main.username
}

output "db_port" {
  description = "The port the database is listening on"
  value       = aws_db_instance.main.port
}

Best Practices

  1. Security

    • Use private subnets
    • Enable encryption at rest
    • Implement proper security groups
    • Use strong passwords
  2. High Availability

    • Consider Multi-AZ deployment
    • Regular backups
    • Automated snapshots
  3. Monitoring

    • Enable enhanced monitoring
    • Use performance insights
    • Set up CloudWatch alarms

Deployment Steps

  1. Initialize Terraform:
terraform init
  1. Create terraform.tfvars:
aws_region        = "us-west-2"
project_name      = "my-app"
environment       = "prod"
database_name     = "myappdb"
database_username = "admin"
database_password = "your-secure-password"
multi_az         = true
  1. Review the plan:
terraform plan
  1. Apply the configuration:
terraform apply

Security Considerations

  1. Network Security

    • Use private subnets
    • Restrict security group access
    • Enable encryption in transit
  2. Access Control

    • Use IAM authentication
    • Implement least privilege
    • Regular password rotation
  3. Encryption

    • Enable encryption at rest
    • Use customer-managed KMS keys
    • Secure parameter storage

Monitoring and Maintenance

  1. CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "database_cpu" {
  alarm_name          = "${var.project_name}-db-cpu-utilization"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "2"
  metric_name        = "CPUUtilization"
  namespace          = "AWS/RDS"
  period             = "300"
  statistic          = "Average"
  threshold          = "80"
  alarm_description  = "This metric monitors database CPU utilization"
  alarm_actions      = [var.sns_topic_arn]

  dimensions = {
    DBInstanceIdentifier = aws_db_instance.main.id
  }
}
  1. Backup Strategy

    • Enable automated backups
    • Configure backup retention
    • Test restore procedures
  2. Performance Optimization

    • Monitor slow queries
    • Optimize parameter groups
    • Regular maintenance updates

Conclusion

You’ve learned how to deploy and manage RDS instances using Terraform. This approach ensures:

  • Consistent database deployments
  • Secure configuration
  • Automated provisioning
  • Proper monitoring and maintenance

Remember to:

  • Follow security best practices
  • Implement proper monitoring
  • Regular backups and testing
  • Keep configurations up to date

Cleanup

To remove the RDS instance and associated resources:

terraform destroy

Note: Ensure you have backups before destruction.