Hosting WordPress and MySQL using AWS, Kubernetes and Terraform

Visual Sources: AWS, Terraform, WordPress, MySQL

In a previous article, I had explained how to host the popular blogging website WordPress on an Amazon Linux 2 AMI, and integrate with a database (MariaDB), where both WordPress and the database run on 2 different AWS EC2 instances. In this method, we need to manage the database ourselves, which can be challenging for beginner with little knowledge of how databases work.

Fortunately, Amazon helps us out here as well, by providing Databases as a Services with the Amazon Relational Database Service (RDS). RDS allows us to provision databases with our required compute and storage requirements, and leave the management of the database to AWS. RDS offers many advantages including a variety of database engines to choose from, like MySQL, MariaDB, Oracle, and Amazon Aurora.

In this article, we will be looking at how we can use Amazon RDS to provision a database and host WordPress with the help of Kubernetes. In this practical, we will be using Kubernetes on the local system with the help of Minikube, but we can do the same by using Amazon’s Elastic Kubernetes Service (EKS), or Amazon Fargate, which offer self-managed and Amazon- managed Kubernetes clusters respectively. Since we are working with multiple tools, it is best to use a common platform to integrate their use. Terraform is capable of doing the same.

The pre-requisites needed to setup our environment include:

  1. Installation of minikube and kubectl on the local system
  2. Creation of AWS account
  3. Installation of Terraform on the local system

Project Directory Structure and Configuring the Providers

The folder structure for our objective is as shown below. Keeping the files for provisioning resources separate from the main.tf helps in management and maintenance of the code.

Figure 1: Directory Structure for all the files and folders

In the file main.tf, all the modules will be called for provisioning of the resources, and the providers of the resources will also be defined here. In k8s_wp.tf, we will be setting up WordPress as a deployment exposed as a NodePort service. In rds.tf, we will be provisioning the database with the help of Amazon RDS. Finally, the security group to restrict access to the database only to port 3306 (default port to communicate with MySQL) will be created using the file my_sgs.tf.

First, we need to configure the providers of the resources we will be using, namely AWS and Kubernetes.The variables being used in the main.tf file will be stored in the variables.tf file in the same directory.

/* Contents of variables.tf */#------------ VARIABLES FOR CONFIGURING AWS PROFILE -------------#variable "profile_name" {
type = string
default = "your-profile-name"
}
variable "region_name" {
type = string
default = "ap-south-1"
}
#------------ VARIABLES FOR VPC -------------#variable "vpc_id" {
type = string
default = "your vpc-id"
}
#------------ VARIABLES FOR KUBERNETES CLUSTER -------------#variable "cluster_name" {
type = string
default = "minikube"
}
variable "pod_img" {
type = string
default = "wordpress"
}
variable "update_method" {
type = string
default = "RollingUpdate"
}
/* Configuring AWS and Kubernetes providers in main.tf */
provider "aws" {
profile = var.profile_name
region = var.region_name
}
provider "kubernetes" {
config_context_cluster = var.cluster_name
}

Provisoning the Database using Amazon RDS

/* Contents of my_sgs.tf */variable "vpc_id" {}resource "aws_security_group" "db_sg" {
name = "db_sg"
description = "SG for Database (SQL) instances"
vpc_id = var.vpc_id
ingress {
description = "Allow SQL DB Access only for port 3306"
from_port = 3306
to_port = 3306
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
#--------- OUTPUTS FOR SECURITY GROUP ---------#
output "db_sg"{
value = aws_security_group.db_sg
}

First, we define the module for creating the security group which will restrict access to the Database only through port 3306. We also declare an output, which will allow us to access the attributes of the security group from the main.tf file to create the database.

/* Contents of rds.tf */variable db_sg {}
variable alloc_store {}
variable store_type {}
variable engine {}
variable engine_ver {}
variable inst_type {}
variable db_name {}
variable db_username {}
variable db_password {}
resource "aws_db_instance" "rds_inst" {
depends_on = [var.db_sg]
allocated_storage = var.alloc_store # Storage size for DB
storage_type = var.store_type #Storage type of DB
engine = var.engine # Type of DB Engine (Eg: mysql, mariadb)
vpc_security_group_ids = [var.db_sg.id]
engine_version = var.engine_ver
instance_class = var.inst_type #DB instance type (Eg: db.t2.micro)

# Giving Credentials for the DB table and user access
name = var.db_name
username = var.db_username
password = var.db_password

# Making the DB publicly accessible to integrate with WordPress running on K8s
publicly_accessible = true

# Setting this true so that there will be no problem while destroying the infrastructure
skip_final_snapshot = true
tags = {
Name = "my_rds_db"
}
}
#--------- OUTPUTS FOR RDS DATABASE ---------## Gives DB table name
output "db_name" {
value = aws_db_instance.rds_inst.name
}
# Gives the DB host address
output "db_addr" {
value = aws_db_instance.rds_inst.address
}
# Gives the username for DB access
output "db_username" {
value = aws_db_instance.rds_inst.username
}
# Gives the password for DB access
output "db_passwd" {
value = aws_db_instance.rds_inst.password
}

Next, we define the module to create a database. Many of the arguments are declared as variables which will help in user customization. The outputs are important, as they will help in setting up WordPress.

Launching WordPress using Kubernetes Deployments and Services

/* Contents of my_k8s_wp.tf */variable deploy_name {}
variable deploy_label {}
variable pod_replicas {type = number}
variable pod_label {}
variable pod_img {}
resource "kubernetes_persistent_volume_claim" "wp_pvc" {
metadata {
name = "wp-pvc"
labels = {
app = "wp-pvc"
}
}
spec {
access_modes = ["ReadWriteMany"]
resources {
requests = {
storage = "1Gi"
}
}
}
}
resource "kubernetes_deployment" "wp_deploy" {
depends_on = [kubernetes_persistent_volume_claim.wp_pvc,]
metadata {
name = var.deploy_name
labels = {
app = var.deploy_label
}
}

spec {

# Defining no. of replicas
replicas = var.pod_replicas

selector {
match_labels = {
app = var.pod_label
}
}

template {

metadata {
labels = {
app = var.pod_label
}
}

spec {
volume {
name = "wp-pv"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.wp_pvc.metadata.0.name
}
}

container {
# Defining the image
image = var.pod_img
name = "wp-container"
port {
container_port = 80
}
volume_mount {
name = "wp-pv"
mount_path = "/var/www/html"
}
}
}
}
}
}
resource "kubernetes_service" "wp_svc" {
depends_on = [kubernetes_deployment.wp_deploy]
metadata {
name = "wp-svc"
labels = {
app = var.pod_label
}
}
spec {
selector = {
app = var.pod_label
}
port {
port = 8080
target_port = 80
node_port = 30303
}
type = "NodePort"
}
}

To launch WordPress on Kubernetes using deployments and expose it using services, we being by creating a persistent volume claim of 1GiB which makes the data related to WordPress (in the directory /var/www/html) persistent. Next, we provision a deployment which takes input from the users for the container image, the number of pod replicas, and labels for deployments and pods. The inputs will be defined when the modules are called in the main.tf file.

Provisioning all the resources

/* Contents of main.tf */provider "aws" {
profile = var.profile_name
region = var.region_name
}
module "sg_rds" {
source = "./my_sg"
vpc_id = var.vpc_id
}
module "rds_inst" {
source = "./my_rds"
db_sg = module.sg_rds.db_sg
alloc_store = 20
store_type = "gp2"
engine = "mysql"
engine_ver = "5.7.30"
inst_type = "db.t2.micro"
db_name = "my_db"
db_username = "testuser"
db_password = "testpassword"

}
provider "kubernetes" {
config_context_cluster = var.cluster_name
}
module "wordpress" {
source = "./my_k8s_wp"
deploy_name = "wp-dp"
deploy_label = "wp"
pod_replicas = 1
pod_label = "wp"
pod_img = var.pod_img
}
output "db_name" {
value = "Database name: ${module.rds_inst.db_name}"
}
output "db_addr" {
value = "Database Host Address: ${module.rds_inst.db_addr}"
}
output "db_username" {
value = "Username for DB Access: ${module.rds_inst.db_username}"
}
output "db_passwd" {
value = "Password for DB Access: ${module.rds_inst.db_passwd}"
}

Finally, we can call the modules from their respective sources and provision all the required resources. To provision the resources, we need to use the following commands on the command line, with the working directory as the location of main.tf.

terraform init # To initialise all the modules and providersterraform plan # To get an overview of all the resources being added, modified and deletedterraform validate # Validating the configurations used in the filesterraform apply --auto-approve # Applying the changes as per the output of terraform plan

If the resources have been successfully created, we get the following output.

Figure 2: Output after successful provisioning of all resources

Now, we can use the minikube IP, with the allocated node port to access WordPress from our local system.

Figure 3: Setting up WordPress (Part 1)
Figure 4: Setting up WordPress (Part 2)

When setting up WordPress, we will be asked to give information about the Database (Database name, username, password, and the host address of the database). These details have been printed as outputs after the successful provisioning of all resources.

Figure 5: Setting up WordPress (Part 3)

Once the installation is complete, we can now access the dashboard of the new blog with the username and password we have set.

Figure 6: Setting up WordPress (Part 4)

ECE Undergrad | ML, AI and Data Science Enthusiast | Avid Reader | Keen to explore different domains in Computer Science

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store