Automated LAMP Setup Using Ansible

LAMP Setup Using Ansible

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 .mycnfin 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!