###### top
## Ansible
[Официальная документация](https://docs.ansible.com/ansible/latest/user_guide/)

***

[Базовые понятия Ansible](#part1)
- [inventory](#chapter1)
- [Модули](#chapter2)
- [Ad-hoc команды](#chapter3)

[Ansible playbook](#part2)
- [Простой playbook](#chapter4)
- [Переменные](#chapter5)
- [Отладка](#chapter6)
- [Блоки и обработка ошибок](#chapter7)
- [Асинхронные задачи](#chapter8)
- [Пример playbook - установка Docker + Docker-compose](#chapter9)
- [](#chapter10)
- [](#chapter11)
- [](#chapter12)

***

###### part1
## Базовые понятия Ansible
[вверх](#top)

Установить ansible можно с помощью пакетного менеджера, будет установлена версия, которая есть в репозиториях дистрибутива, скорее всего, эта версия не будет являться последней.  
При установке через пакетный менеджер будет создана папка с параметрами по-умолчанию по пути `/etc/ansible/`.

Также, ansible можно установить с помощью pip. Будет установлена последняя версия, но папка с параметрами по-умолчанию создана не будет.

###### Chapter1
### inventory
https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#intro-inventory

Описание хостов, на которых необходимо выполнять какие-то действия производится в файле inventory.  
Синтаксис файла может быть в виде `ini` или `yaml` форматов.  

Пример описания inventory с использованием ini
```dosini
my.domain.com

[mygroup]
domain.com
app.domain.com

[dbservers]
db.domain.com
```

Пример описания inventory с использованием yaml
```yaml
all:
  hosts:
  	my.domain.com:
  children:
  	mygroup:
  	  hosts:
  	  	domain.com:
  	  	app.domain.com:
  	dbservers:
  	  hosts:
  	  	db.domain.com:
```

Указывать хосты можно в виде диапазона
```dosini
[webservers]
www[01:50].example.com
```

Для удобства хостам можно задавать свои имена и передавать переменные которые будут доступны на хостах
```dosini
[db]
mydb env=production replicas=2
```

Можно указать параметры подключения
```dosini
[db]
myhost ansible_port=222 ansible_host=192.168.1.23
```

Переменные можно указать для группы хостов
```dosini
[db]
db1.com
db2.com

[db:vars]
env=production
```

В inventory можно указать параметры подключения
```
ansible_connection
ansible_host
ansible_port
ansible_user
ansible_password
```

Параметры подключения ssh/sftp/scp
```
ansible_ssh_private_key_file
ansible_ssh_common_args
ansible_ssh_extra_args
ansible_ssh_pipelining
ansible_ssh_executable

ansible_scp_extra_args
ansible_sftp_extra_args
```

Привилегии
```
ansible_become
ansible_become_method
ansible_become_user
ansible_become_password
ansible_become_exe
ansible_become_flags
```

Настройки shell
```
ansible_shell_type
ansible_python_interpreter
ansible_shell_executable
```

Пример файла inventory
```bash
user@host$ cat hosts.ini
```

```dosini
[deploy]
us1 ansible_host=192.168.10.151

[managers]
us2 ansible_host=192.168.10.152
us3 ansible_host=192.168.10.153

[workers]
us154 ansible_host=192.168.10.154
us155 ansible_host=192.168.10.155

[all:vars]
ansible_user=da2001
ansible_port=22
ansible_ssh_private_key_file=~/.ssh/id_rsa.pub
```

Пользовательский файл инвентаря  
*Файл инвентаря по-умолчанию обычно находится в /etc/ansible/hosts, но можно использовать опцию `-i` для указания пользовательских файлов при запуске команд и плейбуков Ansible. Это удобный способ настройки индивидуального инвентаря для каждого проекта, который можно включить в системы контроля версий, такие как Git:*
```bash
ansible all -m ping -i my_custom_inventory
```
Такая опция действительна и для ansible-playbook:
```bash
ansible-playbook myplaybook.yml -i my_custom_inventory
```
Динамический файл инвентаря  
*Ansible поддерживает сценарии инвентаризации для создания динамических файлов. Это полезно если инвентарь часто меняется, когда серверы создаются и уничтожаются.*

*Существуют готовые скрипты с открытым исходным кодом в официальном репозитории [Ansible GitHub](https://github.com/ansible/ansible). После загрузки требуемого сценария на Ansible control machine и настройки необходимых параметров (например, учетных данных API) можно запустить исполняемый файл в качестве пользовательского инвентаря с любой командой Ansible, которая поддерживает эту опцию.*

*Следующая команда использует скрипт инвентаря my_inventory.py с командой ping для проверки подключения ко всем текущим активным серверам:*
```bash
ansible all -m ping -i my_inventory.py
```
*Как использовать динамические файлы инвентаризации описано в [официальной документации Ansible](https://docs.ansible.com/ansible/latest/user_guide/intro_dynamic_inventory.html).*

Инвентарь можно описывать в отдельной папке и группировать хосты в отдельных файлах.  
*например, можно создать папку `hosts`, в ней папку `bd` и уже в этой папке создавать файлы с описанием хостов*  
Запуск в этом случае будет выглядеть так:
```bash
ansible -i hosts -m ping
```
или
```bash
ansible -i bd -m ping
```

[вверх](#top)

***

###### Chapter2
### Модули

https://docs.ansible.com/ansible/latest/user_guide/modules.html

Пример использования модулей
```bash
ansible webservers -m service -a "name=httpd state=started"
```
```bash
ansible webservers -i /home/user/ansible/hosts.ini -m command -a "/sbin/reboot -t now"
```

[Список доступных модулей на сайте Ansible](https://docs.ansible.com/ansible/2.8/modules/list_of_all_modules.html)  
*описание модулей можно получить в cli, для этого используется команда*
```bash
ansible-doc -l
```
*будет выведен список доступных модулей в less подобном отображении*

Документация по конкретному модулю `ansible-doc <название модуля>`
```bash
ansible-doc user
```

[вверх](#top)

***

###### Chapter3
### Ad-hoc команды

https://docs.ansible.com/ansible/latest/user_guide/intro_adhoc.html

Структура команды
```bash
ansible -i hosts -m user -a "name=student state=present" all
            ^        ^                 ^                  ^
            |        |                 |                  |
        инвентарь    |             аргументы     используемые хосты
                   модуль
```

Создание пользователя на хостах  
*если пользователь существует, вернётся сообщение зеленого цвета*  
*если пользователь не существует, ansible попытается его создать. Если команда была запущена не от root, вернётся сообщение красного цвета типа __Pemission denied__*
```bash
ansible -i hosts.ini -m user -a "name=da2001 state=present" all
```
*создать нового пользователя с повышением привилегий - sudo*
```bash
ansible -i hosts.ini -m user -a "name=student state=present" -b -K all
```
`-b` - команда должна выполняться с привилегиями sudo  
`-K` - запрашивать пароль sudo перед выполнением команды  

*удалить пользователя*
```bash
ansible -i hosts.ini -m user -a "name=student state=absent" -b -K all
```

*передать sudo пароль в виде аргументов - extra args*
```bash
ansible -i hosts.ini -m user -a "name=student state=present" -e "ansible_become=true ansible_become_password=USER-PASS" all
```
`USER-PASS` - пароль пользователя для выполнения команды с привилегиями sudo

[вверх](#top)

***

###### part2
## Ansible playbook
[вверх](#top)

https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html

###### Chapter4
### Простой playbook
```bash
cat user.yml
```
```bash
---
- name: user                 - имя playbook, произвольное
  hosts: deploy              - группа хостов из файла inventory
  tasks:                     - задачи, выполняемые в этом плейбуке
    - name: Create user      - имя задачи, произвольное
      user:                  - название модуля, который используется в этой задаче
        name: da2001         - аргументы модуля
        state: present       - аргументы модуля
      become: true
```

```bash
ansible-playbook -i hosts.ini user.yml -K
```

[вверх](#top)

***

###### Chapter5
### Переменные

https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html

Переменные можно задавать в:
- playbook
- block
- tasks
- group_vars
- host_vars
- inventory
- extra_vars
- var_files

***переменная задаётся в фигурных скобках, обязательно в кавычках***
```bash
cat user.yml
```
```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Create user
      vars:
        user: da2001
      user:
        name: "{{ user }}"
        state: present
      become: true
```

Переменная может быть задана во внешнем файле
```bash
cat myvars.yml
```
```yaml
user: da2001
```
*в плейбуке необходимо указать файл с переменными*
```yaml
---
- name: user
  hosts: deploy
  vars_files:
    - ./myvars.yml
  tasks:
    - name: Create user
      user:
        name: "{{ user }}"
        state: present
      become: true
```

Вместе с ansible устанавливается плагин, который позволяет считывать переменные из определённых файлов и папок без явного указания этих файлов и папок в плейбуке
- `group_vars` - папка в которой нужно создать папку с названием группы, для которой будут задаваться переменные. Например, для группы хостов `deploy` необходимо создать такой путь/файл - `./group_vars/deploy/vars.yml`
```bash
cat ./group_vars/deploy/vars.yml
```
```yaml
user: da2001
```
*в самом плейбуке нет необходимости где-то указывать ссылки на файл с переменными*
```bash
cat user.yml
```
```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Create user
      user:
        name: "{{ user }}"
        state: present
      become: true
```
Если переменные могут использоваться во всех группах, можно создать папку `all` - `./group_vars/all/vars.yml`  

- `host_vars` - папка, в которой создаётся файл с именем хоста - `./host_vars/us1.yml`. Если для хоста имя не задано -  `./host_vars/192.168.10.151.yml`  
- создание переменных в файле инвентаря
*для хоста us1 добавлена переменная `user`*
```bash
cat hosts.ini
```
```
[deploy]
us1 ansible_host=192.168.10.151 user=da2001

[managers]
us2 ansible_host=192.168.10.152
us3 ansible_host=192.168.10.153

[workers]
us154 ansible_host=192.168.10.154
us155 ansible_host=192.168.10.155

[all:vars]
ansible_user=da2001
ansible_port=22
ansible_ssh_private_key_file=~/.ssh/id_rsa.pub
```

- `--extra-vars` - передача переменных в команде запуска
```bash
ansible-playbook -i hosts.ini user.yml -K --extra-vars "user=da2001"
```

- `vars-prompt` - ввод переменных в интерактивном режиме при выполнении playbook
```bash
cat user.yml
```
```yaml
---
- name: user
  hosts: deploy
  vars_prompt:
    - name: user
      prompt: "Введите имя пользователя"
      private: no
  tasks:
    - name: Create user
      user:
        name: "{{ user }}"
        state: present
      become: true
```
`private: no` - отображать ли вводимые символы

[вверх](#top)

***

###### Chapter6
### Отладка

Очень полезно получать в консоль результат выполнения плейбука.  
1. Один из способов это создание в плейбуке поля `register` с какой-либо переменной для вывода. Далее этот вывод получаем в таске `debug`. Например:
```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Create user
      vars:
        user: da2001
      user:
        name: "{{ user }}"
        state: present
      become: true
      register: output
    - debug:
        var: output
```
После запуска плейбука
```bash
ansible-playbook -i hosts.ini user.yml -K
```
к выводу добавится значение таск `debug` с выводом значения созданной нами переменной `output`  
```json
TASK [debug] ***********************************************************************************************************
ok: [us1] => {
    "output": {
        "append": false,
        "changed": false,
        "comment": "da2001",
        "failed": false,
        "group": 1000,
        "home": "/home/da2001",
        "move_home": false,
        "name": "da2001",
        "shell": "/bin/bash",
        "state": "present",
        "uid": 1000
    }
}

```

2. Debugger
https://docs.ansible.com/ansible/latest/user_guide/playbooks_debugger.html#playbook-debugger

| Значение       | Результат |
|----------------|-----------|
| always         | всегда вызывать отладчик, независимо от результата |
| never          | никогда не запускать отладчик, независимо от результата |
| on_failed      | вызывать отладчик только в случае сбоя задачи |
| on_unreachable | вызывать отладчик только в том случае, если хост недоступен |
| on_skipped     | вызывать отладчик только в том случае, если задача пропущена |

```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Create user
      vars:
        user: da2001
      user:
        name: "{{ user }}"
        state: present
      become: true
      debugger: always
```
При наступлении условия, при котором вызывается отладчик, выполнение задачи приостанавливается и отладчик ожидает действий от пользователя  
```bash
user@host:~$ ansible-playbook -i hosts.ini user.yml -K
BECOME password: 

PLAY [user] ************************************************************************************************************

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

TASK [Create user] *****************************************************************************************************
ok: [us1]
[us1] TASK: Create user (debug)> 
```
Отладчик принимает команды:

| Команда                    | Сокр. вызов | Действие                                                     |
| -------------------------- | ----------- | ------------------------------------------------------------ |
| print                      | p           | напечатать информацию о задаче                               |
| task.args[*key*] = *value* | no shortcut | изменить значение аргумента                                  |
| task_vars[*key*] = *value* | no shortcut | изменить значение переменной (далее необходимо выполнить `update_task`) |
| update_task                | u           | повторно создать задачу с обновленными переменными           |
| redo                       | r           | заново запустить задачу                                      |
| continue                   | c           | продолжить выполнение, запуск следующей задачи               |
| quit                       | q           | выход из отладчика                                           |

*Примеры:*  
```
[us1] TASK: Create user (debug)> p task
TASK: Create user
```
```
[us1] TASK: Create user (debug)> p task.args
{'_ansible_check_mode': False,

...

 '_ansible_version': '2.11.6',
 'name': 'da2001',
 'state': 'present'}
```
```
[us1] TASK: Create user (debug)> p task.vars
{'user': 'da2001'}
```
```
[us1] TASK: Create user (debug)> p task_vars
{'ansible_all_ipv4_addresses': ['192.168.10.151'],

...

          'playbook_dir': '/home/da2001/ansible/udemy/ansible',
          'role_names': [],
          'user': 'da2001'}}
```
```
[us1] TASK: Create user (debug)> p task_vars['user']
'da2001'
```
```
[us1] TASK: Create user (debug)> p host
us1
```
```
[us1] TASK: Create user (debug)> task_vars['user'] = 'user'
[us1] TASK: Create user (debug)> p task_vars['user']
'user'
[us1] TASK: Create user (debug)> update_task
[us1] TASK: Create user (debug)> redo
```

[вверх](#top)

***

###### Chapter7
### Блоки и обработка ошибок

Пример блока
```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Preconfig block
      block:
        - name: Create user
          vars:
            user: da2001
          user:
            name: "{{ user }}"
            state: present
        - name: Install curl
          apt:
            name: curl
            update-cashe: yes
      become: true
```

Условие выполнения блока  
*в начале выполнения плейбука, ansible собирает факты о хостах, на основе полученных фактов можно описывать условия выполнения блоков.*  
Например: добавим условие, при котором блок будет выполняться только если в качестве хоста используется Ubuntu. Для этого в блок нужно добавить:
```yaml
when: ansible_facts['distribution' == 'Ubuntu']
```

Обработка ошибок
```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Preconfig block
      block:
        - name: Create user
          vars:
            user: da2001
          user:
            name: "{{ user }}"
            state: present
          register: error
        - name: Install curl
          apt:
            name: curl
            update-cashe: yes
          register: error
      become: true
      rescue:
        - name: Some error print
          debug:
            var: error
      always:
        - name: Reboot
          debug:
            msg: "Rebooooooting"
```
Обработка ошибок происходит в блоке `rescue`. В каждом задании мы определили `register: error` - переменную, которая будет отображаться при возникновении ошибки. Т.к. в блоке `rescue` определена единственная переменная `var: error` - она будет выводиться при ошибке в любом задании. Для каждого задания можно определить свою переменную.  

Блок `always` будет выполняться всегда, вне зависимости от того, были ошибки в заданиях или нет.  

**Другие возможности управления ошибками**  
- `any_errors_fatal` - true/false. Если установлено значение `true` - появление любой ошибки будет приводить к остановке плейбука, `false` - имеет обратное значение, плейбук попытается продолжить своё выполнение при любых ошибках.  
- `ignore_errors` - true/false. В некоторых задачах можно установить параметр, чтобы игнорировать возникающие ошибки и перейти к выполнению следующей задачи.  

- принудительный вызов ошибки. Можно создать отдельную задачу, в которой будет перехвачен вывод и будет сымитирована ошибка, которая приведёт к остановке выполнения плейбука. В нужное место плейбука добавить подобную задачу:  
```yaml
- name: Fail on FAILED
  command: echo "FAILED"
  register: command_result
  failed_when: "'FAILED' in command_result.stdout"
```
*при выполнении этой задачи, команда echo выведет в консоль слово `FAILED`, следующей строкой происходит перехват вывода и проверяется на соответствие условию - `'FAILED' in command_result.stdout`, если условие выполняется, сформируется ошибка и дальнейшее выполнение будет остановлено.*  

[вверх](#top)

***

###### Chapter8
### Асинхронные задачи

Ansible выполняет задачи плейбука последовательно, иногда возникают ситуации, когда есть независимые друг от друга задачи, которые могут выполняться параллельно. Именно для этого используется асинхронный запуск длинных задач.  
Т.е. задача запускается и ansible отключается от неё, чтобы подождать выполнение или переключиться на другую задачу.  
*Пример - 1*  
`async: 1000` - максимальное время в секундах, в течение которого задача должна выполниться  
`poll: 5` - интервал в секундах, в течение которого ansible будет проверять состояние задачи  

```bash
cat async.yml
```

```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Preconfig block
      block:
        - name: Sleep
          command: sleep 15
          async: 1000
          poll: 5
      become: true
```

```bash
user@host:~$ ansible-playbook -i hosts.ini async.yml -K
BECOME password: 

PLAY [user] ************************************************************************************************************

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

TASK [Sleep] ***********************************************************************************************************
ASYNC POLL on us1: jid=290251027418.31066 started=1 finished=0
ASYNC POLL on us1: jid=290251027418.31066 started=1 finished=0
changed: [us1]

PLAY RECAP *************************************************************************************************************
us1                        : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

```
`ASYNC POLL on us1: jid=290251027418.31066 started=1 finished=0` - каждые 5 секунд (значение poll) ansible подключается к задаче по id (`jid=290251027418.31066`) и проверяет её статус. При этом, переключение на следующую задачу не произойдёт до завершения текущей задачи.  

*Пример - 2*  
*Задача 1 - спим 15 секунд, не дожидаясь окончания задачи переходим к следующей*  
*Задача 2 - выполняем команду echo*  
*Зачада 3 - проверяем статус выполнения первой задачи*  
*`jid: "{{ sleep.ansible_job_id }}"` - получаем статус задачи __(выполнение задачи и получение её статуса важно делать от одного и того же пользователя, иначе ansible не увидит задачу. Т.е. если основная задача выполняется от sudo (`become: true`), то и проверять статус также нужно от sudo)__.*  
*ждём выполнения задачи `until: job_result.finished`, при этом задаём необязательные параметры -*  
*`retries: 100` - проверяем выполнение отслеживаемой задачи 100 раз*  
*`delay: 1` - задержка между повторами - 1 секунда.*
```bash
cat async.yml
```

```yaml
---
- name: user
  hosts: deploy
  tasks:
    - name: Preconfig block
      block:
        - name: Sleep
          command: sleep 10
          async: 1000
          poll: 0
          register: sleep
        - name: Echo
          command: echo "Задача выполнена"
      become: true
    - name: Check sleep status
      async_status:
        jid: "{{ sleep.ansible_job_id }}"
      register: job_result
      until: job_result.finished
      retries: 100
      delay: 1
      become: true
```
```bash
user@host:~$ ansible-playbook -i hosts.ini async.yml -K
BECOME password: 

PLAY [user] ************************************************************************************************************

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

TASK [Sleep] ***********************************************************************************************************
changed: [us1]

TASK [Echo] ************************************************************************************************************
changed: [us1]

TASK [Check sleep status] **********************************************************************************************
FAILED - RETRYING: Check sleep status (100 retries left).
FAILED - RETRYING: Check sleep status (99 retries left).
FAILED - RETRYING: Check sleep status (98 retries left).
FAILED - RETRYING: Check sleep status (97 retries left).
FAILED - RETRYING: Check sleep status (96 retries left).
FAILED - RETRYING: Check sleep status (95 retries left).
changed: [us1]

PLAY RECAP *************************************************************************************************************
us1                        : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

[вверх](#top)

***

###### Chapter9
### Пример playbook - установка Docker + Docker-compose
```yaml
---
- name: Preconfig
  hosts: deploy
  tasks:
    - name: Установка Docker
      block:
        - name: Добавление репо Ububntu universe
          apt_repository:
            repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} main universe restricted multiverse"
            state: present

        - name: Установка дополнительных пакетов
          apt:
            name:
              - ca-certificates
              - curl
              - gnupg
              - lsb-release
            update_cache: true
        
        - name: Добавление ключа Docker
          apt_key:
            url: https://download.docker.com/linux/ubuntu/gpg
            keyring: /usr/share/keyrings/docker-archive-keyring.gpg
            state: present

        - name: Установка репозитория Docker
          apt_repository:
            repo: >
              deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg]
              https://download.docker.com/linux/ubuntu
              {{ ansible_distribution_release }} stable
            state: present
            update_cache: true
            filename: docker
        
        - name: Установка Docker
          apt:
            name:
              - docker-ce
              - docker-ce-cli
              - containerd.io
            update_cache: true

        - name: Проверка корректности установки Docker
          service:
            name: docker
            state: restarted
            enabled: true
      become: true

    - name: Установка Docker-compose
      block:
        - name: Получение последней версии Docker-compose
          uri:
            url: "https://api.github.com/repos/docker/compose/releases/latest"
            body_format: json
          register: page

        - name: Установка Docker-compose
          get_url:
            url: "https://github.com/docker/compose/releases/download/{{ page.json.tag_name }}/docker-compose-Linux-x86_64"
            dest: /usr/local/bin/docker-compose
            mode: 0755
      become: true

    - name: Завершение установки
      block:
        - name: Добавление пользователя в группу Docker
          user:
            name: "{{ ansible_user }}"
            groups: docker
            append: true

        - name: Перезагрузка
          reboot:
      become: true
```
*перечень необходимых пакетов, ссылки на репозитории и прочие данные, необходимые для установки взяты из официальной документации*  
*https://docs.docker.com/engine/install/ubuntu/*  
*https://docs.docker.com/compose/install/*  

[вверх](#top)

***

```bash
git add . && git commit -m 'mod ansible' && git push
```
