This is an as-short-as-possible walk through to learn Ansible. It should help you get going as quick as possible.
Ansible is a tool that can configure (remote) servers. It's simple to read, has many modules and can run from a simple laptop.
With Ansible you describe what tasks should be executed on selected remote hosts in the inventory.
+--- command ------------------+
| ansible-playbook my_play.yml |
+------------------------------+
|
V
+--- ansible.cfg -----+ +--- my_play.yml ------------------+
| inventory=inventory | | - name: configure my hosts |
+---------------------+ | hosts: all |
| | become: yes |
V | gather_facts: yes |
+--- inventory ---+ | |
| my_host_1 |-----> | tasks: |
| my_host_2 | | - name: show ntp_servers |
+-----------------+ | debug: |
| msg: "{{ ntp_servers }}" |
+----------------------------------+
^ |
| V
+--- group_vars/all/ntp.yml ---+ +--- my_host_1 ----------+
| ntp_servers: | | ntp_servers: |
| - name: pool.ntp.org | | - name: pool.ntp.org |
+------------------------------+ +------------------------+
An inventory is a file that describes what servers should be managed. For example:
my_server.example.com
[my_webservers]
my_web_1.example.com
my_web_2.example.com
[amsterdam]
my_web_1.example.com
[london]
my_web_2.example.com
[singapore]
my_server.example.com
[europe:children]
amsterdam
london
[asia:children]
singapore
These variables have a precedence, for the group_vars:
group_vars/all
group_vars/*
This means more specific variables overwrite all
.
In playbooks you can target tasks and roles to run on selected hosts. In this case there are multiple groups, 2 are default
all
- A default group for any host mentioned in the inventory.ungrouped
- Another default group for hosts in the inventory, but not in any group.my_webservers
- This user-defined group can be seen in the inventory.amsterdam
,london
&singapore
- Userdefined groups that contains 1 host.europe
&asia
- A group of group(s). Europe contains 2 cities.
You can make variables (like the ntp_servers
) available per group.
Once you have an inventory defined, you can make certain variables available per host or group.
ntp_servers:
- name: pool.ntp.org
ntp_servers:
- name: nl.pool.ntp.org
ntp_servers:
- name: 1.uk.pool.ntp.org
With inventories and host & group variables you can define all data required by roles to configure a system.
A task is a statement of what Ansible should to, it's the smallest component in Ansible and you can use tasks in playbooks, roles and handlers.
This task copies a file from the Ansible Control node to the server the task is targeted to.
- name: Copy file with owner and permissions
copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
When you are writing more playbooks, you'll see that duplicate code creeps in. You may have 2 playbooks that both configure and start ntp.
That's a perfect opportunity to write a role
. A role is basically a list of tasks that you can pull into a play:
---
- name: configure my servers
hosts: all
become: yes
gather_facts: yes
roles:
- role: buluma.ntp
The role itself has a few files and directories. This is a simplified version of my ntp role.
.
├── defaults
│ └── main.yml <- This contains user-overwritable settings, such as `ntp_servers`.
├── files
│ └── ntpd <- Files that are copied to the remote system.
├── handlers
│ └── main.yml <- Handlers are described further below.
├── meta
│ └── main.yml <- Information for Ansible Galaxy, where Ansible roles can be published.
├── README.md <- Documentation, give this the right amount of attention.
├── requirements.yml <- Depending roles can be downloaded if specified here.
├── tasks
│ └── main.yml <- The logic of the role, a list of tasks.
├── templates
│ └── ntp.conf.j2 <- Files that are rendered and placed on the remote system.
└── vars
└── main.yml <- Variables that are not supposed to be overwritten like `ntp_packages`.
This file contains variables that a user/playbook can easily overwrite. Typically this contains settings for a program or role. You need to choose "sane defaults" so that a user that does not overwrite values still has a working application.
---
ntp_servers:
- name: pool.ntp.org
You can use comments here to guide a user of your role on how to use these variables.
Files in here can be copied to a remote machine. The copying itself needs to be described in tasks/main.yml
.
Handlers contain named tasks that are only started when they are called. I know, it's a lot to take in. Here is an example:
tasks/main.yml
- name: configure ntp
template:
src: ntp.conf.j2
dest: /etc/ntp.conf
notify:
- restart ntp
handlers/main.yml
---
- name: restart ntp
service:
name: ntp
state: restarted
So, only if the task configure ntp
is changed
, the handler restart ntp
is called. You can run roles over and over again, only the times where ntp.conf
is changed, ntp is restarted. Quite logical right?
Handlers run at:
- The end of a play.
- When
meta: flush_handlers
runs.
The order of handers is as they are ordered in the handlers/main.yml
, NOT how they are called.
This file is used to tell Ansible Galaxy how to show your role. Some examples of the contens:
galaxy_info:
author: Michael Buluma
role_name: ntp
description: Install and configure ntp on your system.
license: Apache-2.0
company: ShadowNet
min_ansible_version: 2.8
platforms:
- name: Fedora
versions:
- 33
- 34
galaxy_tags:
- ntp
dependencies: []
A playbook is a list of tasks and roles and some details to understand how to apply them.
---
- name: configure my servers
hosts: all
become: yes
gather_facts: yes
tasks:
- name: show something
debug:
msg: "Hello world"
roles:
- role: buluma.ntp
There are a few parameters that need explaining:
hosts: all
- This is where a playbook is targeted against hosts. This is done using patterns.become: yes
- This tells Ansible to run the play as the root users. Although it's tempting to usebecome: no
and choose when to use the root user when needed, for example per task, it's far easier to simply usebecome: yes
at the play level like this example.gather_facts
- Ansible should run thesetup
module that make facts available. Many tasks and roles benefit from these facts.
There order or tasks, roles and others is:
pre_tasks
roles
tasks
post_tasks
Ansible can get a lot of details from systems, called facts
. A few examples of Ansible facts:
ansible_os_family: RedHat
ansible_pkg_mgr: dnf
ansible_distribution: Fedora
ansible_distribution_major_version: '32'
These facts can simply be used as variables in Ansible.
- name: show the distribution
debug:
msg: "You are running {% raw %}{{ ansible_distribution }}{% endraw %} version {% raw %}{{ ansible_distribution_major_version }}{% endraw %}
Handlers are tasks that can be called when a parent tasks is changed. I know, it's a lot to take in but an example may help:
- name: configure my servers
hosts: all
become: yes
gather_facts: yes
tasks:
- name: configure ntp
template:
src: ntp.conf.j2
dest: /etc/ntp.conf
notify:
- restart ntp
handlers:
- name: restart ntp
service:
name: ntpd
state: restarted
Here you see an (incomplete) example of how handlers can be used. The advantage of a handler is that many tasks from the tasks
list can call handlers, it will only be executed once, at the end of the play or when something calls the meta
module with flush_handlers
.
You can run jobs conditionally using when
. It's like the if
of bash.
- name: install ntp on Fedora
package:
name: ntp
state: present
when:
- ansible_distribution == "Fedora"
The when
statement can handle a list of conditions, this is and "AND" list. To use "OR" simply write or
:
- name: install ntp on RedHat and Debian machines
package:
name: ntp
state: present
when:
- ansible_os_family == "RedHat" or ansible_os_family == "Debian"
You can repeat (most) tasks using a loop
statement. This basically repeats the task and changed "{% raw %}{{ item }}{% endraw %} every run.
- name: copy a file
copy:
src: "{% raw %}{{ item }}{% endraw %}
dest: "/tmp/{% raw %}{{ item }}{% endraw %}
loop:
- my_file_1
- my_file_2
- Logic (code that determines how to configure a system) is best kept in roles.
- Data (information that determines what to configure on a system) is best kept in inventories and host & group variables.
This allows a maintainer of a role to focus on the logic and the implementor to focus on settings that are correct for her/his environment.