Automate WordPress Apps with MySQL on Kubernetes over AWS using Ansible

Gursimar Singh
AWS in Plain English
11 min readApr 29, 2021

--

Task Description

  • Automate Kubernetes Cluster Using Ansible.
  • Launch EC2-instances on AWS Cloud for master and slave.
  • Create roles that will configure the master node and slave node separately.
  • Launch a WordPress and MySQL database connected to it in the respective slaves.
  • Expose the WordPress pod so that the client is able to hit the WordPress IP with its respective port.

What is AWS?

AWS is one of the largest cloud providers, and it offers a wide range of technologies. It provides cloud-based services for creating, testing, monitoring, deploying, and running the entire company. They also support technologies such as augmented reality, virtual reality, quantum computing, robotics, and so forth.

EC2 is one of the services (Elastic Cloud Computing). They offer virtual computers with various major operating systems and resources such as RAM, CPU, networking, and so on.

What exactly is Kubernetes?

Kubernetes, often known as K8S, is an open-source system for automating containerised application deployment, scaling, and administration. Kubernetes is an open-source container orchestration system that automates the deployment, scaling, and administration of computer applications. It was created by Google and is currently supported by the Cloud Native Computing Foundation.

Operating apps gets increasingly difficult when they grow to span several containers distributed across multiple hosts. Kubernetes provides an open-source API that controls how and where those containers run to handle this complexity.

Kubernetes orchestrates virtual machine clusters and schedules containers to execute on those virtual machines depending on the virtual machines’ available computational resources and the resource requirements of each container. Containers are organised into pods, Kubernetes’ fundamental operational unit, and those pods grow to your desired state.

Kubernetes also handles service discovery automatically, includes load balancing, records resource allocation, and scales based on computing consumption. It also monitors the health of individual resources and enables programmes to self-heal by restarting or replicating containers automatically.

Kubernetes uses Docker to create containers.

What exactly is Docker?

Docker is a service for container management. Docker’s keywords are develop, ship, and run everywhere. Docker’s entire concept is to enable developers to simply design programs, package them into containers, and then deploy them wherever they please.

What exactly is WordPress?

WordPress is a free and open-source content management system developed in PHP that works in conjunction with a MySQL or MariaDB database. Plugin architecture and a template system, known as Themes in WordPress, are features. WordPress began as a blog-publishing system but has now expanded to accommodate other web content such as more traditional mailing lists and forums, media galleries, membership sites, learning management systems, and online commerce. As of March 2021, WordPress was used by more than 40.5 percent of the top 10 million websites, making it one of the most popular content management system solutions in use. WordPress has also been utilised in various other application domains, including ubiquitous display systems.

What exactly is MySQL?

MySQL is a relational database management system that is free and open-source (RDBMS). Its name is a mix of “My,” co-founder Michael Widenius’s daughter’s name, and “SQL,” the acronym for Structured Query Language. A relational database organises data into one or more data tables where data types can be associated with one another; these relationships assist in the structure of the data. SQL is a programming language that allows programmers to build, change, and retrieve data from relational databases and control user access to those databases. In addition to relational databases and SQL, an RDBMS such as MySQL collaborates with an operating system to implement a relational database in a computer’s storage system, manages users, allows for network access, and simplifies verifying database integrity and backup creation.

What exactly is Ansible?

Ansible is a configuration management tool, but it may also be used for provisioning.
Ansible is a software program that provides cross-platform computer assistance through simple yet effective automation. It is primarily aimed at IT professionals who use it for application deployment, workstation and server upgrades, cloud provisioning, configuration management, and practically anything else a systems administrator performs weekly or daily. Ansible does not rely on agent software and does not require any additional security infrastructure, making it simple to set up. Ansible does not have any agents. You do not need to install any agents/software or additional firewall ports on the client systems or hosts you wish to automate. You do not need to build up a separate management architecture to manage your complete system, network, and storage.

Ansible’s features and capabilities include Configuration Management, Application Deployment, Orchestration, Security and Compliance, and Cloud Provisioning.

Now, we are clear about the technologies being used. So, let’s jump right into the project.

Let us first create a dynamic inventory

Installing Python3: $ yum install python3 -y

Installing the boto3 library: $ pip3 install boto

Creating an inventory directory:

$ mkdir -p /opt/ansible/inventory
$ cd /opt/ansible/inventory

Create a file aws_ec2.yaml in the inventory directory with the following configuration:

plugin: aws_ec2
aws_access_key: <YOUR-AWS-ACCESS-KEY-HERE>
aws_secret_key: <YOUR-AWS-SECRET-KEY-HERE>
keyed_groups:
- key: tags
prefix: tag

Open /etc/ansible/ansible.cfg and find the [inventory] section and add the following line to enable the ec2 plugin:

[inventory]
enable_plugins = aws_ec2

Now let’s test the dynamic inventory configuration by listing the EC2 instances:

$ ansible-inventory -i /opt/ansible/inventory/aws_ec2.yaml --list

The above command returns the list of EC2 instances with all its parameters in JSON format.

Now, check if Ansible is able to ping all the machines returned by the dynamic inventory: ansible all -m ping

Dynamic inventory setup is done.

Launching one master and two slave nodes on AWS

ansible-playbook <file_name>

I used aws.yml file to launch three instances on top of the AWS cloud for launching two slave nodes and one master node.

- hosts: localhost
vars_files:
secret.yml
tasks:
- name: "Creating Master Node"
ec2:
region: ap-south-1
aws_access_key: "{{ access_key }}"
aws_secret_key: "{{ secret_key }}"
vpc_subnet_id: subnet-0288bbf00ed3128d7
count: 1
state: present
instance_type: t2.micro
key_name: redhat-key
assign_public_ip: yes
group_id: sg-0612a79a1fdb041ff
image: ami-08f63db601b82ff5f
instance_tags:
name: master
- name: "Creating Slave Nodes"
ec2:
region: ap-south-1
aws_access_key: "{{ access_key }}"
aws_secret_key: "{{ secret_key }}"
vpc_subnet_id: subnet-0288bbf00ed3128d7
count: 2
state: present
instance_type: t2.micro
key_name: redhat-key
assign_public_ip: yes
group_id: sg-0612a79a1fdb041ff
image: ami-08f63db601b82ff5f
instance_tags:
name: slave

The secret.yml file:

aws_access_key: <YOUR-AWS-ACCESS-KEY-HERE>
aws_secret_key: <YOUR-AWS-SECRET-KEY-HERE>

Creating a yml file for MySQL and WordPress

The file creates a secret that contains the username and password of the database and service which is exposed in the private world only so that WordPress application can connect with port number 3306 only, and deployment with recreate strategy using version mysql:5.6:

apiVersion: v1
kind: Secret
metadata:
name: mysecure
data:
rootpass: cmVkaGF0
userpass: cmVkaGF0
---
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
app: wordpress
tier: mysql
clusterIP: None
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysecure
key: rootpass
- name: MYSQL_USER
value: vd
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysecure
key: userpass
- name: MYSQL_DATABASE
value: sqldb
ports:
- containerPort: 3306
name: mysql

This file creates a service with a type called LoadBalancer exposed with port number 80, deployment with the same strategy called recreate, and getting the user name and password of the database by using the secret database created in the above steps.

apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: mysql
type: LoadBalancer
---apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: wordpress:latest
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_USER
value: vd
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysecure
key: userpass
- name: WORDPRESS_DB_NAME
value: sqldb
ports:
- containerPort: 80
name: wordpress

Ansible playbook for the master node

First add the kubeadm repository so that it can download kubeadm, kubelet, and kubectl. We can use the COPY module instead of the yum_repository module:

- name: Adding Kubeadm repo
copy:
src: kubernetes.repo
dest: /etc/yum.repos.d

The kubernetes.repo file is as follows:

[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg

Now, let’s install Docker and kubeadm using the package module.

We can also use CRI-O instead of Docker:

- name: Installing docker
package:
name: "docker"
state: present
- name: Installing kubeadm
package:
name: "kubeadm"
state: present

Enabling the Docker service:

- name: Enabling docker service
service:
name: docker
state: started
enabled: yes

Now pull all images from the Docker hub which is important to set up the master node.

These images are related to api-server, flannel, kube-controller, etcd, controller-manager, scheduler:

- name: Pulling all kubeadm config image
command: kubeadm config images pull
ignore_errors: no

Now, as we know that Kubernetes supports systemd driver, and we have to change this cgroup to systemd. For this, we can a file called daemon.json which can be copied to /etc/docker/daemon.json so that it automatically changes to systemd driver.

- name: Changing driver cgroup to systemd
copy:
src: daemon.json
dest: /etc/docker/daemon.json

The daemon.json file is as follows:

{
"exec-opts": ["native.cgroupdriver=systemd"]
}

Now remove all swap files from /etc/fstab because it shows errors while initializing the master node:

- name: Removing swapfile from /etc/fstab
mount:
name: "{{ item }}"
fstype: swap
state: absent
with_items:
- swap
- none

Now Enable kubelet service and restart the Docker as we change the driver(systemd):

- name: Enabling kubelet service
service:
name: kubelet
daemon_reload: yes
state: started
enabled: yes
- name: Restarting docker service
service:
name: docker
state: "restarted"

Install iproute-tc software because Kubernetes master uses this software while initializing as a master node.

- name: Installing iproute-tc
package:
name: iproute-tc
state: present
update_cache: yes

Now we can initialize the node as a master node. Remember, it’s not ideal to configure the RAM to less than 2200MB and CPU to less than 2 as it throws an error. For ignoring this error we can use — ignore-preflight-error.

- name: Initializing the kubeadm
shell: "kubeadm init --pod-network-cidr=10.244.0.0/16 --ignore-preflight-errors=Swap --ignore-preflight-errors=NumCPU --ignore-preflight-errors=Mem --node-name=master"

register: kubeadm
ignore_errors: yes

- debug:
msg: "{{ kubeadm }}"

Now, set up the kubeconfig for the home user so that the master node can also work as a client and can use kubectl command.

- name: Setup kubeconfig for home user
shell: "{{ item }}"
with_items:
- "mkdir -p $HOME/.kube"
- "cp -i /etc/kubernetes/admin.conf $HOME/.kube/config"
- "chown $(id -u):$(id -g) $HOME/.kube/config"

Now, add the flannel network in the master node so that it can set up some internal overlay setup.

- name: Adding flannel network
shell: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

Now we create a token for the slave node for authentication purposes and store this token in on file called token.sh by using the local_action module.

- name: Joining token
shell: "kubeadm token create --print-join-command"
register: token
- debug:
msg: "{{ token }}"
ignore_errors: yes
- name: Storing token into a file
local_action: copy content={{ token.stdout_lines[0] }} dest=../slave1/token.sh

Now we copy the database.yml and wordpress.yml files.

- name: Copying mysql-database.yml file
copy:
src: database.yaml
dest: /root

- name: Copying wordpress.yml file
copy:
src: wordpress.yml
dest: /root

Now, finally running both, database and WordPress, files using shell module. Remember to specify the path of the files database.yml and wordpress.yml.

We can see the output of the command using debug module:

- shell: "kubectl apply -f /root/database.yaml"
register: mysql
- shell: "kubectl apply -f /root/wordpress.yml"
register: wordpress
- debug:
msg: "{{ mysql }}
- debug:
msg: "{{ wordpress }}"

Ansible-Playbook for slave node

Almost all the steps are similar to the master node. It’s the same till step 8 except for step 4. So, let's look at what extra we have to do set up the node as a slave node.

9. Copying k8s.conf file at /etc/sysctl.d/ path. It is important to initialise any node as a slave node.

- name: Copying k8s.conf file
copy:
src: k8s.conf
dest: /etc/sysctl.d/k8s.conf

k8s.conf file:

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

10. Now enable the sysctl:

- name: Enabling sysctl
shell: "sysctl --system"

11. Now before joining the slave node to the master node we have to use the token created by the master node. While running the playbook I stored the token created by the master node into the token.sh file. So, I can use it in these steps. I used the shell module to run token.sh file.

- name: Copying token file at /root location
copy:
src: token.sh
dest: /root/token.sh
- name: Joining slave node to master node
shell: "sh /root/token.sh"
register: joined
- debug:
msg: "{{ joined }}"

Now after running this role entire master node and slave nodes will be configured.

Wordpress & MySQL

  1. Now let’s log in to the master node and run the kubectl get pods command to cross verify.
  2. Now WordPress and MySQL Database are running properly. Now check the Port Number where WordPress is running using the below command,
$ kubectl get svc

3. Copy the public IP of any of the nodes & respective port number, go to your browser(preferably Google Chrome) and paste it.

We have successfully performed the task.

Alternatively, we can use the following ansible-playbook to configure WordPress over the multi-node cluster we setup over AWS

We can create a role for WordPress and MySQL instead of using the Kubernetes files.

Now we need to create a pod for WordPress and MySQL to launch them respectively.

---
#tasks file for wordpress-mysql
#task to launch wordpress- name: "Launching Wordpress"
shell: "kubectl run mywp1 --image=wordpress:5.1.1-php7.3-apache"
register: Wordpress- debug:
var: "Wordpress.stdout_lines"
#task to launch mysql
- name: "Launching MySql"
shell: "kubectl run mydb1 --image=mysql:5.7 --env=MYSQL_ROOT_PASSWORD=redhat --env=MYSQL_DATABASE=wpdb --env=MYSQL_USER=vd --env=MYSQL_PASSWORD=redhat"
register: MySql

To launch the MySQL pod we need to set the user name and password. Here, you can use secret resource Kubernetes or vault module from Ansible.

#mysql root password
MYSQL_ROOT_PASSWORD=redhat
#mysql database name
MYSQL_DATABASE=wpdb
#mysql user name
MYSQL_USER=vd
#myd=sql password
MYSQL_PASSWORD=redhat

These are the required variables that need to be answered while launching the MySQL pod. If you don’t use these variables then it will throw an error.

Exposing WordPress pod:

- name: "Exposing wordpess"
shell: "kubectl expose pods mywp1 --type=NodePort --port=80"
register: expose
ignore_errors: yes
- debug:
var: "expose.stdout_lines"

To access WordPress in the public world we need to expose the NodePort.

- name: "get service"
shell: "kubectl get svc"
register: svc
- debug:
var: "svc.stdout_lines"

As we are automating the whole thing so we don’t need to go inside the master node and check the exposed service port. The above code will get the services to give an output while running the main playbook.

- name: "Pausing playbook for 60 seconds"
pause:
seconds: 60
- name: "Getting the Database IP"
shell: "kubectl get pods -o wide"
register: Database_IP
- debug:
var: "Database_IP.stdout_lines"

After launching the pods it takes time to launch. So, we pause the playbook for 60 seconds so all the pods will be ready and we get the complete information about the pods.

Now we just need to the run the playbook

GitHub URL: gursimarh/wordpress-kubernetes-aws-ansible (github.com)

To know about launching Kubernetes multi node cluster over AWS visit: Ansible Role to Configure Kubernetes Multi Node Cluster over AWS Cloud | by Gursimar Singh | Apr, 2021 | Medium

Connect with me for doubts and discussions: https://www.linkedin.com/in/gursimarh

More content at plainenglish.io

--

--

Google Developers Educator | Speaker | Consultant | Author @ freeCodeCamp | DevOps | Cloud Computing | Data Science and more