Ansible - kamialie/knowledge_corner GitHub Wiki

Contents

General

ansible-pull gets the playbook from VCS and runs on the local system. Can be implemented as decentralized management, where each "self-managed" node runs the command on schedule.

ad-hoc

$ ansible HOST_PATTERN -m MODULE [-a LIST_OF_ARGUMENTS] [-i INVENTORY]
$ ansible all -m ping

Some modules support --diff and --check flags. --diff flags also shows the differences being made in git diff format. --check runs the command in dry mode, thus showing what will happen, if you actually run the command. Two flags together would output complete info on changes that will apply.

Check connection to hosts:

$ ansible all -m ping -i inventory

Facts about host:

$ ansible -m setup localhost

Modules

File:

  • copy - copy local file to remote
  • file - set permissions and other properties
  • lineinfile - ensure particular line is present or not
  • synchonize - synchonize content using sync

System:

  • firewalld - manage arbitrary ports and services
  • reboot
  • service - manage services
  • user - add, remote, manage users

Net tools:

  • get_url - download file over HTTP(S), FTP
  • nmcli - manage networking
  • uri - interact with web services and communicate with API

Command:

  • command - runs commands directly
  • shell - runs commands in shell
  • raw - bypasses module subsystem (does not require Python installation on remote); use case - no Python, network devices

Debug module is useful for debugging variables and expressions. msg argument print cutomized message (can contain variables). var argument is used to print variable (no need to enclose in {{ }}). Mutually exclusive with msg. verbosity argument is used to set on which verbocity level to run this module. F.e. if set to 3, then will run, if playbook was executed with -vvv parameter.

Assert module checks that given expressions are true with an optional custom message. that is a list of string conditions (same as when argument for the task). quite boolean argument to avoid verbose output. success_msg and fail_msg set custom message to output depending on result.

- assert:
    that:
      - my_param <= 10
      - my_param > 1
    fail_msg: "should be between 1 and 10"
    success_msg: "my_param is valid"

Fail module forces playbook to fail. Used for testing conditionals. msg argument is used to output message.

Config

The order of places where Ansible looks for its configuration file:

  • ANSIBLE_CONFIG environment variable, if set, is the path to configuration file
  • ./ansible.cfg - in Ansible command's current working directory
  • ~/.ansible.cfg
  • /etc/ansible/ansible.cfg - default Ansible configuration file

Use ansible --version or ansible-config --version commands to see which file is being used.

Two basic section: defaults that sets defaults for Ansible operations and privilege_escalation, which configures how Ansible performs privilege escalation.

Default settings are inventory file path, remote user to use, whether to ask password, etc.

Settings to control privilege escalation:

  • become - control whether to use escalation automatically (default is no, can be overridden at various levels, play, task...)
  • become_user - what user to become (default is root)
  • become_method - sudo by default, can use su and others
  • become_ask_pass - whether to ask for password (default is no)

Output variables that are changed with custom config file:

$ ansible-config dump --only-changed

Setup

Simple setup, file entries in host_vars directory must match the host name in inventory; var files are in yaml format with just key-value pairs:

project
|-- ansible.cfg
|-- host_vars
|   |-- s1.example.com
|   |-- s2.example.com
--- inventory

s1.example.com

# Comment specifying intention
---
var: value

Playbook

docs

# Run the playbook
$ ansible-playbook playbook.yml

# Syntax check
$ ansible-playbook --syntax-check playbook.yml

# Dry run
$ ansible-playbook --check playbook.yml

Add more verbose output with --verbose, -v flag, repeat for more info.

While running plays on local machine add connection: local to speed up the process, as it avoids establishing regular remote connection.

To specify which tasks to run add tags block either on tasks or plays level. --skip-tags and --tags command line options are available to specify desired behavior.

It is also possible to specify which task to start from with --start-at-task option, which takes entire task name (including white spaces) as a parameter.

Some modules support dry run; pass --check or -C option to see what Ansible would do on normal run. Modules that do not support dry runs, may produce unexpected results on dry-run. Set check_mode to no on those tasks to disable check mode. Setting check_mode to yes would run task in dry-run mode in normal run of playbook.

set_fact module is used to create dynamic variables.

While using non-idempotent modules, like shell module, is it useful to register the state to make later decisions. Fe, grep module can be used to capture desired output and register its return status (need to set ignore_errors: true not to stop at failed grep) that later modules can use.

Ansible facts are special variables that contain unique info on the host. By default, they are set my implied task at the beginning of a play - gathering facts. Can also collect facts at any time using setup. Facts are set as dictionary in ansible_facts variable.

hostvars

Output info available on hosts:

$ ansible all -m debug -i inventory -a "var=hostvars['server1']"

loop

Loop over list of items using item, loop keywords.

- name: Play
  hosts: all
  vars:
    new_users:
      - one
      - two
      - three
  tasks:
    - name: create users
      user:
        name: "{{ item }}"
        state: present
      loop: "{{ new_users }}"

It is also possible looping over list of dictionaries, using item and with_dict keywords.

- name: Play
  hosts: all
  tasks:
    - name: create users
        user:
          name: "{{ item.name }}"
          groups: "{{ item.groups }}"
          state: present
      with_dict:
        - { name: "one", groups: "a" }
        - { name: "two", groups: "a" }
        - { name: "three", groups: "b" }
        - { name: "four", groups: "b" }

conditions

Use conditionals to run or skip tasks. Can user both variables and facts. Operators (f.e. >, <) to compare strings, numerical data, or Boolean are available.

ansible_facts is a dictionary of facts about a host (gathered by gather facts). ansible_mounts fact is a list of dictionaries, each representing facts about one mounted file system. group_names is a list of all group names ansible is aware of.

Operation Example
equal (string) var == "string"
equal (numeric) var == 23
comparison var > 23, var <= 10, var != 5
variable exists var is defined
variable does not exist var is not defined
Boolean is true (1, True, or yes) var
Boolean is false (0, False, no) not var
variable is present in an array var in list

or and and can be used to evaluate multiple conditions. when also supports list of conditions, which is interpreted as and that is all conditions must be true.

# runs if run_task is true
when: run_task

# runs if run_task is present (use not defined for
when: run_task is defined

blocks

Blocks are clauses that logically group tasks as a unit.

- name: some play
  hosts: all
  tasks:
    - name: some block
      block:
        - name: first task
          module:
            arg: value
        - name: second task
          module:
            arg: value
      when: var is defined

A block can have a set of tasks grouped in a rescue statement. Those tasks run only if a task in a block has failed. always statement groups tasks that always run (but are subject to conditional on the block).

tasks:
- name: some block
  block:
    - name: first task
      module:
        arg: value
    - name: second task
      module:
        arg: value
  rescue:
    - name: third task
      module:
        arg: value
  always:
    - name: fourth task
      module:
        arg: value

Variables

Scope

  • global - values set for all hosts; f.e. variables set in the job template
  • host - value set for host or group; f.e variables set in the inventory or host_vars directory, gathered facts
  • play - values set in the context of a play; vars directive, include_vars tasks, and so on

Narrow scope overwrites wider scope. Variables defined in playbook are overwritten by variables defined on command line, with -e option.

Defining variables in a playbook

  • vars top directive of a play
  • vars_files top directive of a play - load variables from a list of files

Host and group variables can be defined in host_vars or group_vars sub-directories (in the same directory as inventory) that contain yaml files matching name of host or a group. Those have higher precedence that the ones defined in the inventory. Example:

ansible_user: devops
newfiles:
  - /tmp/a.conf
  - /tmp/b.conf

To access child values of a dictionary use square brackets and single quotes.

---
- name: Create user
  hosts: all
  become: true
  vars:
    username:
      test:
        uname: test
  tasks:
    - name: user task
      user:
        name: "{{ username['test']['uname'] }}"
        state: present

register captures the output of a task and stores it in a variable for later use in the playbook.

inventory_hostname special variable.

Handler

Handlers are tasks that respond to a notification triggered by other tasks. Tasks notify handlers only when they change something on the system. Each handler has a unique (per play namespace) name and is triggered at the end of a block of tasks in a playbook (if more than one task notified a handler, it will still run only once). One task can notify multiple handlers. Handler run in the order they are defined in the file.

tasks:
  - name: copy config file
    template:
      src: /some/path/file.template
      dest: /other/path/file
    notify:
      - restart apache

handlers:
  - name: restart apache
    service:
      name: httpd
      state: restarted

Templating

{# COMMENT #} syntax is used to add comments - those will not appear in the final file.

for loop example:

{% for host in groups['all'] %}
{{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }} {{ host_vars[host]['ansible_facts']['fqdn'] }}
{% endfor %}

if example:

{# Only included if finished is True #}
{% if finished %}
{{ result }}
{% endif %}

Available filters

lookup plugins

Lookup plugin is an Ansible extension of the Jinja2 templating language. They import and format data from external sources for use in variables and templates. Allows to use contents of a file as a value for a variable. Also allows to look up info from other sources and put them in template:

# List available plugins
$ ansible-doc -t lookup -l

# Info on file lookup plugin
$ ansible-doc -t lookup file

Lookup call:

  • lookup - returns a string of comma separated items
  • query - returns yaml list of items
- name: Play
  host: all
  vars:
    mxvar: "{{ query('dig', 'gmail.com', 'gtype=MX') }}"
  tasks:
    - name: List entries
      dbg:
        msg: MX record is: {{ item }}
      loop: "{{ mxvar }}"
    - name: Print the names of each account in /etc/passwd
      dbg:
        msg: A user is {{ item | regex_replace(':.*$') }}
      loop: "{{ query('lines', 'cat /etc/passwd') }}"

Role

# Initialize role by creating directory with prepared layout
$ ansible-galaxy init ROLE_NAME
role_name
|-- defaults
|   |-- main.yml
|-- files
|-- handlers
|   |-- main.yml
|-- meta
|-- README.md
|-- tasks
|   |-- main.yml
|-- templates
|-- tests
|   |-- intentory
|   |-- test.yml
|-- vars
|   |-- main.yml
Directory Functions
defaults contains default values for variables that are most likely to be overwritten by the user of the role
files static files referenced by role tasks
handlers handler definitions
meta program readable file with meta data about the role: author, description, licence, galaxy tags (so other ppl can find it), dependency list, etc
tasks where individual tasks go in the list form; main.yml must be present, while it can include other files too
templates Jinja2 templates referenced by tasks
tests contains inventory and test.yml to test the role
vars defines variables to be used internally by the role, have high precedence; not intended to be changed by the user

Ansible roles are shared via Github repository, simply create and commit changes there. To import a role login to Ansible Galaxy via Github credentials and run the following command:

$ ansible-galaxy login
$ ansible-galaxy import USER_NAME REPO_NAME

Download Ansible role from Ansible Galaxy (default installation path is galaxy global directory, ~/.ansible/roles; use -p option to specify custom path):

$ ansible-galaxy install USER_NAME.REPO_NAME

requirements.yml file can be used to specify required roles for the playbook. Roles are added in a list form.

# name is used to override the local name of the role
- src: geerlingguy.redis
  version: "1.16.0"
  name: new_name

# from Git repo specifying the version
# version can be branch or commit hash
- src: https:/some-site/repo.git
  scm: git
  version: master
  name: new_name

Centralizing roles

Create necessary directory hierarchy:

$ sudo mkdir -p /etc/ansible/roles

Create Ansible configuration file, /etc/ansible/ansible.cfg (looks like Ansible is checking if it exists?):

[defaults]
roles_path=/etc/ansible/roles

Now can put full directory name, [USER].[REPO_NAME] in playbook to reference global roles directory contents.

Debugger

Enables used to check and set values of variables, update module arguments, and re-run tasks.

Enable debugging with one of the following ways:

  • ansible.cfg

     [defaults]
     enable_task_debugger = True
  • environment variable

     ANSIBLE_ENABLE_TASK_DEBUGGER=True

Any failed of unreachable task will trigger the debugger interaction by default. debugger keyward can alter that behaviour and be used in any block that utilises name keyward, suck as play, role, task, or block. More specific (task vs play level) setting takes precedence, f.e. if debugger is set to never on play level and to on_failed on task level, it will trigger, if that task fails.

  • always
  • never
  • on_failed
  • on_unreachable
  • on_skipped

Debugger commands.

Command Description
p(rint) task/task_vars/host/result print values used to execute module
task.args[key] = value update module's variable
task_vars[key] = value update vars used in task
u(date_task) recreate the task from original structure with updated task_vars
r(edo) re-run the task
c(ontinue) continue the playbook execution
q(uit) quit debugger, playbook execution is aborted

Lint

$ pip install ansible-lint

.ansible-lint is the local configuration file. Can be located elsewhere and passed with -c path/to/file parameter. Accepts either list of playbook files or role directories.

Configuration section Description
exclude_paths path to dirs and files to skip
parseable output in the pip8 format
quite less output
rulesdir specify one or more rules dirs; overrides default rules, unless use_default_rules is also set
skip_list only check rules whose id/tags do not match these values
tags only check rules whose id/tags match these values
use_default_rules use default rules at /path/to/ansible-lint/lib/ansiblelint/rules/ in addition to any extra rules dirs set by rulesdirs
verbosity set verbosity level

Custom rule dir can also be passed in command line with -r path/to/rule/dir parameter. -R parameter enables the use_default_rules section. -x tag_one, tag_two parameter works as skip_list section. ansible-lint -T - view all available tags and ids. To skip the particular default check, pass the id (number in brackets) of the rule to -x or skip_list in the configuration.

exclude_paths:
  - ./path/one/
  - ./path/two/
parseable: true
quite: true
rulesdir:
  - ./rule/dir/
skip_list:
  - skip_this_tag
  - skip_this_id
tags:
  - run_this_tag
use_default_rules: true
verbose: 1

Inventory

Ansible configuration file may contain the path to inventory (default is etc/anisble/hosts - contains examples):

[defaults]
inventory = ./inventory

Verify inventory:

# -i inventory to specify an inventory file, if not defined
$ ansible-inventory
$ ansible-inventory -y --list # display inventory in yaml format

Check if host is contained in inventory file:

$ ansible some.example.com --list-hosts

It's easy to organize hosts into groups in ini file; one host can be in multiple groups; hosts can be FGDN or IP address; group names shouldn't contain dashes (underscores are fine); groups can be nested using :children suffix:

[web_servers]
web1.example.com
192.168.1.1

[databases]
data1.example.com
data2.example.com

[prod]
data1.example.com
192.168.1.1

[vms:children]
databases

Two special groups that always exist:

  • all - all defined hosts
  • ungrouped - hosts that are not member of another group

It is possible to specify range of hosts, [START:END] both included:

  • 192.168.[4:7].[0:255] - 192.168.4.0 - 192.168.7.255 (192.168.4.0/22)
  • server[01:20].example.com - server01.example.com - server20.example.com
  • [a:c].dns.example.com - a.dns.example.com, b.dns.example.com, c.dns.example.com

Multiple inventory files can be passed to Ansible by pointing ansible.cgf or command line argument to a directory containing all the files.

Dynamic inventory

Small executable program that can build inventory. Must have execute permissions. Can be written in any language that can provide inventory output in json format.

Developing dynamic inventory guide

Useful commands

  • list declared hosts:

     $ ansible-inventory --list
  • output in graph mode with groups and connections:

     $ ansible-inventory --graph
  • include variables as well:

     $ ansible-inventory --graph --vars
  • given ini file outputs inventory in json format (as example of what dynamic script should generate)

     $ ansible-inventory -i INI_INVENTORY_FILE --list

Secret

The idea is to separate variables' name and value into different files, f.e. vars and vault. Vars will state the name of the variable and value will hold the value of the variable in vault file, which will be encrypted later.

vars:

password: "{{ vault_password }}"

vault:

vault_password: actual_password_value

Suppress the output of the task (sensitive data):

- name: some task
  debug:
    msg: "{{ secret }}"
  no_log: true

Commands

# New file
$ ansible-vault create FILE_NAME # create secret file
$ ansible-vault view FILE_NAME # view encrypted file
$ ansible-vault edit FILE_NAME # edit encrypted file in place

# Existing file
$ ansible-vault encrypt FILE_NAME [--output=NEW_FILE_NAME] # encrypt existing file
$ ansible-vault decrypt FILE_NAME # encrypt existing file

# Change password
$ ansible-vault rekey FILE_NAME

# Supply password to playbook, ansible will prompt for password
$ ansible-playbook --vault-id @prompt playbook.yml

# Set labels on each prompt for multiple passwords
$ ansible-playbook --vault-id vars@prompt --vault-id playbook@prompt playbook.yaml

Console

REPL tool for Ansible that can be used to run individual commands and modules. Tab completion is on my default (module names, parameter list, etc).

Can target single host or group of hosts:

$ ansible-console HOST

Testing

https://www.digitalocean.com/community/tutorials/how-to-test-ansible-roles-with-molecule-on-ubuntu-18-04

Help

List contents of the available option of the plugin:

$ ansible-doc --type <plugin> --list
$ ansible-doc -t connections -l

Info on module usage:

$ ansible-doc <module>
$ ansible-doc git_config
⚠️ **GitHub.com Fallback** ⚠️