[PYTHON] Ansible overview

Ansible quickly stumbles when trying to do something a little different from the sample in the documentation. I suspect that this Ansible wall is because you can't see the whole picture. As you use it, you will gradually see the whole picture, but it is often the case that you need to know it sooner. I will leave it as my own memo, such as the structure for a bird's-eye view of the whole picture, but I hope it will be helpful for other people as much as possible.

  1. Ansible setting notation
  2. Config file ansible.cfg
  3. Inventory
  4. Playbook
  5. Use Ansible directory structure
  6. Difference between import and include
  7. Bonus: Make Raspberry Pi3 (Ubuntu stretch 9) the control host

At a minimum, if you understand 1. Ansible's configuration notation, 2. config file ansible.cfg, 3. Inventory, 4. Playbook, you should be able to use ansible. Ansible. (There are prerequisites such as SSH settings.)

As you use it, you will be able to manage multiple tasks collectively and reuse them more often. Section 5 describes how to use ansible using the directory structure.

Finally, I will describe how to use it with Raspberry Pi3 as a bonus.

Ansible setting notation

In Ansible settings, YAML format and INI format You can use E3% 82% A1% E3% 82% A4% E3% 83% AB) (extended INI notation that allows # in comments). Being able to describe both allows for flexible writing, but it is confusing when used for the first time because it is not uniform.

It's a good idea to keep in mind the following rules.

  1. * .cfg and files without extension are in INI format.
  2. * .yml and * .yaml are in YAML format

INI format example

It consists of a "section name" enclosed in [] and a "parameter" described by =. You can put spaces, TAB, etc. before and after =.

ini:./ansible.cfg


[defaults]
host_key_checking       = True

#You can write comments sharply
[privilege_escalation]
become_ask_pass         = True

;You can write a comment with a semicolon
[ssh_connection]
scp_if_ssh              = False

After =, except for the Booleans True / False and yes / no, it is treated as a "character string" until the end of the line. If you enclose it in " etc., " itself may be interpreted as part of the string.

Separate "lists" (arrays) with commas (,).

YAML format example

An enumeration of "names" separated by : is called a "scalar".

Scalar


name1:
name2:
:
nameN:

If you enumerate without prefixing -, it becomes" series "(simple enumeration," scalar ").

You can write the value after :. Also known as a dictionary type, this is called a "mapping" in YAML.

mapping


name1: aaaa
name2: bbbb
:
nameN: cccc

If you prefix it with -, it becomes a list (array). In YAML, this is called a "sequence."

sequence


- name1:
- name2:

- nameN:

Indentation represents a hierarchy, which is called a "collection". Use spaces for indentation. Please note that if you use TAB as it is, an error will occur. (If you are using vi / vim, setting it with set expandtab will replace TAB with blanks)

Example-playbook-samle1.yml


- name: the 1st Example
  hosts: 172.17.0.2
  tasks:
    - name: Hello, World!
      debug:
    - name: Next debug
      debug: msg="Hello, Ansible!"

In this example, name: the 1st Example, hosts: 172.17.0.2, tasks: are the same level of scalar (parallel (sequential)) settings, -name: Hello, ansible, -name: Next. The debug part is the sequence (list (array)). It is a mapping, each with a value. tasks is a collection.

I often see YAML format with --- (three hyphens) at the beginning. This is because the YAML structure is supposed to start one definition block with --- and end with ... (three dots). You can write multiple blocks in one file with ---, but in general, ansible does not use such a structure. You can think that it is safe to add ---. If you rewrite the above playbook-samle1.yml, it will look like the one below.

playbook-samle1.yml


---
- name: the 1st Example
  hosts: 172.17.0.2
  tasks:
    - name: Hello, World!
      debug:
    - name: Next debug
      debug: msg="Hello, Ansible!"

Config file ansible.cfg

See also: Ansible Configuration Settings — Ansible Documentation (https://docs.ansible.com/ansible/latest/reference_appendices/config.html) Since the extension of the config file is .cfg, it is described in INI format. Ansible-related commands search for config files in the following order, and the first config file found is used.

  1. The file specified by the environment variable ʻANSIBLE_CONFIG`
  2. ʻansible.cfg (./ansible.cfg`) in the current directory
  3. Home directory .ansible.cfg (~ / .ansible.cfg or `$ HOME / .ansible.cfg)
  4. /etc/ansible/ansible.cfg

Ansible.cfg example

ansible.cfg


[defaults]
inventory		= ./inventory
interpreter_python	= /usr/bin/python3
#deprecation_warnings	= False

[privilege_escalation]
become_ask_pass		= yes

[ssh_connection]
scp_if_ssh		= False

Inventory

See: How to build your inventory — Ansible Documentation In order to execute the ansible command and operate the playbook described later, it is necessary to prepare a file called Inventory that describes the host to be operated. In a nutshell, it's like a collection of / etc / hosts files.

Inventory example

myhost1


172.17.0.2

The smallest Inventory file contains only one IP address (host).

myhost2


172.17.0.2
172.17.0.3

[group1]
172.17.0.2
172.17.0.3

[group2]
172.17.0.[3:7]

[my_all:children]
group1
group2

Playbook

Instead of the ad hoc (= one-shot) module specification and operation of ʻansible -m ping host pattern`, specify a configuration file called a playbook to perform a series of operations. The format of the playbook is generally YAML format.

Playbook example

playbook-sample1.yml


- name: the 1st Example
  hosts: 172.17.0.2
  tasks:
#The task is a list(sequence)Specify with.
#Display the default message in debug.
    - name: Hello, World
      debug:
#Display the specified message in debug.
    - name: Next debug
      debug: msg="Hello, Ansible!"

In order to run the playbook, you need to specify an inventory file.

myhosts1


172.17.0.2
$ ansible-playbook -i myhosts1 playbook-sample1.yml

PLAY [the 1st Example] *************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [172.17.0.2]

TASK [Hello, World] ****************************************************************************************************
ok: [172.17.0.2] => {
    "msg": "Hello world!"
}

TASK [Next debug] ******************************************************************************************************
ok: [172.17.0.2] => {
    "msg": "Hello, Ansible!"
}

PLAY RECAP *************************************************************************************************************
172.17.0.2                 : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ansible directory structure

See also: Roles — Ansible Documentation By using the following directory structure, it is easy to specify files, tasks, etc.

./
├─ ansible.cfg  ……………………………… (1)Configuration file. Environment variable ANSIBLE_In CONFIG
│ Fixed name unless explicitly specified.
├─ playbook.yml ……………………………… (2)Playbook Any file name. YAML format is common.
│
├─ inventory/ …………………………………… (3)Inventory directory. Must be specified,
│ │ Any name. Specify as an option.
│    ├─ hosts …………………………………… (4)Any name. If there is no extension, INI format,
│    ├─ hosts2.yml extension.YAML format for yml.
│    ├─ : 
│    └─ hostsN
├─ group_vars/ ………………………………… (5)Variables that apply to the "target host group" of the specified task
│ │ The directory to store. Fixed name.
│    ├─ groupname1 ……………………… (6)Set the "Target Host Group" name to the file name. If there is no extension, INI format,
│    ├─ groupname2.If it is in yml YAML format, add the extension.Make it yml.
│    ├─ : 
│    └─ groupnameN
├─ host_vars/ …………………………………… (7)Variables that apply to the "target host" of the specified task
│ │ The directory to store. Fixed name.
│    ├─ hostname1 ………………………… (8)Set the "target host" name to the file name. If there is no extension, INI format,
│    ├─ hostname2.If it is in yml YAML format, add the extension.Make it yml.
│    ├─ : 
│    └─ hostnameN
├─ files/ ……………………………………………… (9)File storage used for copying and transferring. Fixed name.
│    ├─ file1 ……………………………………(10)Any file name.
│    ├─ file1
│    ├─ :
│    └─ fileN
└─ roles/ ………………………………………………(11)Settings directory for using roles in playbooks. Fixed name.
    ├─ role1/ ……………………………………(12)Set the "target role" name to the directory name.
    ├─ role2/
    ├─ :
    └─ roleN/
         ├─ defaults …………………(13)The directory that stores the default variable settings. Fixed name.
         │   └── main.yml ……(14)The variable configuration file that is loaded first. Fixed name.
         │                       import_Like vars, it is loaded when the playbook is parsed.
         ├─ vars ……………………………(15)A directory that stores variable settings. Fixed name.
│ │ Base directory when a file is specified with a relative path.
         │    ├─ varfile1 ……(16)Any file name. If there is no extension, INI format,
         │    ├─ varfile2.If it is in yml YAML format, add the extension.Make it yml.
         │    ├─ :
         │    └─ varfileN
         └─ tasks …………………………(17)Directory for storing tasks. Fixed name.
             └── main.yml ……(18)The first task configuration file to be executed. Fixed name.
If you want to change the process depending on the OS of the target host,
                                 main.import from yml/Read with include.

There are other roles in directories such as handlers /, templates /, and meta /, but for the time being, if you understand the above directory structure, it will be easier to understand other directories as well. ..

See "Roles — Ansible Documentation" for more information. There is also an example in "Best Practices: Directory Layout — Ansible Documentation".

Difference between import and include

See also: Including and Importing — Ansible Documentation Both import * and include * make no difference in reading a file, but at different times.

  • All import* statements are pre-processed at the time playbooks are parsed.
  • All include* statements are processed as they are encountered during the execution of the playbook.

Of particular importance is when variables are processed using the JINJA2 template (for example, the template {{samplevarname}}) and when they are assigned to variables.

If you use import *, it will be expanded before the task is executed, so the embedded template will be replaced with the actual value and the task will be executed after the assignment to the variable is finished.

On the other hand, if you use include *, it will be expanded when the task is being executed, so when it is loaded and executed, the template will be replaced with the actual value and the assignment will be made.

Bonus: Make Raspberry Pi3 (Ubuntu stretch 9) the control host

Ansible is used in a Python environment, but if you install OS-based ansibule (apt / apt-get install) on Ubuntu (stretch 9) of Raspberry Pi3, Version 2.2 of Ansible will be installed (written January 13, 2020). As of the date).

The latest features are not available in v2.2, which is a bit inconvenient, so you can use the latest Ansible (latest v2.9 at the time of writing) by installing (pip install) in a virtual environment of Python. (The version of Python3 for Raspberry Pi3 / Ubuntu is 3.5)

$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)"
NAME="Raspbian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
VERSION_CODENAME=stretch
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
$ python3 --version
Python 3.5.3

The installation in the Python virtual environment is as follows.

$ python3 -m venv ansible
$ source ansible/bin/activate
$ pip install -U pip
$ pip install ansible

The * ansible * after python3 -m venv and the * ansible * specified in the source command are the same, and you can decide for yourself. Since ʻansiblespecified inpip install` is the package to be installed, execute it as it is without changing it.

Use of: point_up: alias

If you are using bash, it is convenient to make the following settings in ~ / .bashrc.

bash:~/.bashrc Or ~/.bash_aliases Such


alias ansibleenv='type deactivate > /dev/null 2>&1 && deactivate; cd ~/work/Ansible.d/; source ~/work/Python.d/envs/ansible/bin/activate'

In the type deactivate> / dev / null 2> & 1 && deactivate part, deactivate if it was activated in another environment. Change to your working directory with cd ~ / work / Ansible.d. source ~ / work / Python.d / envs / ansible / bin / activate activates the ansible environment. Once set, you can call it with ʻansibleenv`.

: point_up: Weakref.py error fix

On Raspberry Pi, even if ansible itself is executed successfully, the following error may occur.

:
PLAY RECAP *************************************************************************************************************
172.17.0.2                 : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Exception ignored in: <function WeakValueDictionary.__init__.<locals>.remove at 0x75c7fdf8>
Traceback (most recent call last):
  File "/usr/lib/python3.5/weakref.py", line 117, in remove
TypeError: 'NoneType' object is not callable
Exception ignored in: <function WeakValueDictionary.__init__.<locals>.remove at 0x75c7fdf8>
Traceback (most recent call last):
  File "/usr/lib/python3.5/weakref.py", line 117, in remove
TypeError: 'NoneType' object is not callable
    :
    :
Exception ignored in: <function WeakValueDictionary.__init__.<locals>.remove at 0x75c7fdf8>
Traceback (most recent call last):
  File "/usr/lib/python3.5/weakref.py", line 117, in remove
TypeError: 'NoneType' object is not callable

This is an error that occurs because the variable `` `/usr/lib/python3.5/weakref.py``` is not defined (type "None"). The underlying solution should be considered by the caller, but can be tentatively avoided by applying the following patches. (Although it is quite rough)

diff:weakref.py.patch


*** weakref.py.bak      2018-09-28 02:25:39.000000000 +0900
--- weakref.py  2020-01-03 18:44:49.190027705 +0900
***************
*** 114,120 ****
                  else:
                      # Atomic removal is necessary since this function
                      # can be called asynchronously by the GC
!                     _remove_dead_weakref(d, wr.key)
          self._remove = remove
          # A list of keys to be removed
          self._pending_removals = []
--- 114,121 ----
                  else:
                      # Atomic removal is necessary since this function
                      # can be called asynchronously by the GC
!                     if type(_remove_dead_weakref) is not type(None) and type(d) is not type(None) and type(wr) is not type(None):
!                         _remove_dead_weakref(d, wr.key)
          self._remove = remove
          # A list of keys to be removed
          self._pending_removals = []

Apply patch:

$ sudo patch -c -b /usr/lib/python3.5/weakref.py weakref.py.patch

-c is the context type patching option and -b is the backup creation option.

Recommended Posts

Ansible overview
Linux overview
Install Ansible
Ansible Note
Cloud Datalab Overview
Cloud-native service overview