Create an RDS instance in terraform with a Mariadb Example

In this guide, we will explore provisioning an AWS RDS instance using Terraform. This concept is known as Infrastructure as code, i.e. provisioning infrastructure resources in as code.

# Prerequisites

  • AWS IaM user Access Key and Secret Key with permissions to create an RDS
  • Terraform installed and added to path
  • AWS Cli installed

# Terraform

Terraform is an open source tool created by HashiCorp that allow you to define resources as code. Users define and provide data center infrastructure using a declarative configuration language known as HashiCorp Configuration Language, or optionally JSON.
Terraform suppport multiple clouds such as AWS, GCP, Azure, Digital Ocean, Alibaba, IBM Cloud, etc.

# RDS

RDS is a relational database.

Amazon Relational Database Service is a distributed relational database service by Amazon Web Services. It is a web service running “in the cloud” designed to simplify the setup, operation, and scaling of a relational database for use in applications.
Amazon RDS provides DB engine types such as Amazon Aurora, PostgreSQL, MySQL, MariaDB, Oracle Database, and SQL Server.

# Installing Terraform

Terraform is available as a binary for most distributions – Linux, Windows, Mac and BSD. It can be downladed as a binary path and added to path. The simplest way to install it is by downloading the package for your os in the downloads page here https://www.terraform.io/downloads.html.

More information on installation can be found on this page https://learn.hashicorp.com/tutorials/terraform/install-cli.

Once terraform is installed and added to path, confirm that its working as expected by checking its version as shown below:

➜ terraform --version

Terraform v1.0.6
on linux_amd64

# Installing awscli

The awscli is a command line utility that allows you to manage aws resources. It is a written in python and is available as a PyPI. We can install it using pip like in this command:

sudo pip install awscli

Confirm that its working by checking its version:

➜ aws --version

aws-cli/2.2.11 Python/3.8.8 Linux/4.18.0-305.12.1.el8_4.x86_64 exe/x86_64.centos.8 prompt/off

# Configuring awscli profile

We can configure an AWS profile to be used for authentication. An AWS profile allows AWS access either from the cli or from terraform. An AWS profile contains the AWS Access Key, The AWS access Secret and a default region.

To create a profile, use the aws configure command and enter the key id, access key and the region.

Example:

➜ aws configure
AWS Access Key ID [None]: key-here
AWS Secret Access Key [None]: secret-here
Default region name [None]: us-east-1
Default output format [None]: json

If you have another profile defined in ~/.aws/config, you can use its credentials using:

export AWS_PROFILE=my-creds

# Alternatively

If you do not want to create a default profile, you can use the environment variables to set the credentials. Export them like in this example

export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_DEFAULT_REGION=us-west-2

# Terraform code

The terraform code consist of the following: Provider definitions, variable definitions, data difinations, resources definitions and outout definitions.

# Variables

Input variables serve as parameters for a Terraform module, allowing aspects of the module to be customized without altering the module’s own source code, and allowing modules to be shared between different configurations.
We can pass the access key, secret key, and the region of AWS as variables so we don’t have to hard code into the file.

This is out variable definitionas

variable "aws_access_key" {
    type = "string"
}

variable "aws_secret_key" {
    type = "string"
}

# Provider

A Terraform Provider represents an integration that is responsible for understanding API interactions with the underlying infrastructure, such as a public cloud service (AWS, GCP, Azure), a PaaS service (Heroku), a SaaS service (DNSimple, CloudFlare), or on-prem resources (vSphere).

A plugin will be installed using terraform to communicate with the respective providers.

This is our definition of AWS provider.

provider "aws" {
    access_key = var.aws_access_key
    secret_key = var.aws_secret_key
    region = "us-west-2"
}

# Data

A data block requests that Terraform read from a given data source (“aws_vpc”) and export the result under the given local name (“our_vpc”). The name is used to refer to this resource from elsewhere in the same Terraform module, but has no significance outside of the scope of a module.

The RDS instance we launch will reside in subnets inside a vpc. To query the VPC id given the vpc ID and subnets inside it, use this code:

data "aws_vpc" "our_vpc" {
    id = "vpc-xxxxxxx"
}

data "aws_subnet_ids" "subnet_ids" {
    vpc_id = data.aws_vpc.our_vpc.id
    tags = {
        Name = "private-*"
    }
}

# Resources

Now that we have initialized our code with variables, providers and data, let’s now define resources that will create the AWS resources required.

# DB Subnet group

An RDS Subnet Group is a collection of subnets that you can use to designate for your RDS database instance in a VPC. The database within your VPC will use the Subnet Group and the preferred Availability Zone to select a subnet and an IP address within that subnet.

Let us define a subnet group for our RDS:

resource "aws_db_subnet_group" "prod_mariadb" {
    name = "prod-mariadb"
    subnet_ids = data.aws_subnet_ids.subnet_ids.ids
}

# DB Instance

This the database definition. This will launch the AWS RDS instance with te given values:

resource "aws_db_instance" "the_db" {
    engine = "mariadb"
    engine_version = "10.5"
    instance_class = "db.t3.medium"
    name = "prodmariadb"
    identifier = "prod-mariadb"
    username = "root"
    password = "<some-secure-password>"
    parameter_group_name = "default.mariadb10.5"
    db_subnet_group_name = aws_db_subnet_group.prod_mariadb.name
    vpc_security_group_ids = [aws_security_group.allow_mariadb.id]
    skip_final_snapshot = true
    allocated_storage = 50
    max_allocated_storage = 1000
}
  • name – default database name
  • identifier – A unique name for the DB Instance
  • engine_version – DB version to use

# Security Group

A security group acts as a virtual firewall for your rds instance to control inbound and outbound traffic.

Let us define one for our instance allowing only traffic within the VPC:

resource "aws_security_group" "allow_mariadb" {
    name = "prod-mariadb-rds-sg"
    description = "Allow TLS inbound traffic"
    vpc_id = data.aws_vpc.our_vpc.id

    ingress {
        description = "Mariadb Access from within the VPC"
        from_port = 3306
        to_port = 3306
        protocol = "tcp"
        cidr_blocks = ["10.0.0.0/8"]
    }

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

    tags = {
        Name = "Allow access to prod mariadb rds"
    }
}

# Outputs

Terraform output values allow you to export structured data about your resources. You can use this data to configure other parts of your infrastructure with automation tools, or as a data source for another Terraform workspace. Outputs are also necessary to share data from a child module to your root module.

Let us define outputs to print out our RDS instance.

output "this_db_name" {
    value = aws_db_instance.the_db.name
}

output "this_db_instance_address" {
    value = aws_db_instance.the_db.address
}

output "this_db_instance_arn" {
    value = aws_db_instance.the_db.arn
}

output "this_db_instance_domain" {
    value = aws_db_instance.the_db.domain
}

output "this_db_instance_endpoint" {
    value = aws_db_instance.the_db.endpoint
}

output "this_db_instance_status" {
    value = aws_db_instance.the_db.status
}

# Launch instance from snapshot

If we want to launch the instance from another instance’s snapshot, we can query that instance snapshot then pass snapshot_identifier when launching.

Query for latest snapshot for rds instance named prod-db-01:

data "aws_db_snapshot" "prod_snapshot" {
    most_recent = true
    db_instance_identifier = "prod-db-01"
}

Then use it to provision new instance:

resource "aws_db_instance" "the_db" {
    engine = "mariadb"
    engine_version = "10.5"
    instance_class = "db.t3.medium"
    identifier = "prod-mariadb-snap-01"
    username = "root"
    password = "<some-secure-password>"
    parameter_group_name = "default.mariadb10.5"
    db_subnet_group_name = aws_db_subnet_group.prod_mariadb.name
    vpc_security_group_ids = [aws_security_group.allow_mariadb.id]
    skip_final_snapshot = true
    snapshot_identifier = data.aws_db_snapshot.prod_snapshot.id
    allocated_storage = 50
    max_allocated_storage = 1000
}

Save this code in a fine with an extention .tf then use these terraform commands to apply the changes:

terraform plan
terraform apply

# Final code

This is the final code that will launch the instance in the subnets inside the VPC.

provider "aws" {
    access_key = var.aws_access_key
    secret_key = var.aws_secret_key
    region = "us-west-2"
}

data "aws_vpc" "our_vpc" {
    id = "vpc-xxxxxxx"
}

data "aws_subnet_ids" "subnet_ids" {
    vpc_id = data.aws_vpc.our_vpc.id
    tags = {
        Name = "private-*"
    }
}

resource "aws_db_subnet_group" "prod_mariadb" {
    name = "prod-mariadb"
    subnet_ids = data.aws_subnet_ids.subnet_ids.ids
}

resource "aws_db_instance" "the_db" {
    engine = "mariadb"
    engine_version = "10.5"
    instance_class = "db.t3.medium"
    name = "prodmariadb"
    identifier = "prod-mariadb"
    username = "root"
    password = "<some-secure-password>"
    parameter_group_name = "default.mariadb10.5"
    db_subnet_group_name = aws_db_subnet_group.prod_mariadb.name
    vpc_security_group_ids = [aws_security_group.allow_mariadb.id]
    skip_final_snapshot = true
    allocated_storage = 50
    max_allocated_storage = 1000
}

resource "aws_security_group" "allow_mariadb" {
    name = "prod-mariadb-rds-sg"
    description = "Allow TLS inbound traffic"
    vpc_id = data.aws_vpc.our_vpc.id

    ingress {
        description = "Mariadb Access from within the VPC"
        from_port = 3306
        to_port = 3306
        protocol = "tcp"
        cidr_blocks = ["10.0.0.0/8"]
    }

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

    tags = {
        Name = "Allow access to prod mariadb rds"
    }
}

output "this_db_name" {
    value = aws_db_instance.the_db.name
}

output "this_db_instance_address" {
    value = aws_db_instance.the_db.address
}

output "this_db_instance_arn" {
    value = aws_db_instance.the_db.arn
}

output "this_db_instance_domain" {
    value = aws_db_instance.the_db.domain
}

output "this_db_instance_endpoint" {
    value = aws_db_instance.the_db.endpoint
}

output "this_db_instance_status" {
    value = aws_db_instance.the_db.status
}
comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy