Ansible templates and handlers

Ansible tutorial – part 3

In this part we will use templates and handlers in Ansible.

A short word about Ansible tutorial

This is multipart series about Ansible, great tool for provisioning and configuration management. You will learn how to create useful roles and use Ansible in efficient way. You will also pick up some good practices:)

If you like tl;dr take a look at shorter veresion of this tutorial on GitHub: https://github.com/blacksaildivision/ansible-tutorial
There are also some examples!

Part 1: Intro to Ansible and Inventory files
Part 2: Playbooks and tasks
Part 3: Templates and handlers 
Part 4: Variables

 

Ansible templates

Templates are simple text files that we can use in Ansible. Most of the time you will use them to replace configuration files or place some documents on the server.

Start from creating new directory inside our nginx role and name it templates . It should be on the same level as tasks directory. This it the place where we will store our templates.

Let's say that we want to change the configuration of nginx. We will use simplest way and we will just replace whole nginx.conf file. Inside template directory create a file and name it for instance nginx.conf.j2  J2 is extension of Jinja2 templating language that Ansible is using.

Copy+paste following content into that file:

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

We just logged in into the server and copied the contents of /etc/nginx/nginx.conf file. This is most common way to obtain configs for template files. Copy their contents from server after installation of the given tool. We can be sure that this is valid config and it should work out of the box.

Task for template

We need the task that will replace current file on the server with the template.

Just before the task that checks if nginx is working add another task:

- name: copy nginx configuration file
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: u=rw,g=r,o=r
  tags: [nginx, config]

We need to name the task first. After that we are using template module from Ansible. It takes couple of parameters. Two mandatory and couple of optional parameters.

  • src  is required parameter. You need to specify name of the template file from templates directory. In our case it's the file we just created - nginx.conf.j2
  • dest  is another required parameter. It's the path where the file should be placed on the server. Nginx in default installation keeps it config files in /etc/nginx directory. It's important to use filename as well, not only directory!

Rest of the parameters are optional, but when you are using template module, good practice is to specify the ownership and file permissions.

  • owner  is username that should own the file, like in chown command
  • group  is the group that should own the file, like in chown command
  • mode  is set of permissions for the file, like from chmod command. There are couple of ways to set the permissions. We prefer the syntax like we used in the task above. But you can also specify numeric version like that:
mode: 0644

Handlers

As you might now, changing something in configuration file won't give any results without restarting nginx. One way to solve that problem, would be to edit service task in our role and set the state to restarted  instead of started . It would result in restarting nginx. But if you would run this playbook over and over again, on each run it would restart nginx, which should not happen.

It would be the best if we could restart nginx automatically, but only when there are changes in configuration file.

Handlers to the rescue! Ansible comes with awesome feature called handlers. You can execute handler, only when given task will have status changed.

Edit the template task above and add notifier:

- name: copy nginx configuration file
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: u=rw,g=r,o=r
  notify: restart nginx
  tags: [nginx, config]

You can add notifier to any task. You must specify the name of the notifier. There are almost no restrictions, it's just a string.

Next, we need to add yet another directory to our role and call it handlers  (directory should be on the same level as tasks and templates).

Inside that directory create text file and name it main.yml  (All main.yml files are automatically included by Ansible).

Place following content in handlers/main.yml  file, you just created:

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

Each handler should have a name. You need to pass the name to notify  in task like we did in example above. Next you can pick any module you want from Ansible and use it. Most of the time it'll be service module. Here we can use restarted state.

When something will change in the template, task will result in changed status. Handler will be notified, will execute and it will restart nginx.

Run your playbook couple of times, and make sure that you will have everything to OK in recap. Change the template and add epoll support for nginx in templates/nginx.conf.j2  file:

events {
    worker_connections 1024;
    use epoll;
}

Run playbook. You will notice that the handler will execute and will restart nginx.

  • Libert Schmidt

    there was an error with the nginx.conf in ubuntu 16.04, you can copy this default config that works here:
    https://gist.github.com/nishantmodak/d08aae033775cb1a0f8a

    Good tutorial

  • Am I missing something? It looks like you are using the template module to do the work of the copy module. I don’t see any logic or variable replacement or anything that changes when the template is used in the nginx.conf.j2 file. Why would I do this instead of using copy?

    • Krystian

      Hi there and thank you for your comment.
      Yes, you are right, you could use copy module instead of template here. This is just a simple example where we just copy the full configuration file to the server. Copy will work just fine!

      However files that you copy to the server usually have variables or Jinja2 instructions. When you are using copy module, they won’t be replaced, file will be copied as-is.

      I usually prefer using template module, as they are performing similar job, but I don’t have to update the task later on when I need to add some variables in file you want to copy:)