Deploying WordPress in an Automated Way Using Ansible

Today we’ll be installing WordPress using Red Hat’s Ansible. Some hands-on experience with Linux and Ansible is expected.

List of requirements is as follows:

  • Ansible installed in the control node. In my case I’m using the latest version (2.9.3 at the moment this article was written)
  • 1 node with IP address 192.168.0.10 (ansible-control)
  • 1 node with IP address 192.168.0.11 (managed-node)
  • Local DNS has to be configured accordingly in /etc/hosts
  • 1 user with sudo privileges, who’s able to connect from control to managed without providing a password. In my case that user is ‘ansible’

This is the directory and file structure we’ll want to achieve at the very end of this guide:

[ansible@ansible-control ~]$ tree wordpress-sites/
 wordpress-sites/
 ├── ansible.cfg
 ├── inventory
 ├── templates
 │   ├── wordpress.conf.j2
 │   └── wp-config.php.j2
 ├── vars
 │   └── wordpress.yml
 └── wordpress.yml
 2 directories, 6 files 

Let’s start by creating the main directory in which we’ll be working from now on. In user’s ansible home directory run

mkdir wordpress-sites

Change directory to wordpress-sites and copy Ansible’s default configuration

cd wordpress-sites ; cp /etc/ansible.cfg .

Once the config is in place, we’ll open it with vim (or your favorite editor) and edit 2 things. 1. Point the inventory to /home/ansible/wordpress-sites/inventory (we’ll create the inventory in the next step) . 2. Change Ansible’s remote user to ansible, which is the user we are using to run our playbooks in the managed node

inventory      = /home/ansible/wordpress-sites/inventory
...
remote_user = ansible

Time to create and edit our inventory file. Run the following

vim inventory

We’ll add our managed-node under the group [wordpress]:

[wordpress]
managed-node

Test connectivity between control and managed node:

[ansible@ansible-control wordpress-sites]$ ansible wordpress -m ping
 ansible-wordpress | SUCCESS => {
     "ansible_facts": {
         "discovered_interpreter_python": "/usr/bin/python"
     }, 
     "changed": false, 
     "ping": "pong"
 }

#OUTPUT OF COMMAND WITH SUDO BECOME

[ansible@ansible-control wordpress-sites]$ ansible wordpress -m ping -b
 ansible-wordpress | SUCCESS => {
     "ansible_facts": {
         "discovered_interpreter_python": "/usr/bin/python"
     }, 
     "changed": false, 
     "ping": "pong"
 }

Things start getting interesting now, we need to create a directory where we’ll store our variables, another one for our templates and lastly we’ll create a single playbook which contains everything we need. If you want to make this a bit more complex you could implement roles to distribute the work better, but this is not meant to be a full guide but more a quick way to show you the kind of things you can achieve in just over an hour with Ansible.

mkdir templates    
mkdir vars
touch wordpress.yml

We just created our structure and now will create the variables we will need in our main playbook. To do this we need to move directories to vars and create a file named wordpress.yml. Let’s do it:

cd vars; touch wordpress.yml

Content of vars/wordpress.yml

---
http_port: 80
vhost_name: wordpresstest123.com
apache_dir: /var/www/html
wordpress_download: https://wordpress.org/latest.tar.gz
wordpress_dir: /var/www/html/wordpress
db_wordpress: mywordpressdb
db_user_wordpress: mywordpressuser
db_pass_wordpress: strongpassword

With all the variables set, we’ll start creating our main and only playbook. I’ll divide the playbook in 4 areas:

  • Updating and installing packages. Installing and enabling repositories
  • Enabling services and opening firewall ports
  • Database and database user creation, both needed for WordPress
  • WordPress installation and Apache vhost config setup

Open up your main playbook wordpress.yml (/home/ansible/wordpress-site/wordpress.yml) and paste the following

---
 - hosts: wordpress
   become: true
   vars_files:
     - vars/wordpress.yml
   tasks:
     - name: Updating all system packages
       yum:
         name: '*'
         state: latest
   
     - name: Installing EPEL repo
       yum:
         name: https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
         state: present
 
     - name: Importing EPEL gpg key
       rpm_key:
         key: https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7
         state: present
 
     - name: Adding and enabling remi-php73 repos
       yum_repository:
         name: remi-php73
         description: Remi's PHP 7.3 RPM repository for Enterprise Linux $releasever - $basearch
         mirrorlist: http://rpms.remirepo.net/enterprise/$releasever/php73/mirror
         enabled: yes
         gpgcheck: 1
         gpgkey: http://rpms.remirepo.net/RPM-GPG-KEY-remi
 
     - name: Installing PHP, MySQL and Apache
       yum:
         name: "{{ item }}"
         state: latest
       loop:
         - php
         - php-mysql
         - httpd
         - mariadb-server
         - MySQL-python  

You might be asking yourself “what did I just do?” and the response to that is: Updating all system packages > Installing the epel-repo which contains a lot of extra goodies needed > Installing remi repos, this will allow us to install the latest PHP versions, in this case 7.3 > Installing Apache, MariaDB and PHP.

Package MySQL-python is needed by Ansible’s mysql modules, so we’ll also install that.

The next 2 blocks will start and enable Apache and MariaDB, and also open the firewall ports needed for both services

- name: Enabling services
  service:
    name: "{{ item }}"
    state: started
    enabled: true
  loop:
    - httpd
    - mariadb

- name: Opening Apache and MariaDB ports
  firewalld:
    port: "{{ item }}/tcp"
    state: enabled
    permanent: true
    immediate: true
  loop:
    - 80
    - 3306

We are approaching the last 2 areas of this guide. In the next block we’ll create a database which WordPress will use to connect and write to, and a MariaDB user with all privileges to that DB.

- name: Creating WordPress DB
  mysql_db:
    name: "{{ db_wordpress }}"
    state: present

- name: Creating MariaDB user
  mysql_user:
    name: "{{ db_user_wordpress }}"
    password: "{{ db_pass_wordpress }}"
    priv: 'mywordpressdb.*:ALL,GRANT'
    state: present

What do we have configured so far?

  • Apache
  • PHP7.3
  • WordPress is installed in /var/www/html/wordpress
  • Firewall ports for Apache and MariaDB are open
  • MariaDB has a database called ‘mywordpressdb’ and a user ‘mywordpressuser’

WordPress uses a file called wp-config.php to configure some important details, such as database details and so on. We’ll use a jinja2 template to achieve this.

To create the template:

cd templates; touch wp-config.php.j2

Content of templates/wp-config.php.j2:

<?php
 /**
 The base configuration for WordPress
 *
 The wp-config.php creation script uses this file during the
 installation. You don't have to use the web site, you can
 copy this file to "wp-config.php" and fill in the values.
 *
 This file contains the following configurations:
 *
 * MySQL settings
 * Secret keys
 * Database table prefix
 * ABSPATH
 *
 @link https://codex.wordpress.org/Editing_wp-config.php
 *
 @package WordPress
 */ 
 // ** MySQL settings - You can get this info from your web host ** //
 /** The name of the database for WordPress */
 define( 'DB_NAME', '{{ db_wordpress }}' );
 /** MySQL database username */
 define( 'DB_USER', '{{ db_user_wordpress }}' );
 /** MySQL database password */
 define( 'DB_PASSWORD', '{{ db_pass_wordpress }}' );
 /** MySQL hostname */
 define( 'DB_HOST', 'localhost' );
 /** Database Charset to use in creating database tables. */
 define( 'DB_CHARSET', 'utf8' );
 /** The Database Collate type. Don't change this if in doubt. */
 define( 'DB_COLLATE', '' );
 /** Filesystem access **/
 define('FS_METHOD', 'direct');
 /**#@+
 Authentication Unique Keys and Salts.
 *
 Change these to different unique phrases!
 You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
 You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
 *
 @since 2.6.0
 */
 define( 'AUTH_KEY',         '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
 define( 'SECURE_AUTH_KEY',  '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
 define( 'LOGGED_IN_KEY',    '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
 define( 'NONCE_KEY',        '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
 define( 'AUTH_SALT',        '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
 define( 'SECURE_AUTH_SALT', '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
 define( 'LOGGED_IN_SALT',   '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' );
 define( 'NONCE_SALT',       '{{ lookup('password', '/dev/null chars=ascii_letters length=64') }}' ); 
 /*#@-/
 /**
 WordPress Database Table prefix.
 *
 You can have multiple installations in one database if you give each
 a unique prefix. Only numbers, letters, and underscores please!
 */
 $table_prefix = 'wp_'; 
 /**
 For developers: WordPress debugging mode.
 *
 Change this to true to enable the display of notices during development.
 It is strongly recommended that plugin and theme developers use WP_DEBUG
 in their development environments.
 *
 For information on other constants that can be used for debugging,
 visit the Codex.
 *
 @link https://codex.wordpress.org/Debugging_in_WordPress
 */
 define( 'WP_DEBUG', false ); 
 /* That's all, stop editing! Happy publishing. */
 /** Absolute path to the WordPress directory. */
 if ( ! defined( 'ABSPATH' ) ) {
     define( 'ABSPATH', dirname( FILE ) . '/' );
 }
 /** Sets up WordPress vars and included files. */
 require_once( ABSPATH . 'wp-settings.php' );

Whilst in the main playbook we add:

- name: Creating WordPress config file
  template:
    src: templates/wp-config.php.j2
    dest: "{{ apache_dir }}/wordpress/wp-config.php"
    owner: apache
    group: apache

Official WordPress documentation recommends setting file permissions to 755 for all directories and 644 for all files. We’ll do it like this

- name: Setting WordPress permissions
  shell: "{{ item }}"
  loop:
    - 'find . -type d -exec chmod 755 {} \;'
    - 'find . -type f -exec chmod 644 {} \;'

And last but not least, our Apache vhost configuration, which also comes from a template:

cd templates; touch wordpress.conf.j2

Content of templates/wordpress.conf.j2

<VirtualHost             *:{{ http_port }}>
       ServerName        {{ vhost_name }}
       ServerAlias       www.{{ vhost_name }}
       DocumentRoot      /var/www/html/wordpress
       DirectoryIndex    index.html index.php
</VirtualHost>

To use this template, add to your main playbook:

- name: Creating Apache vhost config
  template:
    src: templates/wordpress.conf.j2
    dest: /etc/httpd/conf.d/wordpress.conf
  notify: restarting apache

We include a handler at the very end of our playbook. This handler will trigger anytime our Apache vhost configuration changes and reload the service.

handlers:
  - name: reloading apache
    service:
      name: httpd
      state: reloaded

Everything is in place right now. Proceed to login to your managed node and curl your site configured. The output should look something like this:

[ansible@managed-node ~]$ curl -IL http://wordpresstest123.com
 HTTP/1.1 302 Found
 Date: Sun, 15 Mar 2020 01:47:26 GMT
 Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.15
 X-Powered-By: PHP/7.3.15
 Expires: Wed, 11 Jan 1984 05:00:00 GMT
 Cache-Control: no-cache, must-revalidate, max-age=0
 X-Redirect-By: WordPress
 Location: http://wordpresstest123.com/wp-admin/install.php
 Content-Type: text/html; charset=UTF-8


 HTTP/1.1 200 OK
 Date: Sun, 15 Mar 2020 01:47:26 GMT
 Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.15
 X-Powered-By: PHP/7.3.15
 Expires: Wed, 11 Jan 1984 05:00:00 GMT
 Cache-Control: no-cache, must-revalidate, max-age=0
 Content-Type: text/html; charset=utf-8

First we see a 302 HTTP code redirecting us to http://wordpresstest123.com/wp-admin/install.php and then a 200 code, meaning everything is working as expected.

Here’s the final config for our main playbook:

---
 - hosts: wordpress
   become: true
   vars_files:
     - vars/wordpress.yml
   tasks:
     - name: Updating all system packages
       yum:
         name: '*'
         state: latest
   
     - name: Installing EPEL repo
       yum:
         name: https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
         state: present
 
     - name: Importing EPEL gpg key
       rpm_key:
         key: https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7
         state: present
 
     - name: Adding and enabling remi-php73 repos
       yum_repository:
         name: remi-php73
         description: Remi's PHP 7.3 RPM repository for Enterprise Linux $releasever - $basearch
         mirrorlist: http://rpms.remirepo.net/enterprise/$releasever/php73/mirror
         enabled: yes
         gpgcheck: 1
         gpgkey: http://rpms.remirepo.net/RPM-GPG-KEY-remi
 
     - name: Installing PHP, MySQL and Apache
       yum:
         name: "{{ item }}"
         state: latest
       loop:
         - php
         - php-mysql
         - httpd
         - mariadb-server
         - MySQL-python  

     - name: Enabling services
       service:
         name: "{{ item }}"
         state: started
         enabled: true
       loop:
         - httpd
         - mariadb

     - name: Opening Apache and MariaDB ports
       firewalld:
         port: "{{ item }}/tcp"
         state: enabled
         permanent: true
         immediate: true
       loop:
         - 80
         - 3306

      - name: Creating WordPress config file
        template:
          src: templates/wp-config.php.j2
          dest: "{{ apache_dir }}/wordpress/wp-config.php"
          owner: apache
          group: apache

      - name: Setting WordPress permissions
        shell: "{{ item }}"
        loop:
          - 'find . -type d -exec chmod 755 {} \;'
          - 'find . -type f -exec chmod 644 {} \;'

      - name: Creating Apache vhost config
        template:
          src: templates/wordpress.conf.j2
          dest: /etc/httpd/conf.d/wordpress.conf
        notify: restarting apache

    handlers:
      - name: reloading apache
        service:
          name: httpd
          state: reloaded

This is it. Now you can open up your browser, visit your site and start configuring everything from WordPress itself!

Leave a comment

Your email address will not be published. Required fields are marked *