Automation with Ansible

IT as Planned

Sample Playbook with Different Original States

Because automatic provisioning has the task of achieving a defined state on a computer but does not necessarily start with the same baseline, I changed one of the certificate files on node02 manually before calling ansible-playbook again (Listing 5).

Listing 5

Modified ansible-playbook Run

ansible-demo-scripts$ ansible-playbook -i inventories/hosts.baremetal site.yml --ask-sudo-pass
sudo password: *******
PLAY [baremetals] ***
GATHERING FACTS ***
ok: [node01.baremetal]
ok: [control01.baremetal]
ok: [control02.baremetal]
ok: [node02.baremetal]
TASK: [base | Install CenterDevice Root CA Certificates] ***
ok: [control02.baremetal] => (item=centerdevice-intermediate-ca.crt)
ok: [control01.baremetal] => (item=centerdevice-intermediate-ca.crt)
ok: [node01.baremetal] => (item=centerdevice-intermediate-ca.crt)
changed: [node02.baremetal] => (item=centerdevice-intermediate-ca.crt)
ok: [control02.baremetal] => (item=centerdevice-root-ca.crt)
ok: [control01.baremetal] => (item=centerdevice-root-ca.crt)
ok: [node01.baremetal] => (item=centerdevice-root-ca.crt)
ok: [node02.baremetal] => (item=centerdevice-root-ca.crt)
TASK: [base | Update root certificate database] ***
changed: [control02.baremetal]
changed: [control01.baremetal]
changed: [node01.baremetal]
changed: [node02.baremetal]
PLAY RECAP ***
control01.baremetal : ok=3 changed=1 unreachable=0 failed=0
control02.baremetal : ok=3 changed=1 unreachable=0 failed=0
node01.baremetal : ok=3 changed=1 unreachable=0 failed=0
node02.baremetal : ok=3 changed=2 unreachable=0 failed=0

The log confirms that Ansible discovered the modified file and replaced it with the original copy, whereas the other files already had the expected content.

Using Variables and Templates

Thus far, I have copied static files to a remote computer and remotely triggered command execution. Now I'll show a more complex case in which all nodes synchronize their internal clocks with a dedicated time server. To allow this to happen, you need to install the matching package, set up the configuration, and make sure the daemon launches automatically at boot time.

Assuming you run your own time server, its address could be hardcoded in the NTP configuration file. To improve maintainability and centralize the configuration, you will instead extract its address, store the address in a variable, and ensure that Ansible dynamically adds it to the service configuration.

In Ansible, variables can have different scopes. They can be valid for a single host, a group of hosts, or an entire site. Because you want to synchronize all of the "baremetal" computers with the NTP servers in the example, the scope of the defined variable is the group. To do this, you need to create a baremetals.yml file in the new ansible-demo-scripts/group_vars directory:

---
# Variables for all baremetal hosts
NTP_SERVERS:
      - 192.168.0.150
      - 192.168.0.151
      - 192.168.0.152

You can then add the steps shown in Listing 6 to the base.yml role.

Listing 6

Additions to base.yml

- name: Install NTP daemon
sudo: true
apt: pkg=ntp state=present
- name: Ensure NTP daemon autostart
sudo: true
service: name=ntp enabled=yes
- name: Setup NTP daemon config
sudo: true
template: src=etc/ntpd.conf.j2
          dest=/etc/ntpd.conf
notify: Restart NTP daemon

The first task ensures that the NTP package is installed using apt if it does not already exist. The next task automatically starts the service when the system boots if the installation package doesn't handle this task automatically or if autostart was disabled for other reasons in the meantime.

Things start to get more exciting in the final task: In a similar style to copy, template transfers a local file to the remote system. However, instead of just copying it one-to-one, the task evaluates the file as a Jinja2 template [4]. This gives you the option of dynamically composing the content based on your own variables and Ansible facts.

In line with the convention, Ansible searches in ansible-demo-scripts/roles/base/templates for templates for the base role. Here I am creating an etc/ntp.conf.j2 file, which is a copy of the regular ntp.conf but contains the modification shown in Listing 7.

Listing 7

Modifications to ntp.conf

...
#
# removed all server lines referring to default ubuntu time servers here.
# these are our own servers:
#
{% for item in NTP_SERVERS %}
server {{ item }}
{% endfor %}
...

This fragment iterates through all the values listed previously in the group variable file in NTP_SERVERS, thus effectively generating three new lines in the final configuration.

The last line (Listing 6) in the template task, notify: Restart NTP daemon, tells Ansible to perform a special action (a handler) if the content of the target file has changed compared with the previous version. This makes sense in that it restarts the service only if the configuration really has changed. This means that you can execute the playbook multiple times without causing unnecessary service interruptions.

This handler is a special task and the last element you need to define. It comes as little surprise that it is stored in ansible-demo-scripts/roles/base/handlers/main.yml:

---
name: Restart NTP daemon
sudo: true
service: name=ntp state=restarted

Following these changes, the playbook can now be executed again (Listing 8).

Listing 8

Executing ansible-playbook Again

ansible-demo-scripts$ ansible-playbook -i inventories/hosts.baremetal site.yml --ask-sudo-pass
sudo password: *******
PLAY [baremetals] ***
GATHERING FACTS ***
...
TASK: [base | Install ntp daemon] ***
changed: [control01.baremetal]
changed: [control02.baremetal]
changed: [node02.baremetal]
changed: [node01.baremetal]
TASK: [base | Setup ntp daemon] ***
changed: [control02.baremetal]
changed: [node02.baremetal]
changed: [control01.baremetal]
changed: [node01.baremetal]
NOTIFIED: [base | Restart ntp daemon] ***
changed: [control02.baremetal]
changed: [node01.baremetal]
changed: [node02.baremetal]
changed: [control01.baremetal]
PLAY RECAP ***
...

The logfile shows that the package was installed, the configurations were set up, and, finally, the service was restarted. A quick glance at the node configuration shows that three lines with concrete IP addresses really have been created:

ansible-demo-scripts$ ssh node01.baremetal \
  grep "server\ 192" /etc/ntp.conf
server 192.168.0.150
server 192.168.0.151
server 192.168.0.152

Now the playbook can be run again to demonstrate that the daemon is not restarted unless the file content has changed (Listing 9).

Listing 9

Another Run of the Playbook

ansible-demo-scripts$ ansible-playbook -i inventories/hosts.baremetal site.yml --ask-sudo-pass
sudo password: *******
PLAY [baremetals] ***
GATHERING FACTS ***
TASK: [base | Ensure ntp daemon autostart] ***
ok: [control01.baremetal]
ok: [node01.baremetal]
ok: [node02.baremetal]
ok: [control02.baremetal]
TASK: [base | Setup ntp daemon config] ***
ok: [control02.baremetal]
ok: [node02.baremetal]
ok: [control01.baremetal]
ok: [node01.baremetal]

Because the configuration files were not modified, the handler was not notified, and the service just kept on running.

Integrating Sensitive Variables

One frequent problem in the use of version management systems relates to the access credentials that are needed as variable content in the scope of an Ansible run to store SSH keys automatically, for example, or be able to access a database.

Of course, you will not want to leave these in the clear in the Git repository for everyone to see. To prevent this happening, Ansible uses the concept of vaults , an encrypted form of YAML file for storing sensitive date of this type.

Using vaults is easy as pie: With the ansible-vault command you can create and edit practical data vaults. When you run the playbook, you can pass in the decryption password at the command line or parse it from a file that can only be accessed by authorized users, preventing the password from appearing in the shell history.

ansible-demo-scripts$ vim .vaultpass
ansible-demo-scripts$ ansible-vault \
  create --vault-password-file \
  ~/.vaultpass secrets.yml

If you now integrate the secrets.yml file with the playbook, it expects you to provide the decryption password for the vault before running the commands:

ansible-demo-scripts$ ansible-playbook \
  -i inventories/hosts.baremetal \
  --vault-password-file ~/.vaultpass \
  --ask-sudo-pass site.yml

The vault file itself can be checked into version management without any problems; it is AES256 encrypted and encoded as a pure text format.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus