Deploying AWS Aurora with Terraform

A comprehensive guide to setting up Amazon Aurora databases using Terraform Infrastructure as Code

Deploying AWS Aurora with Terraform

Amazon Aurora is a fully managed relational database engine that’s compatible with MySQL and PostgreSQL. This guide shows how to set up Aurora using Terraform.

Prerequisites

  • AWS CLI configured
  • Terraform installed
  • VPC and subnets configured
  • Basic understanding of relational databases

Project Structure

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

Basic Aurora Configuration

# main.tf
provider "aws" {
  region = var.aws_region
}

# Aurora Cluster
resource "aws_rds_cluster" "main" {
  cluster_identifier     = "${var.project_name}-cluster"
  engine                = "aurora-postgresql"
  engine_version        = "14.6"
  database_name         = var.database_name
  master_username       = var.master_username
  master_password       = var.master_password
  backup_retention_period = 7
  preferred_backup_window = "03:00-04:00"
  skip_final_snapshot    = true

  vpc_security_group_ids = [aws_security_group.aurora.id]
  db_subnet_group_name   = aws_db_subnet_group.aurora.name

  enabled_cloudwatch_logs_exports = ["postgresql"]

  tags = {
    Environment = var.environment
  }
}

# Aurora Instances
resource "aws_rds_cluster_instance" "instances" {
  count               = var.instance_count
  identifier          = "${var.project_name}-instance-${count.index + 1}"
  cluster_identifier  = aws_rds_cluster.main.id
  instance_class      = var.instance_class
  engine             = aws_rds_cluster.main.engine
  engine_version     = aws_rds_cluster.main.engine_version

  db_subnet_group_name = aws_db_subnet_group.aurora.name

  monitoring_interval = 30
  monitoring_role_arn = aws_iam_role.rds_enhanced_monitoring.arn

  tags = {
    Environment = var.environment
  }
}

# Subnet Group
resource "aws_db_subnet_group" "aurora" {
  name       = "${var.project_name}-subnet-group"
  subnet_ids = var.subnet_ids

  tags = {
    Environment = var.environment
  }
}

# Security Group
resource "aws_security_group" "aurora" {
  name        = "${var.project_name}-aurora-sg"
  description = "Security group for Aurora cluster"
  vpc_id      = var.vpc_id

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = var.allowed_security_group_ids
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Environment = var.environment
  }
}

Enhanced Monitoring Configuration

# IAM Role for Enhanced Monitoring
resource "aws_iam_role" "rds_enhanced_monitoring" {
  name = "${var.project_name}-rds-monitoring-role"

  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_enhanced_monitoring" {
  role       = aws_iam_role.rds_enhanced_monitoring.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}

Parameter Group Configuration

# DB Cluster Parameter Group
resource "aws_rds_cluster_parameter_group" "main" {
  family = "aurora-postgresql14"
  name   = "${var.project_name}-cluster-params"

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

  parameter {
    name  = "log_min_duration_statement"
    value = "1000"
  }
}

# DB Instance Parameter Group
resource "aws_db_parameter_group" "main" {
  family = "aurora-postgresql14"
  name   = "${var.project_name}-instance-params"

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

  parameter {
    name  = "pg_stat_statements.track"
    value = "ALL"
  }
}

Variables Configuration

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

variable "project_name" {
  description = "Project name"
  type        = string
}

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

variable "vpc_id" {
  description = "VPC ID"
  type        = string
}

variable "subnet_ids" {
  description = "List of subnet IDs"
  type        = list(string)
}

variable "instance_count" {
  description = "Number of cluster instances"
  type        = number
  default     = 2
}

variable "instance_class" {
  description = "Instance class"
  type        = string
  default     = "db.r5.large"
}

variable "database_name" {
  description = "Database name"
  type        = string
}

variable "master_username" {
  description = "Master username"
  type        = string
}

variable "master_password" {
  description = "Master password"
  type        = string
  sensitive   = true
}

Best Practices

  1. High Availability

    • Deploy across multiple AZs
    • Use appropriate instance count
    • Configure automatic failover
    • Implement proper backup strategy
  2. Security

    • Use encryption at rest
    • Implement proper security groups
    • Use IAM authentication
    • Regular password rotation
  3. Performance

    • Choose appropriate instance size
    • Monitor performance metrics
    • Use parameter groups effectively
    • Implement connection pooling
  4. Cost Optimization

    • Use appropriate instance types
    • Scale instances as needed
    • Monitor storage usage
    • Use Aurora Serverless when appropriate

Serverless Configuration

resource "aws_rds_cluster" "serverless" {
  cluster_identifier   = "${var.project_name}-serverless"
  engine              = "aurora-postgresql"
  engine_version      = "14.6"
  engine_mode         = "provisioned"
  database_name       = var.database_name
  master_username     = var.master_username
  master_password     = var.master_password

  serverlessv2_scaling_configuration {
    min_capacity = 0.5
    max_capacity = 16
  }

  vpc_security_group_ids = [aws_security_group.aurora.id]
  db_subnet_group_name   = aws_db_subnet_group.aurora.name
}

Monitoring Configuration

# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "cpu_utilization" {
  alarm_name          = "${var.project_name}-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 Aurora CPU utilization"
  alarm_actions      = [var.sns_topic_arn]

  dimensions = {
    DBClusterIdentifier = aws_rds_cluster.main.cluster_identifier
  }
}

resource "aws_cloudwatch_metric_alarm" "free_storage" {
  alarm_name          = "${var.project_name}-free-storage"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "1"
  metric_name         = "FreeLocalStorage"
  namespace           = "AWS/RDS"
  period             = "300"
  statistic          = "Average"
  threshold          = "10000000000" # 10GB in bytes
  alarm_description  = "This metric monitors free storage space"
  alarm_actions      = [var.sns_topic_arn]

  dimensions = {
    DBClusterIdentifier = aws_rds_cluster.main.cluster_identifier
  }
}

Deployment Steps

  1. Initialize Terraform:
terraform init
  1. Plan the deployment:
terraform plan
  1. Apply the configuration:
terraform apply

Clean Up

Remove all resources when done:

terraform destroy

Common Use Cases

  1. Global Database
resource "aws_rds_global_cluster" "global" {
  global_cluster_identifier = "${var.project_name}-global"
  engine                   = "aurora-postgresql"
  engine_version           = "14.6"
  database_name            = var.database_name
}

resource "aws_rds_cluster" "primary" {
  cluster_identifier     = "${var.project_name}-primary"
  engine                = "aurora-postgresql"
  engine_version        = "14.6"
  global_cluster_identifier = aws_rds_global_cluster.global.id
  # ... other configuration
}

resource "aws_rds_cluster" "secondary" {
  provider              = aws.secondary_region
  cluster_identifier    = "${var.project_name}-secondary"
  engine               = "aurora-postgresql"
  engine_version       = "14.6"
  global_cluster_identifier = aws_rds_global_cluster.global.id
  # ... other configuration
}
  1. Custom Backup Strategy
resource "aws_rds_cluster" "backup" {
  # ... other configuration

  backup_retention_period = 14
  preferred_backup_window = "03:00-04:00"
  preferred_maintenance_window = "mon:04:00-mon:05:00"

  copy_tags_to_snapshot = true
  
  deletion_protection = true

  enabled_cloudwatch_logs_exports = ["postgresql"]
}

Conclusion

This setup provides a comprehensive foundation for deploying Aurora using Terraform. Remember to:

  • Plan your database architecture carefully
  • Implement proper security measures
  • Monitor performance and costs
  • Keep your configurations versioned
  • Test thoroughly before production deployment

The complete code can be customized based on your specific requirements and use cases.