Deploying Drupal 8 using Ansible Playbook: Part 3

By Liptan Biswas, Alibaba Cloud Tech Share Author. Tech Share is Alibaba Cloud’s incentive program to encourage the sharing of technical knowledge and best practices within the cloud community.

In previous tutorials of this series, we have created our playbook file and two of the four roles. In the first part of the tutorial, we looked at creating our project and overriding the default Ansible behavior. In the second part of the tutorial, we have written the plays into roles.

In this final part of the tutorial series, we will create the two remaining roles. Once the playbook is created, we will run the playbook using Ansible.

Drupal Role

Create a new directory for “drupal” role and subdirectories for tasks.

mkdir -p roles/drupal/tasks

Now, create a new YAML file to write the tasks of “drupal” role.

nano roles/drupal/tasks/main.yaml

The Composer is used to manage the dependencies of a php-based project. Since we will be cloning the Git repository onto the server, we will require Composer to install the dependencies. The task below will run a shell command which will install the latest version of the Composer in the system.

---
- name: install the latest Composer
shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer warn=False
args:
creates: /usr/local/bin/composer

Notice the “creates” keyword in the arguments. It simply verifies if the said file is created or not. The next task installs the Git. Installation can be done using both “package” and “apt” module.

- name: install git
package: name=git state=latest

Now that Git is installed, clone the Drush repository using the “git” module of the Ansible. Drush is a command line tool to install Drupal.

- name: clone Drush repository
git:
repo: https://github.com/drush-ops/drush.git
version: "9.3.0"
dest: /opt/drush

Install the Drush dependencies using Composer.

- name: install Drush dependencies with Composer
shell: composer install
args:
chdir: "/opt/drush"

Create a soft link so that the Drush executable is called directly. The “file” module is used to create the soft links.

- name: create the Drush executable link
file:
src: /opt/drush/drush
dest: /usr/local/bin/drush
state: link

Install MySQL client. MySQL client is used by Drush to write the Drupal database.

- name: install mysql client
package: name=mysql-client state=present

Create the directory to install Drupal. The “file” module is also used to create the directory.

- name: create the Drupal install directory
file:
path: "{{ drupal_site_path }}"
state: directory

Clone the Drupal repository.

- name: clone Drupal repository
git:
repo: http://git.drupal.org/project/drupal.git
version: "{{ drupal_version }}"
dest: "{{ drupal_site_path }}"

Install the Drupal dependencies with the Composer.

- name: install Drupal dependencies with Composer
shell: composer install
args:
chdir: "{{ drupal_site_path }}"
creates: "{{ drupal_site_path }}/vendor/autoload.php"

Use Drush executable to install Drupal. We have used Ansible variables for providing the website name and the administrator credentials in the variable file.

- name: install Drupal
shell: drush si -y --site-name="{{ drupal_site_name }}" --account-name={{ drupal_admin_username }} --account-pass="{{ drupal_admin_pass }}" --db-url=mysql://{{ drupal_db_user }}:{{ drupal_db_pass }}@{{ hostvars['db-server']['ansible_default_ipv4']['address'] }}/{{ drupal_db_name }}
args:
chdir: "{{ drupal_site_path }}"

Finally, set the proper ownership and permissions on Drupal directories and files.

- name: set proper ownership
file:
path: "{{ drupal_site_path }}"
owner: www-data
group: www-data
recurse: yes
- name: set permissions on the settings file
file:
path: "{{ drupal_site_path }}/sites/default/settings.php"
mode: 0744
- name: set permissions on files direcotry
file:
path: "{{ drupal_site_path }}/sites/default/files"
mode: 0777
state: directory
recurse: yes

Save the file and exit from the editor. The role for installing the Drupal is also created. Since we have used some variable in the role we have created, let’s add them in the global variable file we have created.

Edit the common global variables file.

nano group_vars/all.yaml

Append the following variables at the end of the line.

# Drupal Variablesdrupal_version: 8.5.3
drupal_site_path: "/var/www/drupal"
drupal_site_name: "My Drupal Site"
drupal_admin_username: admin
drupal_admin_pass: StrongPass

NGINX Role

Our Drupal website is now installed, but we will need to install NGINX web-server to expose the Drupal site to the internet. The last role “nginx” in our Ansible playbook will install the NGINX web-server along with optional SSL support by let’s encrypt CA. The user can set the variable to true to generate the let’s encrypt certificate in the global variables file. If the SSL cert is generated, then we will configure Drupal to be accessed from the domain name. Else, the Drupal site will be accessed from the IP address of the “web-server” instance.

Create a new directory for “nginx” role and subdirectories for tasks, handlers, and templates.

mkdir -p roles/nginx/{tasks,handlers,templates}

Now, create a new YAML file to write the tasks of the “nginx” role.

nano roles/nginx/tasks/main.yaml

Populate the file with the following tasks. The first task simply checks for the Apache web server. If it is installed, then Ansible removes it from the system. The second task installs the NGINX web server in the system.

---
- name: remove Apache web server
package: name=apache2 state=absent

- name: install nginx web server
package: name=nginx state=present

The next task adds the Certbot repository in the system if letsencrypt_generate variable is set to true and the value of the domain and email variables are provided. This task is an example of using conditional statements in Ansible. Look at the clause “when”. If the conditions provided under the “when” clause satisfies, then only the task will be executed.

- name: add the certbot repository
apt_repository: repo='ppa:certbot/certbot' state=present update_cache=yes
when:
- letsencrypt_generate == true
- letsencrypt_domain is defined
- letsencrypt_email is defined

Similarly, install Certbot for NGINX if the let’s encrypt conditions satisfies.

- name: install certbot
package: name=python-certbot-nginx state=latest update_cache=yes
when:
- letsencrypt_generate == true
- letsencrypt_domain is defined
- letsencrypt_email is defined

Make sure that the NGINX web-server is started because to generate the certificates from Let’s Encrypt, NGINX must be accepting connections.

- name: start and enable nginx service
service: name=nginx state=started enabled=yes

Now, generate the SSL certificates using the Certbot client.

- name: generate letsencrypt certificates
shell: certbot certonly --nginx -d {{ letsencrypt_domain }} -n --agree-tos -m {{ letsencrypt_email }}
args:
creates: /etc/letsencrypt/live/{{ letsencrypt_domain }}
when:
- letsencrypt_generate == true
- letsencrypt_domain is defined
- letsencrypt_email is defined

Remove the default NGINX configuration file.

- name: remove default nginx configuration
file: name=/etc/nginx/sites-enabled/default state=absent
notify:
- restart nginx

Generate a new configuration for Drupal. Notice that we are copying from a template. Later on this tutorial, we will write the NGINX configuration file for Drupal.

- name: generate a new configuration for Drupal
template:
src: drupal.conf
dest: /etc/nginx/sites-enabled/drupal.conf
owner: www-data
group: www-data
notify:
- restart nginx

Finally, create the Cron Job to automatically renew the Let’s Encrypt SSL certificates.

- name: configure the certbot cronjob
cron:
name: letsencrypt_renewal
special_time: weekly
job: /usr/bin/certbot renew --post-hook "systemctl reload nginx"
when:
- letsencrypt_generate == true
- letsencrypt_domain is defined
- letsencrypt_email is defined
- meta: flush_handlers

Save the file and exit from the editor. Our role for NGINX is now created. Let’s add the variables we have used in this role in the variables file.

Edit the common global variables file.

nano group_vars/all.yaml

Append the following variables at the end of the line.

# Let's Encrypt Settings# Set it to false if domain is not configured.
letsencrypt_generate: true
letsencrypt_domain: mydrupal.example.com
letsencrypt_email: me@example.com

Our variables are now in place, let’s proceed to create the handler which restarts the NGINX web-server when requested.

Create a new YAML file to store the Ansible code for the handler.

nano roles/nginx/handlers/main.yaml

Populate the file with the following code.

---
- name: restart nginx
service: name=nginx state=restarted

Save the file and exit from the editor. The last thing we need to do in the “nginx” role is to create the configuration file as template “drupal.conf” in the template directory.

Create the new template named “drupal.conf”.

nano roles/nginx/templates/drupal.conf

Populate the file with the following text.

{% if letsencrypt_generate and letsencrypt_domain is defined and letsencrypt_email is defined %}
server {
listen 80 default_server;
server_name {{ letsencrypt_domain }};
return 301 https://$host$request_uri;
}
server {
listen 443 default_server;
server_name {{ letsencrypt_domain }};
ssl_certificate /etc/letsencrypt/live/{{ letsencrypt_domain }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ letsencrypt_domain }}/privkey.pem;
ssl on;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
ssl_prefer_server_ciphers on;
{% endif %}
{% if not letsencrypt_generate %}
server {
listen 80 default_server;
{% endif %}
root {{ drupal_site_path }}; access_log /var/log/nginx/drupal.access.log;
error_log /var/log/nginx/drupal.error.log;
gzip on;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/html text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml;
gzip_buffers 16 8k;
gzip_disable "MSIE [1-6].(?!.*SV1)";
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~ \..*/.*\.php$ {
return 403;
}
location ~ ^/sites/.*/private/ {
return 403;
}
location ~ (^|/)\. {
return 403;
}
location / {
try_files $uri /index.php?$query_string; # For Drupal >= 7
}
location @rewrite {
rewrite ^/(.*)$ /index.php?q=$1;
}
location ~ '\.php$|^/update.php' {
fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
include fastcgi_params;
include snippets/fastcgi-php.conf;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_intercept_errors on;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
}
location ~ ^/sites/.*/files/styles/ { # For Drpal >= 7
try_files $uri @rewrite;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
}
}

Save the file and exit from the editor. In the configuration file above, you can see that we have used conditional statement with {% if letsencrypt_generate and letsencrypt_domain are defined and letsencrypt_email is defined %} line. Ansible will only process the code block if the condition turns out true. In the file above, if the Let's Encrypt certificates are generated than only it will process the first "if" block. If not, it will process the second "if" block.

Congratulations. You have written the Ansible playbook needed to install Drupal. The following file structure should have been created on your system.

drupal-ansible/
├── ansible.cfg
├── group_vars
│ └── all.yaml
├── hosts
├── playbook.yaml
└── roles
├── drupal
│ └── tasks
│ └── main.yaml
├── mariadb
│ ├── handlers
│ │ └── main.yaml
│ ├── tasks
│ │ └── main.yaml
│ └── templates
│ └── my.cnf
├── nginx
│ ├── handlers
│ │ └── main.yaml
│ ├── tasks
│ │ └── main.yaml
│ └── templates
│ └── drupal.conf
└── php
└── tasks
└── main.yaml
14 directories, 12 files

Running the Playbook

Now that everything is created, run the playbook and let Ansible do the Drupal installation for you.

ansible-playbook playbook.yaml

You should see following out. If you have followed the tutorial correctly, you should not get an error message.

aliyun@ubuntu:~/drupal-ansible$ ansible-playbook playbook.yaml PLAY [all] ***************************************************************************************************TASK [Gathering Facts] ***************************************************************************************
ok: [web-server]
ok: [db-server]
TASK [update and upgrade system packages] ********************************************************************...TASK [nginx : configure the certbot cronjob] *****************************************************************
changed: [web-server]
RUNNING HANDLER [nginx : restart nginx] **********************************************************************
changed: [web-server]
PLAY RECAP ***************************************************************************************************
db-server : ok=6 changed=12 unreachable=0 failed=0
web-server : ok=18 changed=24 unreachable=0 failed=0

Once the playbook completes all the tasks successfully, you can visit https://mydrupal.example.com or http://192.168.0.1 according to your choice of Let’s Encrypt installation. You should see the default Drupal page. You can log in using the credentials supplied in the variables file. You should see that the Drupal is installed and is ready to create a site.

Image for post

Conclusion

In this detailed tutorial, we have learned how to use Ansible by creating a playbook to install Drupal on Ubuntu 16.04. Ansible is a great tool for configuration management due to its simplicity. Adoption is very easy with Ansible, far easier than writing a bash script. It can be used in any use case where repetition is required. Ansible follows the concept of idempotency. Idempotency is doing only necessary changes without side effects. For example, when you install a package using a playbook. On the first run, Ansible will install the package for you. But on the second run of the same playbook, Ansible will just verify if the package is installed. If it is installed, Ansible will just skip the job without reinstalling the package.

Similarly, you can also write playbooks for installing various CMS such as WordPress. The Ansible playbook we have created in this tutorial can also be found on my Github repository.

Reference:

https://www.alibabacloud.com/blog/deploying-drupal-8-using-ansible-playbook%3A-part-3_593823?spm=a2c41.11779138.0.0

Written by

Follow me to keep abreast with the latest technology news, industry insights, and developer trends.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store