Production ready AWS MySQL RDS instance using terraform

Using Terraform for AWS RDS implementation is not new. It is a well defined process with required resources already defined by Hashicorp and AWS. But one pitfall associated with IAC as a whole is the possibility that some of the production implementation considerations that might have been covered when provisioning manually might not be highlighted when the infrastructure is initiated using terraform. Idea of this post is to list down such points and provide sample terraform script for production ready AWS Mysql rds. 

1. Configure backups, maintenance windows, version upgrade preference. Make sure to include variables "rds_backupWindow" , variable "rds_maintenanceWindow", variable "rds_backupRetentionPeriod". Ensure time windows are set as per the timezone setting of AWS data center in use. Refer 02-rds-variables.tf. 

 2. Use separate file for parameter management. This should include all rds parameters such as db instance name, size and also the database level parameters such as max_connections etc. Refer variable "rds_parameters" in file 02-rds-variables.tf below. Some of these parameters can cause db instance restart based on configuration 

 3. Use secret manager service to generate and store the RDS instance database user parameter. Do not hard code the database privilege user password in your TF file. Instead use AWS secret manager service as generate a random password. refer 01-rds-sec.tf. 

 4. Subnet groups Create subnet group according to availability group settings. If multi_az enabled then create the subnet group including the subnets of two zones. 

 5. Security groups Create the security group with least required privileges in mind. Make sure to allow db port access only from the subnets where application will connect to the particular rds instance.

Included below is a simple set of terraform scripts the address above listed considerations.
01-rds-sec.tf
resource "random_password" "master"{
  length           = 16
  special          = true
  override_special = "_!%^"
}

resource "aws_secretsmanager_secret" "password" {
  name = "ctl-db-password"
}

resource "aws_secretsmanager_secret_version" "password" {
  secret_id = aws_secretsmanager_secret.password.id
  secret_string = random_password.master.result
}
02-rds-variables.tf
variable "rds_name" {
  description = "AWS rds name"
  type        = string
  default     = "ctl-cplane-database"
}

variable "rds_instanceType" {
  description = "The instance type"
  default     = "db.m5.xlarge"
}

variable "rds_subnetGroupName" {
  description = "AWS rds subnet group name"
  type        = string
  default     = "ctl-db-subnet-group"
}

variable "rds_parameterGroupName" {
  description = "The name of the DB parameter group"
  type        = string
  default     = "mysql-parameters"
}

variable "rds_storage" {
  description = "Allocated storage size (in GB)"
  default     = 500
}

variable "rds_backupRetentionPeriod" {
  description = "The number of days to retain automated backups"
  default     = 7
}

variable "rds_backupWindow" {
  description = "The daily time range during which automated backups are created"
  default     = "00:30-02:00"
}

variable "rds_maintenanceWindow" {
  description = "The weekly time range during which system maintenance can occur"
  default     = "sun:02:00-sun:03:00"
}

variable "rds_parameters" {
  description = "Map of custom MySQL parameters"
  type        = map
  default     = {
    "max_connections" = "750"
    "wait_timeout"    = "900"
    "max_connect_errors" = "100"
  }
}
03-rds-sg.tf
data "aws_subnet" "ctl-cplane-core-subnet-01a" {
  filter {
    name   = "cidrBlock"
    values = ["10.96.4.96/27"]
  }
}

data "aws_subnet" "ctl-cplane-core-subnet-01b" {
  filter {
    name   = "cidrBlock"
    values = ["10.96.4.128/27"]
  }
}

data "aws_subnet" "ctl-cplane-core-subnet-01c" {
  filter {
    name   = "cidrBlock"
    values = ["10.96.4.160/27"]
  }
}

resource "aws_security_group" "rds_sg" {
  name        = "${var.rds_name}-sg"
  description = "Security group for rds cluster"
  vpc_id      = var.vpc_id

   // Inbound rules
   ingress {
     from_port   = 3306 
     to_port     = 3306 
     protocol    = "tcp"
     cidr_blocks = [data.aws_subnet.ctl-cplane-core-subnet-01a.cidr_block,data.aws_subnet.ctl-cplane-core-subnet-01b.cidr_block,data.aws_subnet.ctl-cplane-core-subnet-01c.cidr_block]
     description = "Allow inbound DB Traffic from application subnets"
   }

#   // Outbound rules 
#   egress {
#     from_port   = 0
#     to_port     = 0
#     protocol    = "-1"
#     cidr_blocks = ["0.0.0.0/0"]
#     description = "Allow all outbound traffic"
#   }

  tags = {
    Name = "${var.rds_name}-sg"
  }
}
04-rds.tf
resource "aws_db_subnet_group" "dbgroup" {
  name       = var.rds_subnetGroupName
  subnet_ids = var.rds_subnets
}

resource "aws_db_parameter_group" "mysql" {
  name        = var.rds_parameterGroupName
  family      = "mysql8.0"
  description = "Custom MySQL parameters"

  dynamic "parameter" {
    for_each = var.rds_parameters
    content {
      name  = parameter.key
      value = parameter.value
    }
  }
}

data "aws_secretsmanager_secret_version" "password" {
  secret_id = aws_secretsmanager_secret.password.id
}

resource "aws_db_instance" "ctl-cplane-database" {
  identifier              = var.rds_name
  allocated_storage       = var.rds_storage
  storage_type            = "gp2"
  engine                  = "mysql"
  engine_version          = "8.0.33"
  instance_class          = var.rds_instanceType
  username                = "ctl_user"
  password                = data.aws_secretsmanager_secret_version.password.secret_string 
  db_subnet_group_name    = aws_db_subnet_group.dbgroup.name
  parameter_group_name    = aws_db_parameter_group.mysql.name
  publicly_accessible     = false
  skip_final_snapshot     = false
  final_snapshot_identifier = "before-Delete-snap-ctl-coreRDS"
  vpc_security_group_ids  = [aws_security_group.rds_sg.id]
  multi_az                = true
  backup_retention_period = var.rds_backupRetentionPeriod
  backup_window           = var.rds_backupWindow
  maintenance_window      = var.rds_maintenanceWindow
  apply_immediately       = true
  tags = {
    Name = "ctl-cplane-databases"
  }
}

Comments

Popular posts from this blog

ORA-16433: The database or pluggable database must be opened in read/write

Oracle Multitenant - Create new service for PDB using DBMS_SERVICE

Wait for unread message on broadcast channel - Blocking Sessions