DEV Community

Oluwaseun Olaleye
Oluwaseun Olaleye

Posted on

How to Set Up MySQL client-server on AWS Using Terraform

Introduction
In this guide, I will walk you through setting up MySQL Client-Server on AWS using Terraform. This setup involves creating a Virtual Private Cloud (VPC), subnets, a NAT gateway, EC2 instances, and security groups. Additionally, we will configure MySQL as a database on one instance, and a client instance to interact with it.

Prerequisites
Before we begin, ensure you have the following tools installed:

  1. Terraform

  2. AWS Account

  3. AWS CLI: Configured and authenticated with your AWS credentials.

Steps
Step 1: Set Up the AWS Provider

We begin by configuring the AWS provider with your region.

provider "aws" {
  region = var.region
}

Image description

Step 2: Create an SSH Key Pair
We generate an RSA private key for SSH access to our instances.

resource "tls_private_key" "rsa" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "MEAN_SERVER" {
  key_name   = var.key_pair_name
  public_key = tls_private_key.rsa.public_key_openssh
}

resource "local_file" "private_key" {
  content  = tls_private_key.rsa.private_key_pem
  filename = "${var.key_pair_name}.pem"
}

Image description

Step 3: Define the VPC and Subnets
Create a VPC and both public and private subnets to host the EC2 instances.

resource "aws_vpc" "MEAN_VPC" {
  cidr_block           = var.vpc_cidr_block
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = var.vpc_name
  }
}

resource "aws_subnet" "public" {
  count                   = length(var.public_subnet_cidrs)
  vpc_id                  = aws_vpc.MEAN_VPC.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
  tags = {
    Name = "MEAN_PUBLIC_${count.index}"
  }
}

resource "aws_subnet" "private" {
  count             = length(var.private_subnet_cidrs)
  vpc_id            = aws_vpc.MEAN_VPC.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "MEAN_PRIVATE_${count.index}"
  }
}

Image description

Image description

Step 4: Set Up Internet and NAT Gateway
We create an Internet Gateway for public subnet access and a NAT Gateway for private subnet access.

resource "aws_internet_gateway" "MEAN_GW" {
  vpc_id = aws_vpc.MEAN_VPC.id
  tags = {
    Name = var.internet_gateway_name
  }
}

resource "aws_eip" "nat" {
  domain     = "vpc"
  depends_on = [aws_internet_gateway.MEAN_GW]
}

resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id
  depends_on    = [aws_internet_gateway.MEAN_GW]
  tags = {
    Name = var.nat_gateway_name
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.MEAN_VPC.id
  tags = {
    Name = var.public_route_table_name
  }
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.MEAN_VPC.id
  tags = {
    Name = var.private_route_table_name
  }
}

resource "aws_route" "public_internet_access" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.MEAN_GW.id
}

resource "aws_route" "private_nat_route" {
  route_table_id         = aws_route_table.private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat.id
}

resource "aws_route_table_association" "public_assoc" {
  count          = length(aws_subnet.public)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private_assoc" {
  count          = length(aws_subnet.private)
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}

resource "aws_security_group" "MEAN_SG" {
  name        = var.security_group_name
  description = "Allow Database and SSH"
  vpc_id      = aws_vpc.MEAN_VPC.id

Step 5: Configure Security Groups
The security group will allow SSH access and MySQL communication between the instances.

resource "aws_security_group" "MEAN_SG" {
  name        = var.security_group_name
  description = "Allow Database and SSH"
  vpc_id      = aws_vpc.MEAN_VPC.id

  ingress {
    description = "Allow SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.ssh_ingress_cidr]
  }

  ingress {
    description = "Allow MySQL"
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = [var.vpc_cidr_block]  # Allow internal access
  }
egress {
    description = "Allow all outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = var.security_group_name
  }
}

Step 6: Launch EC2 Instances
Now we launch two EC2 instances, one for the server and another as the client.

resource "aws_instance" "mean_server" {
  ami                    = var.instance_ami
  instance_type          = var.instance_type
  key_name               = aws_key_pair.MEAN_SERVER.key_name
  subnet_id              = aws_subnet.public[0].id
  vpc_security_group_ids = [aws_security_group.MEAN_SG.id]

  user_data = <<-EOF
              #!/bin/bash
              sudo apt update -y && sudo apt upgrade -y
              sudo apt install -y mysql-server
              sudo systemctl enable mysql
              sudo systemctl start mysql
              sudo mysql -e "CREATE USER 'client'@'%' IDENTIFIED WITH mysql_native_password BY 'NewU$er.3';"
              sudo mysql -e "CREATE DATABASE test_db;"
              sudo mysql -e "GRANT ALL ON test_db.* TO 'client'@'%' WITH GRANT OPTION;"
              sudo mysql -e "FLUSH PRIVILEGES;"
              sudo sed -i "s/^bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/mysql.conf.d/mysqld.cnf
              sudo systemctl restart mysql
              EOF

  tags = {
    Name = "MEAN-SERVER"
  }
}
resource "aws_instance" "mean_client" {
  ami                    = var.instance_ami
  instance_type          = var.instance_type
  key_name               = aws_key_pair.MEAN_SERVER.key_name
  subnet_id              = aws_subnet.public[0].id
  vpc_security_group_ids = [aws_security_group.MEAN_SG.id]

  user_data = <<-EOF
              #!/bin/bash
              sudo apt update -y && sudo apt upgrade -y
              sudo apt install -y mysql-client
              EOF

  tags = {
    Name = "MEAN-CLIENT"
  }
}

output "mean_server_public_ip" {
  value = aws_instance.mean_server.public_ip
}

output "mean_client_public_ip" {
  value = aws_instance.mean_client.public_ip
}

output "instance_dns_names" {
  value = [aws_instance.mean_server.public_dns, aws_instance.mean_client.public_dns]
}

Step 7: Visiting Our Server from the Client
Now, from our MEAN Client, let us try to access our MEAN Server:
sudo mysql -u client -h 10.0.1.199 -p

Step 8: Playing in Our New Server (accessed from the client)
Let us now make sure we can actually do stuff in our server from our client:

First, let us see what we have:

Image description

Image description

Image description

Next, let show what I have in my terraform.tfvars and variable.tf

region                = "us-east-1"
key_pair_name         = "mean-server-key"

vpc_cidr_block        = "10.0.0.0/16"
vpc_name              = "MEAN_VPC"

public_subnet_cidrs   = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs  = ["10.0.101.0/24", "10.0.102.0/24"]

internet_gateway_name     = "MEAN_IGW"
nat_gateway_name          = "MEAN_NAT"
public_route_table_name   = "MEAN_Public_RT"
private_route_table_name  = "MEAN_Private_RT"

security_group_name   = "MEAN_SG"

# Choose the appropriate AMI ID for your region 
instance_ami          = "ami-0f9de6e2d2f067fca"
instance_type         = "t2.micro"
ec2_instance_name     = "MEAN_EC2"
variable "region" {
  description = "AWS region to deploy into"
  type        = string
}

variable "vpc_cidr_block" {
  description = "CIDR block for the VPC"
  type        = string
}

variable "public_subnet_cidrs" {
  description = "List of CIDR blocks for public subnets"
  type        = list(string)
}

variable "private_subnet_cidrs" {
  description = "List of CIDR blocks for private subnets"
  type        = list(string)
}

variable "instance_ami" {
  description = "AMI ID for EC2 instance"
  type        = string
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
}

variable "key_pair_name" {
  description = "Name for the AWS key pair"
  type        = string
}

variable "ec2_instance_name" {
  description = "Name tag for the EC2 instance"
  type        = string
}

variable "vpc_name" {
  description = "Name tag for the VPC"
  type        = string
}

variable "public_route_table_name" {
  description = "Name tag for public route table"
  type        = string
}

variable "private_route_table_name" {
  description = "Name tag for private route table"
  type        = string
}

variable "nat_gateway_name" {
  description = "Name tag for the NAT Gateway"
  type        = string
}

variable "internet_gateway_name" {
  description = "Name tag for Internet Gateway"
  type        = string
}

variable "security_group_name" {
  description = "Name for the Security Group"
  type        = string
}

variable "ssh_ingress_cidr" {
  description = "CIDR block allowed to SSH (port 22)"
  type        = string
  default     = "0.0.0.0/0"
}


variable "db_access_cidr" {
  description = "CIDR block allowed for DB(port 3306)"
  type = string
  default = "10.0.55.0/24"

}

Conclusion
This Terraform script automates the creation of a Server-Client on AWS, including networking, instances, and database setup. Copy and paste the script into your .tf file, modify the variables, and run terraform apply to deploy your infrastructure.

Top comments (0)