Creating a playbook to perform system configuration tasks

Changing a system's configuration with Ansible isn't much more difficult than provisioning a new system.

Getting ready

For this recipe, we will need the following facts for the new host:

  • ntp_servers
  • dns_servers
  • dns_search

We'll also need to have a couple of templates to provision the following files:

  • /etc/logrotate.d/syslog
  • /etc/ntp.conf
  • /etc/ntp/step-tickers
  • /etc/resolv.conf

How to do it…

Now, we'll create the playbook to configure the system. Perform the following steps:

  1. Create a ~/playbooks/config.yml playbook with the following content:
    - name: Configure system
      hosts: all
    
      handlers:
      - include: networking.handlers.yml
      - include: ntp-client.handlers.yml
      
      tasks:
      - include: networking.tasks.yml
      - include: ntp-client.tasks.yml
      - include: logrotate.tasks.yml
  2. Create a ~/playbooks/networking.handlers.yml file with the following content:
      - name: reset-sysctl
        action: command /sbin/sysctl -p
  3. Now, create a ~/playbooks/ntp-client.handlers.yml file with the following content:
      - name: restart-ntpd
        action: service name=ntpd state=restarted enabled=yes
  4. Create a ~/playbooks/networking.tasks.yml file with the following content:
      - name: Set the hostname
        action: hostname name={{ inventory_hostname }}
    
      - name: Deploy sysctl template to disable ipv6
        action: template src=templates/etc/sysctl.d/ipv6.conf.el7 dest=/etc/sysctl.d/ipv6.conf
        notify: reset-sysctl
        
      - name: 'Detect if ::1 is in /etc/hosts'
        action: shell /bin/egrep '^s*::1.*$' /etc/hosts
        register: hosts_lo_ipv6
        failed_when: false
        always_run: yes
      
      - name: 'Remove ::1 from /etc/hosts'
        action: lineinfile dest=/etc/hosts regexp='^s*::1.*$' state=absent
        when: hosts_lo_ipv6.rc == 0
    
      - name: Configure DNS
        action: template src=templates/etc/resolv.conf.el7 dest=/etc/resolv.conf
  5. Next, create a ~/playbooks/ntp-client.tasks.yml file with the following content:
      - name: "Install ntpd (if it's not installed already)"
        action: yum name=ntp state=present
        notify: restart-ntpd
        
      - name: Configure the ntp daemon
        action: template src=templates/etc/ntp.conf.el7 dest=/etc/ntp.conf
        notify: restart-ntpd
        
      - name: Configure the step-tickers
        action: template src=templates/etc/ntp/step-tickers.el7 dest=/etc/ntp/step-tickers
        notify: restart-ntpd
  6. Create a ~/playbooks/logrotate.tasks.yml file with the following content:
      - name: Configure logrotate for rsyslog
        action: template src=templates/etc/logrotate.d/syslog.el7 dest=/etc/logrotate.d/syslog

This is it for the playbook. Now we need to create the templates:

  1. First, create a ~/playbooks/templates/etc/sysctl.d/ipv6.conf.el7 file with the following content:
    # {{ ansible_managed }}
    net.ipv6.conf.all.disable_ipv6 = 1
    net.ipv6.conf.default.disable_ipv6 = 1
    net.ipv6.conf.lo.disable_ipv6 = 1
    
  2. Then, create a ~/playbooks/templates/etc/resolv.conf.el7 file with the following content:
    # {{ ansible_managed }}
    search {{ dns_search|join(' ') }}
    {% for dns in dns_servers %}
    nameserver {{ dns }}
    {% endfor %}
    
  3. Create a ~/playbooks/templates/etc/ntp.conf.el7 file with the following content:
    # {{ ansible_managed }}
    
    driftfile /var/lib/ntp/drift
    
    restrict default nomodify notrap nopeer noquery
    
    restrict 127.0.0.1
    restrict ::1
    
    {% for ntp in ntp_servers %}
    server {{ ntp }} iburst
    {% endfor %}
    includefile /etc/ntp/crypto/pw
    
    keys /etc/ntp/keys
    
    disable monitor
    
  4. Next, create a ~/playbooks/templates/etc/ntp/step-tickers.el7 file with the following content:
    # {{ ansible_managed }}
    {% for ntp in ntp_servers %}
    {{ ntp }}
    {% endfor %}
    
  5. Create a ~/playbooks/templates/etc/logrotate.d/syslog.el7 file with the following content:
    # {{ ansible_managed }}
    /var/log/cron
    /var/log/maillog
    /var/log/messages
    /var/log/secure
    /var/log/spooler
    {
        daily
        compress
        delaycompress
        dateext
        ifempty
        missingok
        nocreate
        nomail
        rotate 365
        sharedscripts
        postrotate
            /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
        endscript
    }
    
  6. Then, deploy the playbook to a newly created host by executing the following command:
    ~]# ansible-playbook --limit newhost ~/playbooks/config.yml
    PLAY [Configure system] **************************************
    
    GATHERING FACTS **********************************************
    ok: [newhost]
    
    TASK: [Set the hostname] *************************************
    skipping: [newhost]
    ok: [newhost]
    
    TASK: [Deploy sysctl template to disable ipv6] ***************
    changed: [newhost]
    
    TASK: [Detect if ::1 is in /etc/hosts] ***********************
    changed: [newhost]
    
    TASK: [Remove ::1 from /etc/hosts] ***************************
    changed: [newhost]
    
    TASK: [Configure DNS] ****************************************
    changed: [newhost]
    
    TASK: [Install ntpd (if it's not installed already)] *********
    ok: [newhost]
    
    TASK: [Configure the ntp daemon] *****************************
    changed: [newhost]
    
    TASK: [Configure the step-tickers] ***************************
    changed: [newhost]
    
    TASK: [Configure logrotate for rsyslog] **********************
    changed: [newhost]
    
    NOTIFIED: [reset-sysctl] *************************************
    skipping: [newhost]
    ok: [newhost]
    
    NOTIFIED: [restart-ntpd] *************************************
    changed: [newhost]
    
    PLAY RECAP ***************************************************
    newhost            : ok=9  changed=8  unreachable=0  failed=0  
    ~]#
    

There's more…

The guys at Ansible are really smart people, and they have Ansible packed with lots of power tools. Two that are worth mentioning here and are lifesavers for debugging your playbooks are --check and --diff.

The ansible-playbook --check tool allows you to run your playbook on a system without actually changing anything. Why is this important, you ask? Well, the output of the playbook will list which actions of the playbook will actually change anything on the target system.

An important point to remember is that not all modules support this, but Ansible will tell you when it's not supported by a module.

The shell module is one such module that doesn't support the dry run, and it will not execute unless you specify the always_run: yes directive. Be careful with this directive as if the action would change anything, this directive will cause this change to be applied, even when specifying --check.

I added the 'Detect if ::1 is in /etc/hosts' action to the networking.tasks.yml file with the always_run: yes directive. This specific action just checks whether the line is present. The ergep returns code 0 if it finds a match and 1 if it doesn't. It registers the result of the shell action to a variable (hosts_lo_ipv6).

This variable contains everything about the result of the action; in this case, it contains the values for stdout, stder,r, and also (but not limited to) the result code, which we need for the next task in the playbook ('Remove ::1 from /etc/hosts') to decide on. This way, we can introduce a manual form of idempotency into the playbook for modules that cannot handle idempotency due to whatever restrictions.

The ansible-playbook --diff --check tool does the exact same work as discussed here. However, it comes with an added bonus: it shows you what exactly will be changed in the form of a diff -u between what it actually is and what it's supposed to be. Of course, once again, the module has to support it.

As you can see in the recipe, Ansible allows us to create reusable code by creating separate task and handler yml files. This way, you could create other playbooks referring to these files, without having to reinvent the wheel.

This becomes particularly practical once you start using roles to deploy your playbooks.

Roles allow you to group playbooks and have them deployed according to the needs (that is, roles) of your server.

For instance, a "lamp" role would deploy Linux, Apache, MariaDB, and PHP to a system using the playbooks included in the role. Roles can define dependencies. These dependencies are other roles, and thus, the "lamp" role could be broken down into three more roles that may be more useful as separate roles: Linux, Dbserver, and ApachePHP.

This is a breakdown of the directory/file structure that you'll need to use for certain roles:

File structure

Description

roles/

The container for all roles to be used by Ansible.

roles/<role>

This is the container for your role.

roles/<role>/files

This contains the files to be copied using the copy module to the target hosts.

roles/<role>/templates

This contains the template files to be deployed using the template module.

roles/<role>/tasks

This is where the tasks go to perform all the necessary actions.

roles/<role>/tasks/main.yml

This playbook is automatically added to the play when this role is applied to a system.

roles/<role>/handlers

This is the location of your role handlers.

roles/<role>/handlers/main

This set of handlers is automatically added to the play.

roles/<role>/vars

This location holds all the variables for your role.

roles/<role>/vars/main.yml

This set of variables is automatically applied to the play.

roles/<role>/defaults

This is the directory to hold the defaults for any fact you may need. The facts/variables defined in this way have the lowest priority, meaning that your inventory will win in the event that a fact is defined in both.

role/<role>/defaults/main.yml

This set of defaults is automatically added to the play.

role/<role>/meta

This directory holds all the role dependencies for this role.

role/<role>/meta/main.yml

This set of dependencies is automatically added to the play.

In order to address the roles created in this way, you just need to create a playbook containing the following:

- name: Deploy LAMP servers
  hosts: lamp
  roles:
  - linux
  - DBserver
  - Apache-PHP

Alternatively, you could create a role lamp that has Linux, DBserver, and ApachePHP as the dependencies in the meta/main.yml file by creating it with the following contents:

dependencies:
  - { role: linux }
  - { role: DBserver, db_type: mariadb }
  - { role: Apache-PHP }

See also

For more information on Ansible Roles and Includes, go to http://docs.ansible.com/ansible/playbooks_roles.html.

For more information on playbooks, go to http://docs.ansible.com/ansible/playbooks.html.

For more information on Ansible templates, go to http://docs.ansible.com/ansible/modules_by_category.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset