Skip to content

ansible

Marcel Schmalzl edited this page Apr 4, 2025 · 9 revisions
  1. Ansible
    1. Main components:
      1. Control node
      2. Inventory (aka hostfile)
      3. Managed nodes (aka hosts)
  2. Installation
  3. Inventory
    1. ini
    2. yml
    3. Inventory commands
      1. Verify the inventory
      2. Run playbook for one inventory
      3. Execute commands on an inventory without a playbook
    4. Inventory structure
  4. Playbooks
    1. Playbooks by example
    2. Facts
    3. Variables
      1. Inventory (host/group vars)
      2. Playbook (vars/vars_files)
      3. Extra variables
      4. Module return variables
    4. Run
    5. Direct Role/Task Execution
  5. Security
    1. Passwords: Avoid interactive password prompts.
    2. no_log: true: Use to prevent sensitive data from being logged.
  6. Best practises
    1. Directory Layout
  7. Misc
  8. Troubleshooting
  9. yml reference (YAML 1.1)

Ansible

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 or ansible-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

Installation

pip install ansible

Inventory

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:

ini

[someHostGroup]
myhost.lan
102.23.02.01

We'll focus, however, on the yml format.

yml

---
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
    • ungrouped: Any host which does not belong to a group

Inventory commands

Verify the inventory

ansible-inventory -i inventory.ini --list

Run playbook for one inventory

# 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

Execute commands on an inventory without a playbook

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 structure

└── 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

Playbooks

= 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
  • 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 to changed)
  • 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)

Playbooks by example

---
- 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

Variables

Usage for all:

# playbook.yml
- hosts: localhost
  tasks:
    - name: Display variables
      debug:
        msg: "Global: {{ my_global_var }}, Host: {{ my_host_var }}"

Inventory (host/group vars)

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

Playbook (vars/vars_files)

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 }}"

Extra variables

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"

Module return variables

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 }}"

Run

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)

Direct Role/Task Execution

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's tasks/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

Security

  • 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.

Best practises

Directory Layout

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)

Misc

Troubleshooting

  • [WARNING]: Could not match supplied host pattern, ignoring: myhosts
    • The host group mentioned in your playbook might not be in the inventory

yml reference (YAML 1.1)

Collection indicators

  • ?: Key indicator
  • :: Value indicator
  • -: Nested series entry indicator
  • ,: Separate in-line branch entries
  • []: Surround in-line series branch
  • {}: Surround in-line keyed branch

Scalar indicators

  • '' : 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')

Alias indicators

  • &: Anchor property
  • *: Alias indicator

Tag property

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")

Document indicators

  • %: Directive indicator
  • ---: Document header
  • ...: Document terminator

Misc indicators

  • #: Throwaway comment indicator
  • ``@`: Both reserved for future use

Special keys

  • =: Default "value" mapping key
  • <<: Merge keys from another mapping

Core types

Default automatic tags

  • !!map: { Hash table, dictionary, mapping }
  • !!seq: { List, array, tuple, vector, sequence }
  • !!str: Unicode string

More types

  • !!set: { cherries, plums, apples }
  • !!omap: [ one: 1, two: 2 ]

Language Independent Scalar types

{ ~, 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

Escape codes

  • 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)

Python 3

(un)fold
Snippets
General
Libs

Linux/bash

(un)fold
Guides
Scripts

Git

(un)fold

C/C++

(un)fold

Video

(un)fold

Databases

(un)fold

Misc

(un)fold

Windows

(un)fold

Mac

(un)fold

SW recommendations

(un)fold

(Angular) Dart

(un)fold
Clone this wiki locally