In this tutorial series, we are setting up a highly available WordPress web site from scratch.
Part 1 – Introduction, Considerations, and Architecture
Part 2 – Ordering the VPSes
Part 3 – Ansible (this article)
Part 4 – Gluster
Part 5 – WordPress install
Part 6 – MariaDB Multi-Master
Part 7 – Round-Robin DNS, Let’s Encrypt, & Conclusion
In 2021, I just executed all the setup commands on both nodes. Now we have three nodes, and it’s time to take a more convenient approach.
We’re going to use Ansible, a fantastic tool for managing systems and applications. You could install it on node1, and use it to control all three nodes, but I have an existing Ansible master that I’m using. It really doesn’t matter.
To install ansible:
apt install ansible
On my Ansible master, I have a filesystem mounted at /ansible that contains everything I need. However, if you install Ansible from apt, it sets up various things in /etc/ansible, so we’ll use that.
Here is my /etc/ansible/hosts file:
[nodes] node1.lowend.party node2.lowend.party node3.lowend.party
What the does is create a group called “nodes” that has our three nodes. Make sure these nodes are in DNS, or you can create /etc/hosts entries like these:
5.78.68.150 node1.lowend.party node1 5.78.91.194 node2.lowend.party node2 5.78.74.126 node3.lowend.party node3
In /etc/ansible/ansible.cfg you shouldn’t need to change anything.
Now here’s the meat of things, the playbook itself. In Ansible, a playbook is a list of tasks (and other things, but we’re focused on tasks here) that will be performed. We’re going to call our playbook wp_nodes.yml. That .yml tells you that the file is in YaML format.
Here’s the playbook:
--- - name: setup HA WordPress Nodes hosts: nodes tasks: - name: hostname hostname: name={{ ansible_host }} - name: /etc/hostname lineinfile: path=/etc/hostname line="{{ ansible_host }}" create=yes - name: locale generation locale_gen: name=en_US.UTF-8 state=present - name: apt-get update apt: update_cache=yes - name: upgrade apt: upgrade=dist - name: apt packages apt: name=nginx,mariadb-server,php-fpm,php-mysql,python3-pymysql,python3-pexpect,glusterfs-server,rsyslog,parted,certbot,python3-certbot-nginx - name: Set timezone to US/Pacific community.general.timezone: name: US/Pacific - name: /etc/profile mods blockinfile: path: /etc/profile block: | alias ll='ls -al' set -o vi - name: run mysql_secure_installation expect: command: mysql_secure_installation responses: 'Enter current password for root': '' 'Switch to unix_socket authentication' : 'Y' 'Change the root password' : 'Y' 'Set root password': 'Y' 'New password': 'StrongPassword' 'Re-enter new password': 'StrongPassword' 'Remove anonymous users': 'Y' 'Disallow root login remotely': 'Y' 'Remove test database': 'Y' 'Reload privilege tables now': 'Y' timeout: 1 - name: create wp database mysql_db: login_user=root login_password="StrongPassword" name=wp state=present collation=utf8_general_ci - name: create db user mysql_user: login_user=root login_password="StrongPassword" name=wp password=ComplePassword priv=wp.*:ALL host=localhost - name: nginx logrotate copy: src=/ansible/src/nodes/nginx_web_dirs dest=/etc/logrotate.d owner=root group=root mode=0644 force=yes - name: nginx www.lowend.party copy: src=/ansible/src/nodes/www.lowend.party dest=/etc/nginx/sites-available owner=root group=root mode=0644 force=yes - name: create sites-enabled symlink file: src=/etc/nginx/sites-available/www.lowend.party dest=/etc/nginx/sites-enabled/www.lowend.party state=link - name: nginx log dir file: path=/var/log/nginx/www.lowend.party state=directory owner=www-data group=adm mode=0775 - name: nginx access log file: path=/var/log/nginx/www.lowend.party/access.log state=touch owner=www-data group=adm mode=0664 state=touch - name: nginx error log file: path=/var/log/nginx/www.lowend.party/error.log state=touch owner=www-data group=adm mode=0664 state=touch - name: nginx /web file: path=/web state=directory owner=www-data group=adm mode=0775 - name: nginx /web/www.lowend.party file: path=/web/www.lowend.party state=directory owner=www-data group=adm mode=0775 - name: restart nginx service: name=nginx enabled=yes state=restarted - name: gluster mount point file: path=/gluster state=directory owner=root group=root mode=0777
Let’s break that down step by step.
--- - name: setup HA WordPress Nodes hosts: nodes
The name: parameter is just a descripter for the playbook. The hosts: parameter tells us what group in /etc/ansible/hosts it will run against.
tasks: - name: hostname hostname: name={{ ansible_host }} - name: /etc/hostname lineinfile: path=/etc/hostname line="{{ ansible_host }}" create=yes
Each Ansible command leverages one of Ansible’s modules. You don’t write your own Bash or whatever code, but instead pass arguments to Ansible modules, which do all the work. This frees you from having to worry about syntax, logic, quoting, etc.
Here we use the hostname module, and given it the Ansible variable “ansible_host”. This variable will be node1.lowend.party, etc. and this module will set the system hostname appropriately.
Next we use the “lineinfile” module to make sure the hostname is put in /etc/hostname (a Debianism I believe) so the box will always pick up its correct hostname at boot.
- name: locale generation locale_gen: name=en_US.UTF-8 state=present
Next, we generate the appropriate locales for our environment, making sure that en_US.UTF-8 is selected. Obviously, suit to your own needs here.
- name: apt-get update apt: update_cache=yes - name: upgrade apt: upgrade=dist - name: apt packages apt: name=nginx,mariadb-server,php-fpm,php-mysql,python3-pymysql,python3-pexpect,glusterfs-server,rsyslog,parted,certbot,python3-certbot-nginx
Here we’re using the Ansible apt module to do three things:
- apt-get update
- apt-get upgrade
- Install some apt packages we’ll need. Whether you want to run rsyslog is up to you. I prefer to have traditional text logs instead of using journalctl for everything, but it’s up to you.
- name: Set timezone to US/Pacific community.general.timezone: name: US/Pacific
Here we set the timezone. Flavor to your own taste.
- name: /etc/profile mods blockinfile: path: /etc/profile block: | alias ll='ls -al' set -o vi
I left this in as an example, but it’s entirely optional. I like to have those two lines in my /etc/profile because no matter what user I’m working as, I want that alias and option set. What the blockinfile module does is make sure that the block (the two commands listed here) are present in the file specified, in this case /etc/profile.
- name: run mysql_secure_installation expect: command: mysql_secure_installation responses: 'Enter current password for root': '' 'Switch to unix_socket authentication' : 'Y' 'Change the root password' : 'Y' 'Set root password': 'Y' 'New password': 'StrongPassword' 'Re-enter new password': 'StrongPassword' 'Remove anonymous users': 'Y' 'Disallow root login remotely': 'Y' 'Remove test database': 'Y' 'Reload privilege tables now': 'Y' timeout: 1
When installing MySQL (MariaDB actually), it’s recommended to run mysql_secure_installation. But rather than run that manually, we’ll use Ansible’s expect module. You tell it what command to run, and then a list of what output to expect and what answers to give. This section walks through the mysql_secure_installation questions and provides the appropriate answers. Of course, you should change StrongPassword to a genuinely strong password!
- name: create wp database mysql_db: login_user=root login_password="StrongPassword" name=wp state=present collation=utf8_general_ci - name: create db user mysql_user: login_user=root login_password="StrongPassword" name=wp password=ComplexPassword priv=wp.*:ALL host=localhost
Here we create a MariaDB database for WordPress, a user for that DB, and grant it appropriate permissions. Note that even if you run this playbook multiple times, Ansible is smart enough to first check if the DB exists and not error out trying to recreate it, etc.
- name: nginx logrotate copy: src=/ansible/src/nodes/nginx_web_dirs dest=/etc/logrotate.d owner=root group=root mode=0644 force=yes - name: nginx www.lowend.party copy: src=/ansible/src/nodes/www.lowend.party dest=/etc/nginx/sites-available owner=root group=root mode=0644 force=yes
Here we’re distributing two local files from our Ansible master server to each node. I store them in /ansible/src but you can store them anywhere.
See below for the content of these files.
- name: create sites-enabled symlink file: src=/etc/nginx/sites-available/www.lowend.party dest=/etc/nginx/sites-enabled/www.lowend.party state=link
We link /etc/nginx/sites-enabled/www.lowend.party to /etc/nginx/sites-available/www.lowend.party, which is standard Nginx setup on Debian.
- name: nginx log dir file: path=/var/log/nginx/www.lowend.party state=directory owner=www-data group=adm mode=0775 - name: nginx access log file: path=/var/log/nginx/www.lowend.party/access.log state=touch owner=www-data group=adm mode=0664 state=touch - name: nginx error log file: path=/var/log/nginx/www.lowend.party/error.log state=touch owner=www-data group=adm mode=0664 state=touch
We create a directory and the access.log and error.logs for Nginx. I prefer to have each site in its own directory.
- name: nginx /web file: path=/web state=directory owner=www-data group=adm mode=0775 - name: nginx /web/www.lowend.party file: path=/web/www.lowend.party state=directory owner=www-data group=adm mode=0775
I organize my web roots in /web (yes, right off the root directory – why not?).
- name: restart nginx service: name=nginx enabled=yes state=restarted - name: gluster mount point file: path=/gluster state=directory owner=root group=root mode=0777
Finally, we restart the nginx service and create another directory we’ll need for gluster.
The /etc/logrotate.d/nginx_web_dirs file that we distribute to all nodes from /ansible/src/nginx_web_dirs looks like this:
/var/log/nginx/*/*.log { daily missingok rotate 14 compress delaycompress notifempty create 0640 www-data adm sharedscripts prerotate if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ run-parts /etc/logrotate.d/httpd-prerotate; \ fi \ endscript postrotate invoke-rc.d nginx rotate >/dev/null 2>&1 endscript }
You only need this if you’re using rsyslog.
The Nginx file that is put in /etc/nginx/sites-available/www.lowend.party looks like this:
server { server_name www.lowend.party; access_log /var/log/nginx/www.lowend.party/access.log; error_log /var/log/nginx/www.lowend.party/error.log; location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(.*)$; include /etc/nginx/fastcgi_params; fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /web/www.lowend.party$fastcgi_script_name; } location / { root /web/www.lowend.party; index index.php index.html; try_files $uri $uri/ /index.php; if (!-e $request_filename) { rewrite . /index.php last; } } }
This is a pretty standard Nginx file. As you can see, we’re using PHP-FPM, which is a package we installed above.
Wow, we’ve got a ton done with Ansible:
- Setting hostname, locales, and timezone
- Installing packages with apt
- Setting up the database and securing it
- Installing rsyslog and configuring it to rotate Nginx
- Installing Nginx, configuring it, configuring the site, and setting up the site’s logs
Next time we’ll get GlusterFS up and running!
Related Posts:
- Crunchbits Discontinuing Popular Annual Plans – The Community Mourns! - November 20, 2024
- RackNerd’s Black Friday 2024: Bigger, Better, and Now in Dublin! - November 19, 2024
- It’s the Season of Giving and CharityHost Has Deals for You! - November 18, 2024
Leave a Reply