×
Community Blog Deploying Drupal 8 using Ansible Playbook: Part 3

Deploying Drupal 8 using Ansible Playbook: Part 3

In this multi-part article series, we will be deploying Drupal 8 using Ansible Playbook on an Alibaba Cloud Elastic Compute Service (ECS) instance.

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 Variables

drupal_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.

1

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.

0 0 0
Share on

Alibaba Clouder

2,599 posts | 758 followers

You may also like

Comments