Changing a system's configuration with Ansible isn't much more difficult than provisioning a new system.
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
Now, we'll create the playbook to configure the system. Perform the following steps:
~/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
~/playbooks/networking.handlers.yml
file with the following content:- name: reset-sysctl action: command /sbin/sysctl -p
~/playbooks/ntp-client.handlers.yml
file with the following content:- name: restart-ntpd action: service name=ntpd state=restarted enabled=yes
~/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
~/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
~/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:
~/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
~/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 %}
~/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
~/playbooks/templates/etc/ntp/step-tickers.el7
file with the following content:# {{ ansible_managed }} {% for ntp in ntp_servers %} {{ ntp }} {% endfor %}
~/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 }
~]# 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 ~]#
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:
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 }
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.