
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:
[[email protected] ~]$ 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:
[[email protected] 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
[[email protected] 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:
[[email protected] ~]$ 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!