-
Notifications
You must be signed in to change notification settings - Fork 0
ansible
- Ansible
- Installation
- Inventory
- Playbooks
- Security
- Best practises
- Misc
- Troubleshooting
- yml reference (YAML 1.1)
is an automation framework that works agentless by leveraging SSH to access remote machines.
Main components:
- Control node: where Ansible is installed (you run commands like
ansible
,ansible-inventory
oransible-vault
from here)- Multiple are possible but Ansible does not coordinate across them (see AAP if this is desired)
- Inventory (aka hostfile): one or more lists of managed nodes
- Managed nodes (aka hosts): systems to manage via Ansible
pip install ansible
An inventory manages your nodes (e.g. hosts).
- Nodes are specified by FQDN or IP address
- Make sure you added the SSH key of the control node to all of your managed nodes
The inventory consists of a .ini
or .yml
file structure:
[someHostGroup]
myhost.lan
102.23.02.01
We'll focus, however, on the yml
format.
---
all: # All hosts in the inventory (=reserved keyword); optional => group = implied automatically; no need to explicitly define it
hosts:
192.168.1.10:
api.example.com:
children: # Define nested group (=reserved keyword)
webservers:
hosts:
web1.example.com:
dbservers:
hosts:
db1.example.com:
db2.example.com:
---
someHostGroup:
hosts:
a_host: # Reachable via this name as hostname
my_host:
ansible_host: host_alias_fqdn.lan
another_host:
ansible_host: 192.168.1.222
ansible_user: user_used_to_log_in_via_ssh
webserver:
ansible_host: 102.23.02.01:5555 # To define a non-standard SSH port
# or via:
ansible_port: 5555
# Some variable for a specific host
http_port: 443 # Has precedence over http_port in vars below
# Variables valid for all hosts in a group
vars:
a_group_variable: 1234
http_port: 443
http_port: 999 # Variables can be overwritten (order matters!)
someOtherHostGroup:
hosts:
another_host:
ansible_host: another_host.lan
www[01:10:2].helloworld.com: # Matches www01.helloworld.com, www03.helloworld.com, www05.helloworld.com, ... (01 until 10 (inclusive); step size = 2); see also: https://docs.ansible.com/ansible/2.7/user_guide/intro_patterns.html#working-with-patterns
~(sftp|portal)\.helloworld\.com: # Tell ansible you are using a RegEx by prefixing `~`
someMetaGroup:
children:
someHostGroup:
someOtherHostGroup:
- 2 spaces per indent level
-
ansible_*
,hosts
are reserved keywords - More about host patterns
- groups are usually clustered as what (topology, type, ...) / where (geographic location) or when (stage)
-
metagroups are used to organise multiple groups
-
children
is a reserved keyword to include groups (notice the trailing:
after the group names) - Circular relationships are not allowed
-
-
default groups created by Ansible
-
all
: Every host in the inventory- If you want to set variables for the
all
group inside the inventory file, the all group must be the first entry in the file
- If you want to set variables for the
-
ungrouped
: Any host which does not belong to a group
-
ansible-inventory -i inventory.ini --list
# Run only on region1:
ansible-playbook site.yml -i ./inventory.yml --limit region1
# or on a local host file:
ansible-playbook site.yml --limit @my_local_host_list.txt # Note the prefix: `@`; file needs to be comma separated
Ping a inventory group (here someHostGroup
):
ansible -u myUsername someHostGroup -m ping -i inventory.ini
-> you only need -u
if your user name on the managed node(s) is different from the username from the control node
└── inventory
├── 01-inventory-loaded-first.yml
├── 02-inventory-loaded-second.yml
└── 03-inventory-groups-loaded-third.yml
- Internally Ansible merges all inventories into one
- Load order is based on the ASCII character sequence
- If you cross-file reference hosts or groups make sure they are already loaded (-> check load order)
- In our example we define groups in the last file (loaded) to ensure that all hosts mentioned in the group definitions are already loaded
= automation blueprints in .yml
Hierarchy:
Playbook
├── Play
│ ├── Task
│ │ └── Module
│ └── Role
│ ├── Tasks
│ │ └── Task
│ │ └── Module
│ ├── Templates
│ ├── Handlers
│ └── Vars
- Module
- Smallest executable unit (e.g.
ansible.builtin.copy
) in Ansible - = code or binary that Ansible runs on the managed nodes
-
Collections
=> built-in "action"/module groups- Can contain playbooks, roles, modules and plugins
- Install via Ansible Galaxy
- Smallest executable unit (e.g.
- Task: Call of a module (setting the modules parameters if exists)
-
Handlers
: special form of a Task: executes only when notified by a previous Task (status change tochanged
)
-
- Play: Ordered list of tasks to be executed on a group of hosts (mandatory)
- Playbook: Collection of plays
-
Roles
: Reusable Ansible content (tasks, handlers, variables, plugins, templates and files) to be used inside a Play- Roles can be included in plays within a playbook
-
Plugins
: Code that expands Ansibles core capabilities
Plug-ins (executed on the control node) vs. modules (executed on the managed nodes)
---
- name: My first play # Start of Play
hosts: someHostGroup
tasks:
- name: Ping my hosts # Tasks
ansible.builtin.ping: # Doc: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ping_module.html
- name: Print message
ansible.builtin.debug: # Doc: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/debug_module.html#ansible-collections-ansible-builtin-debug-module
msg: Hello world
- name: Print multiple messages
ansible.builtin.debug:
msg:
- "Hello"
- "Hello again!"
- name: Apt package management
hosts: someOtherHostGroup
tasks:
- name: Run equivalent of `apt-get update` as a separate step
become: true # Do something as root (see also: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_privilege_escalation.html)
ansible.builtin.apt: # Doc: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html
update_cache: yes # Equivalent of apt-get update
- block: # Use a `block` (reserved keyword) to apply certain parameters to all tasks (here: become)
- name: Update the repo cache and install the package `pwgen`
ansible.builtin.apt:
name: pwgen
update_cache: yes
- name: Install the package `pwgen` with a more or less (due to the wildcard) specific version or below and allow downgrade
ansible.builtin.apt:
name: pwgen=2.08*
allow_downgrade: yes # Allow downgrading the package
autoremove: true # Automatically invoke `apt autoremove`
- name: Install multiple packages
ansible.builtin.apt:
pkg:
- pwgen # Here attributes like above are not allowed
- git>=2.4*
become: true
- name: Change files
hosts: someOtherHostGroup
tasks:
- name: Ensure X11 forwarding is activated
become: yes
ansible.builtin.lineinfile: # Doc: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html
path: /etc/ssh/sshd_config
regexp: '^(\s*X11Forwarding\s+)(\w+)$'
line: '\1yes' # Use a backreferences in our replacement string (first group: `\1`)
backrefs: yes # Use backreferences in regex (basically if you have groups in the regex you can fill in the groups as part of the result)
validate: 'sshd -t -f %s' # Command to verify the configuration before file replacement (%s provides the temporary file path (must be present))
= automatically gathered information about the managed hosts (collected by the setup
module)
- Read-only (you cannot modify facts directly)
- Dynamically generated
- System-specific
# ...
tasks:
- name: Print all available facts
ansible.builtin.debug:
var: ansible_facts
- name: Print Ansible fact via dict access
ansible.builtin.debug:
var: ansible_facts.distribution
- name: Print Ansible fact via Unsafe Text Access
ansible.builtin.debug:
var: ansible_facts["distribution"]
- name: Print Ansible fact via Unsafe Text and list access
ansible.builtin.debug:
var: ansible_facts["all_ipv6_addresses"][1]
- name: Print Ansible fact type we try to access via type_debug filter
ansible.builtin.debug:
var: ansible_facts["all_ipv6_addresses"][1]|type_debug
The syntax is exactly how you would expect it in Python (lists: [2]
, dict: ansible_facts.memory.mb_real
or ["key access"]
).
Directly access fact on local host:
ansible <hostname> -m ansible.builtin.setup
Usage for all:
# playbook.yml
- hosts: localhost
tasks:
- name: Display variables
debug:
msg: "Global: {{ my_global_var }}, Host: {{ my_host_var }}"
Scope: All hosts in the group; host variables apply to a specific host
# inventory/group_vars/all.yml
my_global_var: "global value"
# inventory/host_vars/localhost.yml
my_host_var: "host specific value" # Host variable, here it applies only to localhost
Scope: Within the playbook
# playbook.yml
- hosts: localhost
vars:
playbook_var: "playbook value"
vars_files:
- vars.yml # Variables from external file
tasks:
- name: Display playbook variable
debug:
msg: "Playbook: {{ playbook_var }}, file: {{ file_var }}"
Scope: Entire playbook run
Via file:
# vars.yml
file_var: "file value"
Via command-line (--extra-vars
):
ansible-playbook playbook.yml --extra-vars "cli_var=cli value"
Scope: Within the play
register
keyword:
- hosts: localhost
tasks:
- name: Get current date
command: date +%Y-%m-%d
register: date_output # Register the command output
- name: display the date
debug:
msg: "the date is {{ date_output.stdout }}"
ansible-playbook -i ./inventory.yml hello.yml
# Multiple inventories
# ... mentioned individually
ansible-playbook -i ./inventory-eu.yml -i ./inventory-na.yml hello.yml
# ... or a whole folder
ansible-playbook -i ./inventory hello.yml
# Ask for become password
ansible-playbook install.yml -i ./inventory.yml -K # -K == --ask-become-pass; -k == --ask-pass (connection password)
- More about
become
Testing Ansible roles and tasks without creating a dedicated test playbook:
ansible-playbook -i <inventory> <role_tasks_file> --extra-vars "<role_vars>" --check
# or alternatively
ansible -i <inventory> all -m ansible.builtin.include_role -a "name=<role_name> <role_vars>" --check
-
<inventory>
: Path to your inventory file or list of comma-separated hosts -
<role_tasks_file>
: Path to the role'stasks/main.yml
-
--extra-vars
/-e
: Define required role variables-
<role_vars>
: Any variables needed by the role (e.g.,username=test_user
) - Specifying a host with
ansible_host=<target_host>
might confuse Ansible if it's also in the inventory and might raise an error
-
-
--check
: Runs in dry-run mode (simulates changes)
Examples:
ansible-playbook -i 'ansible_test_user@ubuntu-24-10-base,' roles/some_ansible_role/tasks/main.yml -e "some_req_arg=arg_value" --check
# or
ansible -i 'ansible_test_user@ubuntu-24-10-base,' all -m ansible.builtin.include_role -a "name=some_ansible_role some_req_arg=arg_value" --check
Limitations:
- Handlers are not executed
- Role dependencies (defined in
meta/main.yml
) are not resolved - Behaviour might differ from execution within a full playbook
-
Passwords: Avoid interactive password prompts.
- Use Ansible Vault for secure password storage.
- Use the
expect
module for unavoidable interactive prompts.
-
no_log: true
: Use to prevent sensitive data from being logged.
production # inventory file for production servers
staging # inventory file for staging environment
group_vars/
group1.yml # here we assign variables to particular groups
group2.yml
host_vars/
hostname1.yml # here we assign variables to particular systems
hostname2.yml
library/ # if any custom modules, put them here (optional)
module_utils/ # if any custom module_utils to support modules, put them here (optional)
filter_plugins/ # if any custom filter plugins, put them here (optional)
site.yml # master playbook
webservers.yml # playbook for webserver tier
dbservers.yml # playbook for dbserver tier
roles/
common/ # this hierarchy represents a "role"
tasks/ #
main.yml # <-- tasks file can include smaller files if warranted
handlers/ #
main.yml # <-- handlers file
templates/ # <-- files for use with the template resource
ntp.conf.j2 # <------- templates end in .j2
files/ #
bar.txt # <-- files for use with the copy resource
foo.sh # <-- script files for use with the script resource
vars/ #
main.yml # <-- variables associated with this role
defaults/ #
main.yml # <-- default lower priority variables for this role
meta/ #
main.yml # <-- role dependencies
library/ # roles can also include custom modules
module_utils/ # roles can also include custom module_utils
lookup_plugins/ # or other types of plugins, like lookup in this case
webtier/ # same kind of structure as "common" was above, done for the webtier role
monitoring/ # ""
fooapp/ # ""
(from https://docs.ansible.com/ansible/2.8/user_guide/playbooks_best_practices.html#content-organization)
- Special variables
-
Ansible configuration
- Special command line options and other things can be configured statically in Ansible via the
ansible.cfg
file
- Special command line options and other things can be configured statically in Ansible via the
-
[WARNING]: Could not match supplied host pattern, ignoring: myhosts
- The host group mentioned in your playbook might not be in the inventory
-
?
: Key indicator -
:
: Value indicator -
-
: Nested series entry indicator -
,
: Separate in-line branch entries -
[]
: Surround in-line series branch -
{}
: Surround in-line keyed branch
-
''
: Surround in-line unescaped scalar ('' escaped ') -
"
: Surround in-line escaped scalar (see escape codes below) -
|
: Block scalar indicator -
>
: Folded scalar indicator -
-
: Strip chomp modifier ('|-' or '>-') -
+
: Keep chomp modifier ('|+' or '>+') -
1-9
: Explicit indentation modifier ('|1' or '>2') # Modifiers can be combined ('|2-', '>+1')
-
&
: Anchor property -
*
: Alias indicator
Usually unspecified
-
none
: Unspecified tag (automatically resolved by application) -
!
: Non-specific tag (by default, "!!map"/"!!seq"/"!!str") -
!foo
: Primary (by convention, means a local "!foo" tag) -
!!foo
: Secondary (by convention, means "tag:yaml.org,2002:foo") -
!h!foo
: Requires "%TAG !h! " (and then means "foo") -
!<foo>
: Verbatim tag (always means "foo")
-
%
: Directive indicator -
---
: Document header -
...
: Document terminator
-
#
: Throwaway comment indicator - ``@`: Both reserved for future use
-
=
: Default "value" mapping key -
<<
: Merge keys from another mapping
Default automatic tags
-
!!map
: { Hash table, dictionary, mapping } -
!!seq
: { List, array, tuple, vector, sequence } -
!!str
: Unicode string
-
!!set
: { cherries, plums, apples } -
!!omap
: [ one: 1, two: 2 ]
{ ~, null }
: Null (no value)
[ 1234, 0x4D2, 02333 ]
: [ Decimal int, Hexadecimal int, Octal int ]
[ 1_230.15, 12.3015e+02 ]
: [ Fixed float, Exponential float ]
[ .inf, -.Inf, .NAN ]
: [ Infinity (float), Negative, Not a number ]
{ Y, true, Yes, ON }
: Boolean true
{ n, FALSE, No, off }
: Boolean false
? !!binary >
: e.g.: R0lG...BADS=
: >-
: Base 64 binary value
- Numeric :
{ "\x12": 8-bit, "\u1234": 16-bit, "\U00102030": 32-bit }
- Protective:
{ "\\": '\', "\"": '"', "\ ": ' ', "\<TAB>": TAB }
- C :
{ "\0": NUL, "\a": BEL, "\b": BS, "\f": FF, "\n": LF, "\r": CR, "\t": TAB, "\v": VTAB }
- Additional:
{ "\e": ESC, "\_": NBSP, "\N": NEL, "\L": LS, "\P": PS }
- ...
from https://yaml.org/refcard.html (formatting modified)
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License *.
Code (snippets) are licensed under a MIT License *.
* Unless stated otherwise