Continuing with our Ansible Series, we will move on to a pretty common use case for most VPSes, and that is of LAMP Stack setup. LAMP stands for Linux, Apache, MySQL and PHP.This is a very popular stack of technologies that web developers use to build apps.
Linux refers to the base operating system which in our case will be Ubuntu 20.04 LTS, Apache is the web server that will serve the web content, MySQL (or MariaDB) is the database that will be used to store the state of the app (things like user content, usernames, encrypted passwords, etc) and PHP is the programming language that is used to generate dynamic content. This tech stack is the base for many popular apps like WordPress, Drupal and more.
So let us create an Ansible playbook that will reliably setup LAMP stack on Ubuntu 20.04 LTS. As a developer, this would allow you to experiment and break things, and simply reinstall your VPS and get LAMP up and running with a single ansible command!
Prerequisites
- A VPS running Ubuntu 20.04 LTS with a public IP. If you don't have one, feel free to pick one up here.
- Initial Server Setup using Ansible to get you up to speed with ansible, and to harden your server's security.
Installing Packages
Let's start by create a lamp-setup.yaml
Ansible Playbook like we did during our initial setup blog, except this time we will use the package
builtin module to install the necessary packages:
---
- name: LAMP Setup
hosts: all
remote_user: root
tasks:
- name: Installing Packages
package:
name: "{{ item }}"
state: present
with_items:
- apache2
- mysql-server
- php
- libapache2-mod-php
- php-mysql
- python3-pymysql
This will install all the basic packages that are needed for a typical LAMP setup. Linux is obviously the base system, mysql-server
, apache2
, php
are pretty self-explanatory as well. We will also need libapache2-mod-php
to enable apache2
to talk to the PHP language interpreter. We would also need php-mysql
which is a library that allows php
to interface with the mysql
database. Finally, we will also install python3-pymysql
which is not a part of LAMP stack but it is required by Ansible to make MySQL requests on the target system.
Ansible Modules for Setting Up MySQL
Next we need to setup MySQL. This would involve setting up MySQL's root user's password (which is quit different from Linux's root
user). Please be very careful and read the following:
- DO NOT USE the same password as below, rather generate a random, unique and strong password.
- DO NOT LEAVE the password in your ansible playbook. We will learn about dealing with passwords later in the future.
- DO NOT PUSH the file
.mycnf
in your target's root directory to a git repository. This will contain your MySQL credentials and it should be ignored by git and other tools.
Now, that we have that out of the way, we can start using Ansible to configure MySQL. To do this, we will first need to download and install a collection of Ansible modules on our Ansible host machine
$ ansible-galaxy collection install community.mysql
Now we can use the module mysql_user
to set root
user's password. We will also create a .my.cnf
file in the Linux root user's Home directory. This file contains username and password that can be used by Linux's root user to log into MySQL. This will also be used by Ansible in subsequent tasks to automatically authenticate as root user for MySQL without us having to repeatedly supply username and password for each task.
- name: Set root password
no_log: true
community.mysql.mysql_user:
name: root
password: PLACE_YOUR_PASSWORD_HERE
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Copy .my.cnf for easier mysql automation
blockinfile:
path: ~/.my.cnf
create: yes
block: |
[client]
user=root
password="PLACE_YOUR_PASSWORD_HERE"
Notice, we also add no_log: true
for each of the tasks above, this is to prevent Ansible from accidentally mentioning the MySQL root password in any of its log files.
Secure MySQL Installation
Most installation of MySQL comes with a default script called mysql_secure_installation
which you are meant to run as root. This script prompts you through a list of choices such as setting root password, removing anonymous user and test databases, as well as prohibiting the root user to connect to the the database remotely.
We will use Ansible to carry out these tasks without explicitly calling the script. Since we have already set root password, we only need to prohibit remote root login, remove anonymous user (represented by an empty string ''
) and we need to delete the test
database. The following tasks help us with this:
- name: Removes test database
no_log: true
community.mysql.mysql_db:
name: test
state: absent
- name: Prohibit Remote Root login
no_log: true
community.mysql.mysql_query:
login_db: mysql
query: "{{ item }}"
with_items:
- DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
- FLUSH PRIVILEGES;
We used different modules above, namely mysql_db
and mysql_query
. For more information on this you can always refer to their excellent documentation.
Configuring Apache
Next we need to configure Apache to serve php files. By default, Apache is configured to look into the directory /var/www/html
directory and serve the index.html
file first. If that is not found, it will look for index.htm
, followed by a few other extensions.
We will configure it to look for index.php
first. After this, you can deploy your PHP application to /var/www/html
and Apache will automatically serve it.
- name: Configure apache2
lineinfile:
path: /etc/apache2/mods-enabled/dir.conf
regexp: "^\tDirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm$"
line: "\tDirectoryIndex index.php index.html index.cgi index.pl index.php index.xhtml index.htm"
notify: Restart Apache2
handlers:
- name: Restart Apache2
service:
name: apache2
state: restarted
The configuration was in file /etc/apache2/mods-enabled/dir.conf
and after the task results in a changed state of the file Ansible restarts the Apache web server as specified by notify: Restart Apache2
.
The Restart Apache2
handler is defined inside the handlers
section.
Final Playbook and Testing PHP rendering
To combine all the above setups, the lamp-setup.yaml
playbook would look something like below:
---
- name: LAMP Setup
hosts: all
remote_user: root
tasks:
- name: Installing Packages
package:
name: "{{ item }}"
state: present
with_items:
- apache2
- mysql-server
- php
- libapache2-mod-php
- php-mysql
- python3-pymysql
- name: Set root password
no_log: true
community.mysql.mysql_user:
name: root
password: PLACE_YOUR_PASSWORD_HERE
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Copy .my.cnf for easier mysql automation
blockinfile:
path: ~/.my.cnf
create: yes
block: |
[client]
user=root
password="PLACE_YOUR_PASSWORD_HERE"
- name: Removes test database
no_log: true
community.mysql.mysql_db:
name: test
state: absent
- name: Prohibit Remote Root login
no_log: true
community.mysql.mysql_query:
login_db: mysql
query: "{{ item }}"
with_items:
- DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
- FLUSH PRIVILEGES;
- name: Configure apache2
lineinfile:
path: /etc/apache2/mods-enabled/dir.conf
regexp: "^\tDirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm$"
line: "\tDirectoryIndex index.php index.html index.cgi index.pl index.php index.xhtml index.htm"
notify: Restart Apache2
handlers:
- name: Restart Apache2
service:
name: apache2
state: restarted
To run the playbook use the below commands:
$ ansible-galaxy collection install community.mysql
$ ansible-playbook lamp-setup.yaml
Conclusion
With the playbook handy, you can really accelerate your development workflow. If something breaks in your testing server, you can reinstall the VPS back to a clean state, setup LAMP stack with a single command. If you wish to deploy your application directly from your git repository we have a blog post showing you how to do that as well!