LAB05_AWS_RDS MySQL Instance with Parameter Group and Random Password

RDS MySQL Instance with Parameter Group and Random Password

☁ AWS ⭐ Beginner RDS MySQL Parameter Groups Sensitive Variables random_password

Scenario

The development team needs a MySQL database for their application. It should be placed in a private subnet, use a securely-generated random password (never a hardcoded one), and have performance tuning applied through a custom parameter group.

Provision the database so it is ready to connect from within the VPC, with all configuration tracked in Terraform state — not in someone's notes.

Your Objectives
  • Use random_password to generate a secure 20-character DB password.
  • Create an aws_db_subnet_group using two private subnets (in different AZs).
  • Create a custom aws_db_parameter_group for MySQL 8.0 with slow_query_log enabled.
  • Create an aws_db_instance: MySQL 8.0, db.t3.micro, 20 GB gp3, in the subnet group, with deletion protection enabled.
  • Mark the password output as sensitive = true so it does not appear in logs.

Additional Context

The aws_db_subnet_group requires at least two subnets in different Availability Zones — even for a Single-AZ deployment. This is an AWS requirement. Ensure you have two private subnets in different AZs (e.g., us-east-1a and us-east-1b) before applying this configuration.

Use skip_final_snapshot = true in development to allow easy teardown. In production you would set this to false and provide a final_snapshot_identifier.

versions.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws    = { source = "hashicorp/aws",    version = "~> 5.0" }
    random = { source = "hashicorp/random", version = "~> 3.5" }
  }
}

variables.tf

variable "aws_region"   { type = string; default = "us-east-1" }
variable "vpc_id"       { type = string; description = "VPC to deploy the DB into." }
variable "subnet_ids"   { type = list(string); description = "List of at least 2 private subnet IDs in different AZs." }
variable "db_name"      { type = string; default = "appdb" }
variable "db_username"  { type = string; default = "dbadmin" }
variable "environment"  { type = string; default = "dev" }

main.tf

# main.tf

provider "aws" {
  region = var.aws_region
}

# Generate a strong random password — never hardcode DB passwords.
# The result is stored in Terraform state. Use AWS Secrets Manager in production
# if you need automatic rotation of database credentials.
resource "random_password" "db" {
  length           = 20
  special          = true
  override_special = "!#$%^&*()-_=+[]{}"  # exclude characters that confuse some MySQL drivers
}

# DB Subnet Group — tells RDS which subnets it can use.
# Requires subnets in at least 2 different AZs (AWS requirement).
resource "aws_db_subnet_group" "main" {
  name       = "app-db-subnet-group-${var.environment}"
  subnet_ids = var.subnet_ids
  description = "Private subnets for the application database"

  tags = { Environment = var.environment, ManagedBy = "terraform" }
}

# Custom Parameter Group — tune MySQL settings.
# Parameter groups are versioned by engine family, e.g., mysql8.0.
resource "aws_db_parameter_group" "mysql8" {
  name   = "app-mysql8-params-${var.environment}"
  family = "mysql8.0"
  description = "Custom MySQL 8.0 parameters for the application DB"

  # Enable slow query log to identify queries taking longer than 1 second.
  parameter {
    name  = "slow_query_log"
    value = "1"
  }

  parameter {
    name  = "long_query_time"
    value = "1"         # log queries slower than 1 second
  }

  tags = { Environment = var.environment, ManagedBy = "terraform" }
}

# Security group — only allow MySQL traffic from within the VPC.
resource "aws_security_group" "rds" {
  name        = "rds-sg-${var.environment}"
  description = "Allow MySQL from within the VPC"
  vpc_id      = var.vpc_id

  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"]  # VPC CIDR — only internal traffic
    description = "MySQL from VPC"
  }

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

  tags = { Name = "rds-sg", Environment = var.environment }
}

# RDS MySQL instance.
resource "aws_db_instance" "app" {
  identifier        = "app-db-${var.environment}"
  engine            = "mysql"
  engine_version    = "8.0"
  instance_class    = "db.t3.micro"

  db_name  = var.db_name
  username = var.db_username
  password = random_password.db.result  # from the random_password resource

  # Storage configuration
  allocated_storage     = 20
  storage_type          = "gp3"
  storage_encrypted     = true          # always encrypt at rest

  # Network configuration
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  publicly_accessible    = false        # private — only reachable within VPC

  # Tuning and compliance
  parameter_group_name = aws_db_parameter_group.mysql8.name
  multi_az             = false          # Single-AZ for dev; set true for prod

  # Maintenance and lifecycle
  backup_retention_period = 7           # keep 7 days of automated backups
  skip_final_snapshot     = true        # dev only — set false in production
  deletion_protection     = false       # set true in production

  tags = { Environment = var.environment, ManagedBy = "terraform" }
}

outputs.tf

output "db_endpoint" {
  description = "Connection endpoint (host:port) for the database."
  value       = aws_db_instance.app.endpoint
}

output "db_password" {
  description = "Generated database password. Store this in a password manager."
  value       = random_password.db.result
  sensitive   = true   # hides the value in plan/apply output and logs
}

Workflow Commands

terraform init
terraform plan -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-aaa","subnet-bbb"]'
terraform apply -var-file="dev.tfvars"

# Read the sensitive password output (won't show in apply but works here)
terraform output -raw db_password

# Test connectivity from an EC2 instance in the same VPC
mysql -h $(terraform output -raw db_endpoint | cut -d: -f1) \
      -u dbadmin -p appdb

terraform destroy -var-file="dev.tfvars"
✓ Expected Output After Apply
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

db_endpoint = "app-db-dev.c9abcdefg.us-east-1.rds.amazonaws.com:3306"
db_password = <sensitive>