In the previous tutorial, we walked through setting up Ansible on both the control (master) node and on target nodes. Now let’s look at using Ansible capabilities.
One cool thing about Ansible is that hosts can have various roles, and you can layer these roles. So for example you could have roles like this:
- a “common” role that contains tasks to be run on all hosts
- a “backup-client” role for hosts that are backed up
- a “db” role for hosts that operate as database servers
etc. In this tutorial, we’ll have a “common” role that we intend to run on all hosts. That we’ll create another role called ‘db’ for database servers.
In /ansible, create the following playbook file called db.yml:
---
- hosts: db
roles:
- common
Note that we are using YAML, which is very fussy about syntax, paticular spaces, so if you get an error, make sure it’s as show above.
Ansible comes with a wide variety of modules, which are packages that can be used to issue commands on target hosts. These cover a ton of common tasks. Some examples:
- the ‘apt’ module can be used to install and remove packages using apt on Debian. There are also ‘yum’, ‘pacman’ and other package manager modules.
- modules such as ‘user’, ‘group’, etc. can add/remove users and groups
- locale_gen, timezone, and other modules can be used for system configuration
- the ‘postgresql*’ set of modules and the ‘mysql*’ modules can be used to add/remove databases, add/remove users, etc.
Etcetera. So if you want to make sure that a certain package is available on your server, you don’t need to code dpkg, apt-get, etc. commands. You can just use the relevant Ansible module.
If something you need is not covered by a stock module, there are also modules for modifying files, making sure a line is present in a file, etc. which allow for configurations not covered by the Ansible distributed modules. And of course you can always copy a script from your control node and execute it.
See the docs for a full list of all modules and capabilities.
Create the following directory structure:
mkdir -p /ansible/roles/common/tasks
Now we’ll create the actual tasks we’re going to execute for the ‘common’ role. In /ansible/roles/common/tasks, create the file main.yml as follows. You do not need to include the comments – they are explanatory text for purposes of this tutorial.
The ‘name’ portion of each task is whatever you choose to call that task. The following line contains the module name followed by a colon, and then specific arguments and options for that module.
---
# this task will run dpkg-reconfigure locales and ensure that
# en_US.UTF-8 is present. Modify for your locale.
- name: locale generation
locale_gen: name=en_US.UTF-8 state=present
# this task will run apt-get update
- name: apt-get update
apt: update_cache=yes
# this task will run apt-get upgrade
- name: apt-get upgrade
apt: upgrade=dist
# this task will install some packages we want on all hosts
- name: basic packages
apt: name=bsd-mailx,bzip2,cron,dnsutils,gpg,git,man,sqlite,unzip,vim,wget,whois,zip state=latest
# this task will set the localtime to US/Pacific. Modify to taste.
- name: set timezone
timezone:
name: US/Pacific
# this task will make sure that cron is enable in systemd
- name: cron enable
service: name=cron enabled=yes state=started
# this task ensures that root's .bash_profile exists
- name: make sure /root/.bash_profile exists
file:
path: /root/.bash_profile
state: touch
# this task ensures that 'set -o vi' is in root's .bash_profile
- name: set -o vi for root
lineinfile:
path: /root/.bash_profile
state: present
regexp: '^set -o vi'
line: set -o vi
Then run the ansible-playbook file against db.yml:
root@master:/ansible# ansible-playbook db.yml
PLAY (db) **********************************************************************
TASK (Gathering Facts) *********************************************************
ok: (target.example.com)
TASK (common : locale generation) **********************************************
ok: (target.example.com)
TASK (common : apt-get update) *************************************************
changed: (target.example.com)
TASK (common : apt-get upgrade) ************************************************
ok: (target.example.com)
TASK (common : basic packages) *************************************************
ok: (target.example.com)
TASK (common : set timezone to US/Pacific) *************************************
ok: (target.example.com)
TASK (common : cron enable) ****************************************************
ok: (target.example.com)
TASK (common : make sure /root/.bash_profile exists) ***************************
changed: (target.example.com)
TASK (common : set -o vi for root) *********************************************
changed: (target.example.com)
PLAY RECAP *********************************************************************
target.example.com : ok=9 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Now you may say to yourself “so you’ve set the locale and timezone and installed some packages – so what?” But the key points here are:
- You didn’t have to do it manually.
- Because it’s automated, it’s done identically every single time.
- You can do this as easily on one host as on a thousand hosts.
- Ansible allows you to set roles that baseline your environment. So every single host you use will be setup exactly as you wish.
- You can run these commands every night to make sure no host drifts from the configuration you want. Think of these as “policies” that enforce how you want each server to be configured.
Modify your db.yml to add a db-server role:
---
- hosts: db
roles:
- common
- db-server
Then:
mkdir -p /ansible/roles/db-server/tasks
And create a main.yml there with these tasks:
---
# make sure that postgres is installed
- name: postgres package
apt: name=postgresql-11,python-ipaddress state=latest
# make sure postgresql is configured to start on boot
- name: postgres enable
service: name=postgresql enabled=yes state=started
# enable md5 (password) connections locally
- name: modify pg_hba.conf to allow md5 connections
postgresql_pg_hba:
dest: /etc/postgresql/11/main/pg_hba.conf
contype: local
users: all
databases: all
method: md5
state: present
Now run
ansible-playbook db.yml
You’ll see Ansible walk through all the ‘common’ tasks, and then:
TASK (db-server : postgres packages) *******************************************
changed: (target.example.com)
TASK (db-server : postgres enable) *********************************************
ok: (target.example.com)
TASK (db-server : modify pg_hba.conf to allow md5 connections) *****************
changed: (target.example.com)
Other PostgreSQL modules allow us to create databases, setup users, grant permissions, etc.
Ansible allows the easy distribution of files, either for configuration or application purposes. It also comes with a powerful templating package called Jinja2 that can modify these templates so that they are customized properly for each system.
Let’s add a mail-server role. We’ll use postfix.
Modify db.yml again:
---
- hosts: db
roles:
- common
- db-server
- mail-server
Then execute:
mkdir -p /ansible/roles/mail-server/tasks
And edit /ansible/roles/mail-server/tasks/main.yml:
---
# ensure postfix packages are installed
- name: postfix package package
apt: name=postfix state=latest
# template /etc/mailname
- name: /etc/mailname
template: src=/ansible/src/mailname.j2 dest=/etc/mailname owner=root group=0 mode=0644
# copy /etc/postfix/main.cf
- name: postfix main.cf
template: src=/ansible/src/main.cf.j2 dest=/etc/postfix/main.cf owner=root group=0 mode=0644
# make sure postfix is configured to start on boot and restart it
# in case main.cf is changed
- name: postfix enable and restart
service: name=postfix enabled=yes state=restarted
# set the root: alias in /etc/aliases
- name: root alias in /etc/aliases
lineinfile:
path: /etc/aliases
state: present
regexp: '^root:'
line: 'root: someone@somewhere.com'
# run newaliases
- name: newaliases
command: /usr/bin/newaliases
Now let’s create our templates. In /ansible/src/mailname.j2, enter this text:
{{ ansible_host }}
This is a Jinja2 template (hence the .j2 ending). Text in between the double braces will be replaced with variables. Ansible supports many different variables. In this case we are using ‘ansible_host’ which will be replaced with ‘target.example.com’.
Here is /ansible/src/main.cf.j2, our postfix configuration:
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 2
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = {{ ansible_node_name }}
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = $myhostname, localhost.example.com , localhost
relayhost =
mynetworks = 127.0.0.0/8 (::ffff:127.0.0.0)/104 (::1)/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
Of course many Postfix variables could be set – this is a tutorial on Ansible not Postfix.
Now run:
ansible-playbook db.yml
And after the ‘common’ and ‘db-server’ sections, you’ll see the ‘mail-server’ tasks run:
TASK (mail-server : postfix package package) ***********************************
changed: (target.example.com)
TASK (mail-server : /etc/mailname) *********************************************
changed: (target.example.com)
TASK (mail-server : postfix main.cf) *******************************************
changed: (target.example.com)
TASK (mail-server : postfix enable and restart) ********************************
changed: (target.example.com)
TASK (mail-server : root alias in /etc/aliases) ********************************
changed: (target.example.com)
TASK (mail-server : newaliases) ************************************************
changed: (target.example.com)
And if we look on the system, we see that the proper template substitutions have been made:
root@master:/ansible# cat /etc/mailname
master.example.com
Although we’ve only scratched the surface of Ansible’s capabilities, hopefully you can see the tremendous power and flexibility of the product. With Ansible playbooks, you can both setup new systems quickly and enforce policies on a continuous basis.
raindog308
I’m Andrew, techno polymath and long-time LowEndTalk community Moderator. My technical interests include all things Unix, perl, python, shell scripting, and relational database systems. I enjoy writing technical articles here on LowEndBox to help people get more out of their VPSes.