Skip to content

Latest commit

 

History

History
723 lines (493 loc) · 18 KB

cheat-sheet-Ansible.adoc

File metadata and controls

723 lines (493 loc) · 18 KB

Some notes about Ansible

BEST PRACTICES (source [best_practices](https://www.ansible.com/blog/ansible-best-practices-essentials))

  1. Name your plays and Tasks Always name you plays and tasks, for example:

- hosts: web
  name: install and start apache
  taks:
    - name: install apache
      yum:
        name: httpd
        state: latest
  1. Use Prefixes and Human Meaningful Names with Variables The recommendation is prefix variables with the source or target of the data it represents. Prefixing variable is particulary vital with developing reusable and portables roles.

apache_max_keepalive: 25
apache_port: 80
ssh_port: 22
  1. Use Native YAML Syntax Use native YAML for best readability, the native YAML has more lines but those lines are shorter reducing horizontal scrolling and line wrapping.

4. Use Modules before Run Commands

Don not abuse of command, shell or raw modules. Use the Ansible modules to perform de tasks because they are idempotent.

5. Clean Up your debuging messages

In the past, you had to delete or comment out your debug tasks and that was suboptimal approach though.

Starting in 2.1, the Ansible debug moduel supports a verbosity parameter that suppresses output unless the play is being run with a sufficiently high enough verbosity level.

- debug:
  msg: "This always is displayed"

- debug:
  msg: "This only displays with ansible-playbook -vv+"
  verbosity: 2

EXECUTIONS

Execute ansible ad-hoc in several hosts without inventory:

$ ansible all -i host1.org, -m shell -a 'uptime' -u user1 -k

CONTROL AND MANAGED NODE REQUIREMENTS

CONTROL NODE

Ansible can be run from any machine with Python 2 (versions 2.6 or 2.7) or Python 3 (versions 3.5 and higher). Windows is not supported for control machine.

MANAGED NODE

For this kind of nodes the following requirements are needed:

  • SSH (default uses sftp but you can switch to scp in ansible.cfg)

  • Python 2.6 or later**

    • Ansible raw module and the script module do not even need python, so you can use ansible to install python-simplejson using the raw module, which then allows you to use everything else.

  • libselinux-python if you have SELinux enabled on remote nodes (to use copy/file/template functions in Ansible)

Configuration File

Use in order of preference: + Contents of $ANSIBLE_CONFIG environment variable + ./ansible.cfg + ~/.ansible.cfg + /etc/ansible/ansible.cfg

To see what will be the config used by ansible:

   # ansible --version
     ansible 2.3.1.0
     config file = /tmp/ansible.cfg

INVENTORY FILES

Two kind of definitions for inventory file:

INI-file structure:

Blocks define groups. Hosts allowed in more than one group.

Hostname ranges: www[01:50].example.com, db-[a:f].example.com

Per-host variables: thor.example.com ansible_ssh_port=2424 variable1=var1 variable2=var2

  • [foo:children]: new group foo containing all members if included groups

  • [foo:vars]: variable definition for all members of group foo

Inventory file defaults to /etc/ansible/hosts. Veritable with -i or in the configuration file. The file can also be a dynamic inventory script. If a directory, all contained files are processed.

YAML file

Definition of inventory:

 all:
   hosts:
     mail.example.com
   children:
     webservers:
       hosts:
         foo.example.com:
         bar.example.com:
    dbservers:
       hosts:
         one.example.com:
         two.example.com:
         three.example.com:

Definition of variables for a host (or group)

hosts:
  jumper:
    ansible_port: 5555
    ansible_host: 192.0.2.50

PATTERNS

Used on the ansible command line or in playbooks:

  • all or *

  • hostname: lab.example.com

  • groupname: webservers or webservers:dbservers

  • exclude: webservers:!phoneix

  • intersection: webservers:&staging

You can do combinations: websersers:dbservers:&staging:!phoneix

You can also user variables if you want to pass some group specifiers via the "-e" argument to any ansible playbook: webservers:!{{excluded}}:&{{required}}

Also you can use wildcards: .exampe.com or 192.168.1. and regular expressions: ~(web|db).*\.example\.com

And finally you can refer to hosts within the group by adding a subscript to the group name. For instance if you have the following group:

[webservers]
web1
web2
web3

You can select a host or subset of hosts from a group by their position:

webservers[0]	# web1
webservers[0:1]	# web1,web2
webservers[1:]	# web2,web3

Configuration VIM for YAML

These are my favourite options in .vimrc to edit yaml files for Ansible:

autocmd FileType yaml setlocal ai ts=2 sw=2 et nu cuc
autocmd FileTyep yaml colo desert

Jinja2 Templates

  • Jinja templates uses {% EXPR %} for expressions and logic

  • And {{ }} for outputting the results and expressions.

For example:

{{ ansible_facts['default_ipv4']['address'] }} {{ ansible_facts['hostname'] }}

Control Structures

Using loops

In this example the user variable is replaced with all the values included in the users variable yaml {% for user in users %} {{ user }} {% end for %}

Controling Task Execution

The order when the tasks are running:

  1. pre_tasks |__ handler notification (pre_tasks)

  2. roles

  3. tasks

  4. handlers notified by roles and tasks

  5. post_tasks |__ handler by post_taks

import_ vs include_

include_role -→ dynamically include a role Ansible parses and inserts the role in play when it reaches the include role

import_role -→ statically import a role Ansible parses the role at the begining and it detects errors before starting executing tasks. "

Managing Task Execution

Optimizing Execution Speed

In Ansible you can be beneficial of the use of callbacks plugins to get some statiscitcs like the time that each tasks "tarda" for running. To activate this plugins via ansible.cfg:

[defaults]
callback_whitelist=timer,profile_tasks

Data Filtering

Variables Type

Strings

A sequence of characteres.

my_string_var: This contains a string

For better readability, you can use pipe caracter | to break the string in several lines, or the operator > to suppres the break line but used for read beatter.

string_with_break_lines: |
This string
is represented
with several lines
string_whitout_line_breask: >
This string will
shown in just
one line

Numbers

  • Integer: number: 10

  • Float: 15.2

  • Scientific: 0.20e2

  • String with no number: "15"

Booleans

yes, no,y,n,on,off,true or false

Date

The date has to be in the ISO-8601 standard, for example:

date_time_ok: 2019-05-30T10:05:25.23+02:00
date_time_short: 2019-05-30

List of Arrays, or hashes

Array is a list of values and the difference with Dictionaries is that the second ara a list of key=values

Two different ways to define an Array in Yaml:

my_own_list: ['Tere', 'Alba', 'Ainhoa']
my_own_list_2:
  - Tere
  - Alba
  - Ainhoa

To access the values of the Array, like follows:

12     - name: Check how to access the element of the array
13       assert:
14         that:
15           - my_own_list['0'] == 'Tere'

Dictionaries

Consist of key=values list

This is an example of how to define a dictionary:

my_dictionary: { Emma: Human, Laika: Dog, Alba: Human }

my_dictionary_2:
  Emma: Human
  Laika: Dog
  Alba: Human

An example of how to access the elements of a dictionary:

assert:
  that: my_dictionary['Laika'] != 'Human'
Note
You can also use the notation 'my_dictionary.Laika' to access the element, but this is not recommended because it can collide with some reserverd names for attributes of methos of Python dict.

Processing Data with Filters

You can use Jinja2 filtering options to convert the given variables. You can check the Jinja offical documentation

For instance, to convert a number variable to string:

{{ my_number | string }}
{{ my_name | capitalize }}

You can also process the information using the unique and eq Jinja filters:app-name:

 - name: Playing with data processing
      assert:
        that:
        - "{{ [1, 4, 2, 2] | unique | sort }} is eq ( [1, 2, 4] )"
      register: output
      tags: filter

Using Filter to Manipulate Data

Handling undefined variables

Providing default values

Examples:

{{ some_variable | default(5) }} -→ some_variable = 5 when it is not defined

{{ lookup(''env), 'MY_USER') | default('admin', true) -→ use default value admin when the variable MY_USER is not defined or is empty

Making variables optional

By default Ansibe requires values for all variables in a templated expresion. I you want to make optional some variable you can use special variable omit :

- name: Create users with optional homedir
  user:
    name: "{{ item.name }}"
    shell: /bin/bash
    groups: wheel
    append: yes
    home: "{{ item.homedir | default(omit) }}"
  loop:
    - name: james
      homedir: /opt/james
    - name: toni
    - name: pepe

In this example the default home directory for users toni an Pepe will be /home/{{ user.name }} and for james will be /opt/james.

Mandatory values

To setup a variable which is mandatory. If there is no value provided you will get an error.

{{ my_value | mandatory }}

Defining different values for true/false/null(ternary)

{{ (pet_type == "dog") | ternary('Dog','Cat') }} -→ if sentence is True return Dog and if false cat.

In addition, you can define a one value to use on tru, one value on false and third value on null:

{{ enabled | ternary('no shutdown', 'shutdown', omit) }}

Managing data Types

Discovering data types

 - name: Check type from variable
    debug:
      msg: "{{ my_number | default(42.0)  | type_debug }}"

TASK [Check type from variable] ******************************************************************************************************************************
ok: [127.0.0.1] => {
    "msg": "float"
}

Transforming dictorionaris into lists

Use dict2items filter to transform a dictionary into a list of items suitable for looping:

Speacial variables

Magic variables

ansible_check_mode Boolean that indicates ansible is in check mode

ansible_config_file: The full path of ansible.cfg

ansible_forks: integer showing number of maximum forks available in the runing tasks

ansible_inventory_sources: List of sources used as inventory

inventory_hostname: the current hosts being iterated over in the play

hostvars: A dictionary/map with all the hosts in inventory and variables assigned to them

Check all the list of available magic variables

Facts

These are variables gathered when ansible has the flag gather_facts set to true.

ansible_facts

Connections variables

ansible_host: The ip/name of the target host ot use instead inventory_hostname

ansible_user: the Ansible user logs in as.

Overview of Selected Filters

There are many filters available, ones from Jinja2 and others provided by Red Hat Ansible engine.

Check if a variable is Defined

mandatory

{{ my_variable | mandatory }}

default

You can establish a default value in case there is no value provided, for example:app-name: {{ my_vaule | default('my_default, True') }}

omit

You can also force to an undefined value in case there is no value provided:

"{{ my_value | default(omit) }}"

Mathematical Calculation

You can use the following arithmetic operations:

Operator

Purpose

+

Add two numbers

-

Subsctract two number

For example, you can use

Manipulate Lists

To sum the values of a List:

tasks:
  - name: The sum of a list
    debug:
      msg: "The sum of a list is [1,2,3,4]: {{ [1, 2, 3, 4] | sum }}"

Merging Lists

You can use flatten to merge several lists.

   - name: Merge several lists
      debug:
        msg: "{{ [ 2, 3, 4, [ 2, 5, 6]] | flatten }}"
      tags: unir

Or sorting a list:

"{{ [2, 3, 9, 1 ,5, 4, 7 ,6 ,8] | sort }}"

Modifying Order

"{{ [2, 4, 5, 8. 9 ] | reverse | list }}"

"{{ [2, 4, 5, 8. 9 ] | sort | list }}"

Find the difference

"{{ [ 2, 3, 5, 8, 9 ] | difference([2, 4, 16]])}}"

Manipulating Dictionaries

Merge dictionaries

   - name: Merge dictionaries
      debug:
        msg: "{{ { 'A': 1, 'B':5 } | combine( {'B':2, 'C':3 }) }}"

Hashing, Encodin, and Manipulating Strings

Hashing strings and passwords

The hash filter returns the has value in string format. As an example:

- name: Use hash filter in strings
  vars:
    # generated by "echo 'Toni' | sha1sum"
    expected: 'ee0f3f344826f230ec3581f9924aee07945b0cbb'
  tasks:
    assert:
      that:
        - "{{ 'Toni' | hash('sha1') }} is eq( expected )"

Data Filtering

Query Lines

Read the files and save it in lines

{{ query('lines','cat /etc/pass') }}

Difference between query and lookup plugins are:

  • query is a Jinja2 function for invoking lookup plugins. The main difference is that query will always return a list.

  • lookup is an Ansible plugin, and the default behaviour of lookup is to return a string of comma separated values. You can use wantList=True to have the same list return as query.

Using selectattr filter

Resources:

* [https://docs.ansible.com/ansible/latest/user_guide/complex_data_manipulation.html]
* http://blog.networktocode.com/post/jinja-map-review/
---
- name: Playing with selectattr
  hosts: localhost
  become: false
  tasks:

    - name: Get using selectattr the 'name' elements from the list of dictionaries in 'hosts'
      vars:
        hosts:
          - name: bastion
            ip:
              - 192.168.1.254
              - 192.168.1.1
          - name: classroom
            ip:
              - 192.168.1.254
              - 192.168.1.1
      ansible.builtin.assert:
        that:
	  # This example verifies the 'name' key is defined and get the value of it
          - "{{ hosts | selectattr('name', 'defined') | map (attribute='name') }} is eq( ['bastion','classroom'] ) "
      tags: selectattr_example

    - name: Get the 'name' elements from the list of dictionaries in 'hosts'
      vars:
        hosts:
          - name: bastion
            ip:
              - 172.25.250.254
              - 172.25.252.1
          - name: classroom
            ip:
              - 172.25.252.254
      ansible.builtin.assert:
        that:
          - "{{ hosts | json_query('[*].ip') }} is eq( ['bastion','classroom'] )"
      tags: json_query

Ansible Navigator

Documentation

Getting help: ansible-navigator subcommand --help

Collections

Collections are basically how Ansible content is packaged and distributed. A collection provides a set of related modules, roles, and plug-ins that you can download to your ansible control node.

With Collections, you can separate the ansible core development from other ansible’s content vendors.

Namespaces

A collection is organized into namespaces to make it eaiser to specify different collections. To list a specify collection, you must use a valid fully qualified collection name (FQCN).

Examples of FQCN:

  • From community namespace: community.crypto, community.postgresql, etc.

  • For redhat supported collections: redhat.satellite, redhat.insights, etc.

The name of namespaces are limited to ASCII lowercase letters, numbers, and underscores, must be at least two characters long, and must not start with an underscore.

Using Ansible Content Collections

To access the documentation ansible-navigator collections --eei ee-supported-rhel8:latest or ansible-navigator doc -l --eei ee-supported-rhel8:latest -m stdout.postgresql, etc. - For redhat supported collections: redhat.satellite, redhat.insights, etc.

The name of namespaces are limited to ASCII lowercase letters, numbers, and underscores, must be at least two characters long, and must not start with an underscore.

Using Ansible Content Collections

To access the documentation ansible-navigator collections --eei ee-supported-rhel8:latest or ansible-navigator doc -l --eei ee-supported-rhel8:latest -m stdout`

Ansible navigator configuration

Formats: - JSON or YAML - ansible-navigator.yml or ansible-navigator.json

Ansible navigator looks for a settings file in the following order and uses the first file that it finds: - ANSIBLE_NAVIGATOR_CONFIG env variable. - ansible-navigator.yml file in your current Ansible project directory. - ~/.ansible-navigator.yml in your home directory

Example of ansible-navigator.yml config:

---
ansible-navigator:
  ansible:
    config: /tmp/ansible.cfg

  ansible-runner:
    arfifact-dir: /tmp/test1
  logging:
    level: critical

  editor:
    command: /bin/emacs

  playbook-artifact:
    enable: false

  execution-environment:
    image: ee-supported-rhel8:latest
    pull-policy: always

References