# Ansible

# Установка и настройка

# Установка

Было написано, что необходим только python. Где-то предлагают устанавливать через pip, brew,

**На Debian-подобных системах:**

```bash
apt-get install ansible
```

**Настройка управляемых хостов**

На каждом хосте:

1. Если sudo нет, то ```bash
    apt install sudo
    ```
2. Если создается новый пользователь, то ```bash
    sudo adduser ansibleuser
    sudo usermod -aG sudo ansibleuser
    ```
3. Если существующий пользователь, то добавить в группу sudo: ```
    usermod -aG sudo sergey
    ```
4. Разрешить пользователю повышать привилегии без ввода пароля. Создать файл ```bash
    sudo nano /etc/sudoers.d/ansibleuser
    ```
5. И добавить текст ```
    ansibleuser ALL=(ALL) NOPASSWD:ALL
    ```
6. Установить пароль на пользователя ```
    passwd ansibleuser
    ```
7. Еще создать директорию /home/ansibleuser

Теперь можно использовать в скриптах become: true

**Настройка сервера**

```bash
ssh-keygen
ssh-copy-id username@remote_host
```

1. Создать ключ доступа
2. Скопировать на каждый управляемый хост, используя логин на хост. Удобнее, если для задач управления на всех хостах сделать одинаковый логин.

В настройке каждого хоста нужно использовать соответствующий логин, для которого настроен ключ доступа.

# Настройка ansible

<div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BE%D1%80%D0%B8%D1%82%D0%B5%D1%82-%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0-%D1%84%D0%B0%D0%B9"><div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BE%D1%80%D0%B8%D1%82%D0%B5%D1%82-%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0-%D1%84%D0%B0%D0%B9-1">**Приоритет поиска файла настроек**</div>1. ANSIBLE\_CONFIG (environment variable if set)
2. ansible.cfg (in the current directory)
3. ~/.ansible.cfg (in the home directory)
4. /etc/ansible/ansible.cfg (default)

</div><div id="bkmrk-%D0%93%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F-%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BA">**Генерация настроек**</div><div id="bkmrk-%C2%A0"> </div>```bash
ansible-config init --disabled > ansible.cfg
```

<div id="bkmrk-%D0%93%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F-%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BA-%24"></div><div id="bkmrk-%D0%93%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F-%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BA-%D1%81">С плагинами</div>```bash
ansible-config init --disabled -t all > ansible.cfg
```

<div id="bkmrk-%23-%D0%B8-%3B-%D0%BA%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8.-%D0%9D">\# и ; комментарии. Но ; обычно используется для комментария значения по умолчанию.</div><div id="bkmrk--1"></div><div class="align-center" id="bkmrk-%D0%A4%D0%B0%D0%B9%D0%BB-%D0%B8%D0%BD%D0%B2%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8">**Файл инвентаризации**</div><div id="bkmrk-%7E%2Fhosts-%D1%84%D0%B0%D0%B9%D0%BB-%D0%B8%D0%BD%D0%B2%D0%B5%D0%BD%D1%82%D0%B0">hosts </div><div id="bkmrk-%C2%A0-%C2%A0-%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D0%B5-%D1%84%D0%B0%D0%B9%D0%BB%D0%B0">Размещение файла может задаваться переменной окружения $ANSIBLE_HOSTS, либо ключ -i при запуске </div><div id="bkmrk-%D0%9F%D0%BE%D1%81%D0%BB%D0%B5-%3A-%D0%BF%D0%B8%D1%88%D0%B5%D1%82%D1%81%D1%8F-%D1%82%D0%B8%D0%BF-">После : пишется тип данных для группы. Например группа [example], [example:vars] это переменные</div><div id="bkmrk-"></div><div id="bkmrk-%D0%94%D0%BB%D1%8F-%D0%BE%D0%B1%D1%8B%D1%87%D0%BD%D0%BE%D0%B9-%D0%B3%D1%80%D1%83%D0%BF%D0%BF%D1%8B-%D0%B4">Для обычной группы должны быть указаны доменные имена с поднятым ssh сервером. Если порт отличается от 22, то должен быть указан </div><div id="bkmrk--2"></div><div id="bkmrk-%3Avars-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%B4%D0%BB%D1%8F">:vars переменные для группы</div><div id="bkmrk-%3Achildren-%D0%B3%D1%80%D1%83%D0%BF%D0%BF%D1%8B-%D0%BF%D0%BE%D1%82">:children группы-потомки</div><div id="bkmrk--3">  
</div><div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80">**Пример**</div>```yaml
# Обычная группа
[example]
www.example.com
1.2.3.4:2222

# Группа 'multi', состоящая из других групп
[multi:children]
example

#5 servers in one line [a:z] or [A:Z], or numbers with specific digits, such as [001:250].
[dyngroup]
Node[0:4].lab.edu

# Variables that will be applied to all servers
[multi:vars]
ansible_ssh_user=vagrant
```

Вывод дерева hosts:

```
ansible-inventory --graph
```

\--vars дополнительно выведет переменные

<div id="bkmrk-ansible_ssh_user%3Dvag"></div><div id="bkmrk-%D0%A4%D0%B0%D0%B9%D0%BB%D1%8B-%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BF%D0%B5%D1%80"><div id="bkmrk-%D0%98%D0%BC%D0%B5%D0%BD%D0%B0%3A">**Файлы размещения переменных:**</div><div id="bkmrk-%D1%84%D0%B0%D0%B9%D0%BB%D1%8B-%D1%81-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%BC%D0%B8-">Файлы с переменными групп хранятся в директории “group_vars/имя_группы”;</div><div id="bkmrk-%D1%84%D0%B0%D0%B9%D0%BB%D1%8B-%D1%81-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%BC%D0%B8--1">Файлы с переменными хостов в директории “hosts_vars/имя_хоста”;</div><div>Формат файла одинаковый, дочерние переменные заменяются на родительские.</div><div>Директории на уровне playbook</div></div>```yaml
---
ansible_user=setup
ansible_private_ssh_key=/home/user/ansible.key
```

Может применяться и плоское определение, и вложенное, тогда в скрипте {{ db.user }}:

```yaml
db: lll
  user: ppp
```

**Динамический реестр:**

Должен поддерживать следующий интерфейс:  
\--host=hostname - реестр выдает список в JSON { "ansible\_ssh\_host": "127.0.0.1", "ansible\_ssh\_port": 2200, "ansible\_ssh\_user": "vagrant"}   
\--list реестр выдает список групп {"staging": \[ "ontaro.ехамрlе.сом", "quebec.ехаmple.сом"\], "vagrant": \[ "vagrant1", "vagrant2", "vagrantЗ"\]}  
 "\_meta" : {"hostvars" : {"vagrant1" : {"ansible\_ssh\_host": "127.0.0.1", "ansible\_ssh\_port": 2222, "ansible\_ssh\_user": "vagrant"}, "vagrant2": {"ansible\_ssh\_host": "127.0.0.1", "ansible\_ssh\_port": 2200, "ansible\_ssh\_user": "vagrant"}}}  
 Если создать папку inventory и добавить в ansible.cfg параметр hostfile = inventory то статический hosts и динамический будут объединены  
 Если включить сбор фактов gather\_facts: True то можно во время выполнения задач группировать хосты (например debian/centos)

Использование динамического реестра (в примере my\_dynamic\_inventory.py должен быть исполняемым скриптом)

```bash
ansible-playbook -i my_dynamic_inventory.py my_playbook.yml
```

В Ссылках есть материал по динамическим реестрам

<div id="bkmrk-%7E%2Fgroup_vars%2Fexample"></div><div id="bkmrk--4"></div><div id="bkmrk--7"></div><div id="bkmrk-%D0%9F%D1%80%D0%B5%D0%B4%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%BF%D0%B5">**Предустановленные переменные**</div><div id="bkmrk-%D0%9F%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F-%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2"><table border="1" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 25.8562%;"></col><col style="width: 74.1438%;"></col></colgroup><tbody><tr><td class="align-center">Переменная</td><td class="align-center">Использование</td></tr><tr><td>ansible\_user</td><td>пользователь, от имени которого выполняются задачи на хостах данной группы. Значение в файле задач игнорируется.</td></tr><tr><td>ansible\_host</td><td>Сопоставление ip-имени</td></tr><tr><td>ansible\_port</td><td>Порт доступа по ssh</td></tr><tr><td>ansible\_connection</td><td>Может быть ssh, local, docker (запуск команд непосредственно на контейнере)</td></tr><tr><td>ansible\_become</td><td>+ sudo</td></tr><tr><td>ansible\_become\_user</td><td>sudo -&gt; another user</td></tr><tr><td>ansible\_ssh\_private\_key\_file</td><td>адрес ключа</td></tr></tbody></table>

</div><div id="bkmrk-%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D0%B8%3A">**Ссылки:**</div><div id="bkmrk-%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D0%BE%D0%B3%D0%BE-c">[Описание основного config файла](https://docs.ansible.com/ansible/latest/reference_appendices/config.html)</div><div id="bkmrk-%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9-%D1%80%D0%B5%D0%B5%D1%81%D1%82%D1%80">[Динамический реестр](https://habr.com/ru/articles/704518/)</div>

# Запуск и элементы управления ansible скриптами

<div class="align-center" id="bkmrk-ansible-%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82-%D0%BD%D0%B5-%D0%BE%D0%BF"><span style="color: rgb(224, 62, 45);">**Ansible скрипт не описывает что нужно сделать, а описывает как должно быть.**</span></div><div class="align-center" id="bkmrk-%D0%97%D0%B0%D0%BF%D1%83%D1%81%D0%BA-%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2">**Запуск одного действия**</div><div id="bkmrk-ansible-%5Bhost%2Fgroup%2F">ansible [host/group/all] [action parameters]</div><div id="bkmrk-%D0%94%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D1%8F%C2%A0">**Действия** </div><div id="bkmrk--a-%22shell-command%22-s">-a "shell command" shell command</div><div id="bkmrk--m-exec_name-install">-m exec_name installed module</div><div id="bkmrk--i-%3Cfilename%3E-invent">-i &lt;filename&gt; inventory file</div><div id="bkmrk---become-%D0%B2%D1%81%D0%B5-%D0%BA%D0%BE%D0%BC%D0%BC%D0%B0%D0%BD%D0%B4">--become все команды + sudo</div><div id="bkmrk---become-user-user1-">--become-user user1 обязательно с become, переключается на выбранного пользователя</div><div id="bkmrk--f-1-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BE">-f 1 использование одного потока. По умолчанию процессы параллельно для всех хостов. </div><div id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%3A">**Пример:**</div>```bash
ansible multi -a "hostname"
```

**Запуск через ansible playbook**

<div id="bkmrk-%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D0%B9-%D1%81%D0%BE%D1%85%D1%80">Список действий сохраняется в файле *.yml (playbook)</div>```bash
ansible-playbook filename.yml
```

<div id="bkmrk-%D0%A1%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0-playbook-%D1%84">**Структура playbook файла**</div>```yaml
---
- hosts: [host ip | host group | all]
    remote_user: []
    become: true
    tasks:
      - [task 1]
      - [task n]
---
- hosts: [host ip | host group | all]
    remote_user: []
    become: true
    tasks:
      - [task 1]
      - [task n]
```

**Параметры задачи**

gather\_facts: False Проводить сбор данных по умолчанию  
ignore\_errors: True Прекращать или нет работу при ошибке

**Переменные из модулей**

```yaml
---
- name: Check if host is up and running
  hosts: all
  become: false
  gather_facts: false

  tasks:
    - name: Ping the remote server
      ping:
      register: result
      until: result is succeeded
      retries: 3
      delay: 10

    - name: Get system uptime
      command: uptime
      when: result is succeeded
      register: uptime_result

    - name: Show system uptime
      debug:
        var: uptime_result.stdout
      when: result is succeeded
```

10: полученное значение из модуля ping сохраняется в переменной result

11: данное задание выполняется до тех пор, пока result не будет succeeded, но с ограничением кол-ва попыток (12) и задержкой между попытками (13)

17: задание 15 выполняется если result succeeded

18: полученное значение заносится в переменную uptime\_result

21: вывод значения uptime\_result

**Условия (handlers)**

```yaml
when: ansible_os_family == "Debian"
```

**Получить список фактов:**

```yaml
- name: Show facts available on the system
  ansible.builtin.debug:
  var: ansible_facts
```

В папке /etc/ansible/facts.d/\*.fact файлы с локальными фактами о машине. Обращение к фактам в yaml:

```yaml
ansible_local.<fact file name>.<fact group inside file>.<varname>
```

Создание новой переменной:

```yaml
set_fact:<valname>=<...>
```

Либо команда

```
ansible -i inventory.ini docker_hosts -m setup
```

inventory.ini - файл hosts, docker\_hosts - имя группы

**Циклы (loops)**

Цикл с объявленной переменной:

```yaml
 - name: use apt to install multiple apps
   apt:
     name: '{{ app }}'
     state: latest
     update_cache: yes
     vars:
       app:
         - htop
         - mc
         - nload
     become: yes
```

Цикл с необъявленной переменной:

```yaml
- name: use apt to install multiple apps
  apt:
    name: '{{ item }}'
    state: latest
    update_cache: yes
    loop:
      - htop
      - mc
      - nload
    become: yes
```

Цикл с переменной-словарем

```yaml
- name: install python packages
  pip: name={{item.name}} version={{item.version}}
  become: True
  with_items:
    - {name=mezzanine, version=1.2.1}
	- {name=guincorn, version=3.2.9}
```

**Проверка ошибок выполнения**

Проверяет условия и останавливает выполнение при нарушении

```yaml
- name: Проверка значения переменной
  assert:
    that:
      - my_variable == "expected_value"
```

<details id="bkmrk-%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B-%D0%B8%D0%B7-%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0"><summary>Примеры из документации:</summary>

```yaml
- name: A single condition can be supplied as string instead of list
  ansible.builtin.assert:
    that: "ansible_os_family != 'RedHat'"

- name: Use yaml multiline strings to ease escaping
  ansible.builtin.assert:
    that:
      - "'foo' in some_command_result.stdout"
      - number_of_the_counting == 3
      - >
        "reject" not in some_command_result.stderr

- name: After version 2.7 both 'msg' and 'fail_msg' can customize failing assertion message
  ansible.builtin.assert:
    that:
      - my_param <= 100
      - my_param >= 0
    fail_msg: "'my_param' must be between 0 and 100"
    success_msg: "'my_param' is between 0 and 100"

- name: Please use 'msg' when ansible version is smaller than 2.7
  ansible.builtin.assert:
    that:
      - my_param <= 100
      - my_param >= 0
    msg: "'my_param' must be between 0 and 100"

- name: Use quiet to avoid verbose output
  ansible.builtin.assert:
    that:
      - my_param <= 100
      - my_param >= 0
    quiet: true
```

</details>

# Модули

# Общая информация

Модули - созданный на python скрипт, упрощающий конкретную задачу.

Модули возвращают значения, название модуля должно быть под name. Есть общие переменные и специфичные для модулей

Справка по модулю

```bash
ansible-doc apt
```

Список модулей

```bash
ansible-doc -l 
```

Пример проверки необходимости перезагрузки

```yaml
- name: check if reboot is required
  become: yes
  become_method: sudo
  shell: "[ -f /var/run/reboot-required ]"
  failed_when: False
  register: reboot_required
  changed_when: reboot_required.rc == 0
  notify: reboot
  
handlers:
- name: reboot
  command: shutdown -r now "Ansible triggered reboot after system updated"
  async: 0
  poll: 0
  ignore_errors: true
```

Ссылки

[Информация на русском языке о модулях](https://runebook.dev/ru/docs/ansible/-index-)

# Системные модули и скрипты

<details id="bkmrk-%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%B8%2F%D0%B8%D0%BB%D0%B8-%D0%BF%D1%80%D0%BE%D0%B2"><summary>Установка и/или проверка установки apt пакета</summary>

**Название модуля:** apt

**Переменные:**

- name: ntp #имя проверяемого и устанавливаемого модуля
- state: present #состояние после завершения
- update\_cache: yes # обновлять ли кэш

```yaml
- name: Install module
  apt: 
    name: ntp
    state: present
    update_cache: yes
```

</details><details id="bkmrk-%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D0%BA%D0%BB%D1%8E%D1%87-%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD"><summary>Добавить ключ стороннего репозитория</summary>

```yaml
- name: Add Docker GPG key
  apt_key:
     url: https://download.docker.com/linux/ubuntu/gpg
```

</details><details id="bkmrk-%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D1%81%D1%82%D0%BE%D1%80%D0%BE%D0%BD%D0%BD%D0%B8%D0%B9-%D1%80"><summary>Добавить сторонний репозиторий</summary>

```yaml
- name: Add Docker repository
  apt_repository:
    repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable
```

</details><details id="bkmrk-%D0%9E%D0%B1%D0%BD%D0%BE%D0%B2%D0%B8%D1%82%D1%8C-apt-%D0%BA%D1%8D%D1%88---n"><summary>Обновить apt кэш</summary>

```yaml
- name: Update apt cache
  apt: update_cache=yes
```

</details>---

<details id="bkmrk-%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%81-%D0%BB%D0%BE%D0%BA%D0%B0%D0%BB%D1%8C"><summary>Копирование файлов</summary>

Копирование с локального на удаленный

```yaml
- name: Copy from server to client
  copy:
    src: /home/user/file.txt 
    dest: /home/setup/file.txt
    owner: foo
    group: foo
    mode: '0644'
```

Копирование с удаленного на локальный

```yaml
- name: Copy from client to server
  fetch:
    src: /var/log/access.log
    dest: /var/log/fetched
    flat: true удалить структуру родительских папок для файла
```

</details>---

<details id="bkmrk-%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB"><summary>Создание пользователя и группы</summary>

Создание пользователя

```yaml
- name: Create and/or check presence user
  user:
    name: install
    state: present
    shell: /bin/bash
    group: sudo
    system: yes
    hidden: yes
    ssh_key_file: .ssh/id_rsa
    expires: -1
```

Создание группы

```yaml
- name: Create check group
  group:
    name: clustergroup
    state: present
    gid: 1040
```

</details><details id="bkmrk-%D0%A3%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D0%B0%D0%BC%D0%B8"><summary>Управление сервисами (daemon)</summary>

```yaml
- name: Update sysctl
  sysctl:
    name: net.ipv4.ip_forward
    value: 1
    sysctrl_set: yes
    state: present
    reload: yes   
```

```yaml
- name: Set or check service
  service:
    name: ntp
    state: started
    enabled: yes
```

```yaml
- name: Set check daemon starting
  systemd:
    name: ntp
    state: started
    enabled: yes
    masked: no
    daemon_reload: yes
    register: systemd
```

cron:

</details><details id="bkmrk-%D0%A1%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B-%D0%B8-%D0%BA%D0%BE%D0%BD%D1%81%D0%BE%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5"><summary>Скрипты и консольные команды</summary>

```yaml
- name: Raw command
  raw: echo "this was written by a raw Ansible module!!" >> ~/raw.txt
```

```yaml
- name: Executing script
  shell: ./shell_script.sh >> ~/shell.txt
    args:
    chdir: /usr/local/
    creates: ~/shell.txt
    executable: /bin/csh
```

```yaml
- name: Executing python script
  script: ./shell_script.py –some-argumets "42"
    args:
    creates: ~/shell.txt
    executable: python
```

Для исполнения expect скриптов нужно сначала проверить и установить пакет expect

```yaml
- name: Expect module
  expect:
    command: passwd user1
    responses:
    (?i)password: "Ju5tAn07herP@55w0rd":
```

</details>

# Git и pip

<details id="bkmrk-git---name%3A-clone-up"><summary>Git</summary>

```yaml
- name: Clone update repo
  git:
    repo: https://github.com/ansible/ansible.git
    dest: /usr/local/ansible
    clone: yes
    update: yes 
```

</details><details id="bkmrk-pip-%D0%98%D0%B7-%D0%BE%D1%84%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE-"><summary>Pip</summary>

Из официального репозитория:

```yaml
- name: Install python package
  pip:
     name: numpy
     version: 0.3
```

Из внешнего источника

```yaml
- name: install a python library from a github
  pip:
    name: https://github.com/jakubroztocil/httpie
```

</details>

# Docker

**Необходимые модули:**   
pip install 'docker-py&gt;=1.7.0'  
pip install 'docker-compose&gt;=1.7.0'  
ansible-container позволяет работать с docker без dockerfile

<details id="bkmrk-%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D0%B0-"><summary>Создание контейнера</summary>

```yaml
- name: create a container
  docker_container:
    name: debianlinux
    image: debian:9
    pull: yes
    state: present
```

</details><details id="bkmrk-%D0%97%D0%B0%D0%BF%D1%83%D1%81%D0%BA-%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D0%B0---"><summary>Запуск контейнера</summary>

```yaml
- name: start a container
  docker_container:
    name: debianlinux
    state: started
    devices:
      - "/dev/sda:/dev/xvda:rwm"
```

</details><details id="bkmrk-%D0%9E%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D0%B0"><summary>Остановка контейнера</summary>

```yaml
- name: stop a container
  docker_container:
    name: debianlinux
    state: stopped
```

</details><details id="bkmrk-%D0%A3%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%B0-%D0%B8%D0%B7-%D0%BB"><summary>Удаление образа из локального хранилища</summary>

```yaml
- name: remove a container image
    docker_image:
      name: labimages/ubuntu
      state: absent
      tag: lab16
```

</details><details id="bkmrk-%D0%90%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D0%BD%D0%B0-docke"><summary>Авторизация на docker hub</summary>

```yaml
- name: login to DockerHub
  docker_login:
    username: labuser1
    password: "L@bp@55w0rd"
    email: user1@lab.edu
```

</details><details id="bkmrk-%D0%A1%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C-%D0%BE%D0%B1%D1%80%D0%B0%D0%B7-%D0%B8%D0%B7-doc"><summary>Скачать образ из docker hub</summary>

```yaml
- name: pull a container image
    docker_image:
      name: ubuntu:18.04
      pull: yes
```

</details><details id="bkmrk-%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C-%D0%BE%D0%B1%D1%80%D0%B0%D0%B7-%D0%B2-do"><summary>Сохранить образ в docker hub</summary>

```yaml
- name: push a container image to docker hub
  docker_image:
    name: labimages/ubuntu
    repository: labimages/ubuntu
    tag: lab18
    push: yes
```

</details>

# Консольные команды

При помощи модуля command можно выполнить команду на всех серверах из inventory файла.

```
ansible -i inventory.ini docker_hosts -m command -a "ip a" -b
```

-b выполнение от sudo

# Роли

Роль — способ организовать набор tasks, переменных, шаблонов, файлов и обработчиков в переиспользуемый модуль. Роли используются для конфигурации конкретных приложений. По умолчанию директория /etc/ansible/roles

Настраивается в cfg файле:

```
[defaults]
roles_path = ./roles
```

Также в переменной ANSIBLE\_ROLES\_PATH

**Структура роли и добавление в playbook**

1. Создается папка roles, внутри папки с названиями ролей, внутри каждой папки - defaults files handlers meta templates tasks vars
2. в playbook добавляется   
    ```yaml
       roles:
        - имя
    ```

**Назначения папок**

<table border="1" id="bkmrk-%D0%9F%D0%B0%D0%BF%D0%BA%D0%B0-%D0%9D%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-def" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 19.5587%;"></col><col style="width: 80.4413%;"></col></colgroup><thead><tr><td class="align-center">Папка</td><td class="align-center">Назначение</td></tr></thead><tbody><tr><td>defaults</td><td>Устанавливает переменные по умолчанию для включенных ролей. Обычно defaults/main.yml ```yaml
---
# Used only for Debian/Ubuntu installation, as the -t option for apt.
nginx_default_release: ""

# Used only for Redhat installation, enables source Nginx repo.
nginx_yum_repo_enabled: true

# Use the official Nginx PPA for Ubuntu, and the version to use if so.
nginx_ppa_use: false
nginx_ppa_version: stable

```

</td></tr><tr><td>files</td><td>содержит статические файлы и файлы сценариев, которые могут быть скопированы на удалённый сервер или выполнены на нём. Пример копирования внутри роли и выполнения:

roles/myrole/  
├── files/  
│ └── install.sh  
└── tasks/  
 └── main.yml

```yaml
- name: Copy script
  ansible.builtin.copy:
    src: install.sh
    dest: /tmp/install.sh
    mode: "0755"

- name: Execute script
  ansible.builtin.command:
    cmd: /tmp/install.sh
```

</td></tr><tr><td>handlers</td><td>Обработчики, выполняемые в самом конце</td></tr><tr><td>meta</td><td>для метаданных роли, которые используются для управления зависимостями. Например, вы можете определить список ролей, которые должны быть применены до вызова текущей роли.</td></tr><tr><td>templates</td><td>Шаблоны генерации файлов на удалённых хостах. Шаблоны jinja2.

Например нужно создать файл /etc/docker/daemon.json

Создаем шаблон в roles/docker/templates/daemon.json.j2

```
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "{{ docker_log_size }}",
    "max-file": "{{ docker_log_files }}"
  }
}
```

Добавляем переменные в roles/docker/defaults/main.yml

```yaml
docker_log_size: "10m"
docker_log_files: "3"
```

Создаем задачу

```yaml
- name: Deploy daemon.json
  ansible.builtin.template:
    src: daemon.json.j2
    dest: /etc/docker/daemon.json
    owner: root
    group: root
    mode: "0644"
  notify: Restart Docker
```

Создаем handlers roles/docker/handlers/main.yml

```yaml
- name: Restart Docker
  ansible.builtin.systemd:
    name: docker
    state: restarted
```

 В результате на сервере появится json файл.

[Описание Jinja2](http://bobrobotirk.ru/books/python/page/jinja2 "Jinja2")

</td></tr><tr><td>tasks</td><td>Содержит файлы с задачами, которые определяются в разделе tasks обычного плейбука Ansible. Сначала просматривается файл tasks/main.yml В нем указывается условия импорта других файлов

```yaml
---
# Variable setup.
- name: Include OS-specific variables.
  include_vars: "{{ ansible_os_family }}.yml"

- name: Define nginx_user.
  set_fact:
    nginx_user: "{{ __nginx_user }}"
  when: nginx_user is not defined

# Setup/install tasks.
- include_tasks: setup-RedHat.yml
  when: ansible_os_family == 'RedHat'

- include_tasks: setup-Ubuntu.yml
  when: ansible_distribution == 'Ubuntu'

- include_tasks: setup-Debian.yml
  when: ansible_os_family == 'Debian'

# Vhost configuration.
- import_tasks: vhosts.yml

# Nginx setup.
- name: Copy nginx configuration in place.
  template:
    src: "{{ nginx_conf_template }}"
    dest: "{{ nginx_conf_file_path }}"
    owner: root
    group: "{{ root_group }}"
    mode: 0644
  notify:
    - reload nginx

- name: Ensure nginx service is running as configured.
  service:
    name: nginx
    state: "{{ nginx_service_state }}"
    enabled: "{{ nginx_service_enabled }}"

```

Пути указываются относительно директории tasks

Эти задачи могут напрямую ссылаться на файлы и шаблоны, содержащиеся в соответствующих каталогах внутри роли, без необходимости указывать полный путь к файлу.

</td></tr><tr><td>vars</td><td>переменные для роли могут быть указаны в файлах внутри каталога, а затем ссылаться на них в другом месте роли.</td></tr></tbody></table>

**Ссылки:**

[Пример преобразования одного файла в роль](https://habr.com/ru/companies/slurm/articles/706920/)

**Хранилище ролей**

Есть сайт с ролями почти для всего. [Сайт с ролями ansible](https://galaxy.ansible.com) Можно найти нужную роль на сайте, затем установить при помощи

```
ansible-galaxy install current_role_name -p .
```

Создание роли:

```
ansible-galaxy init new_role_name
```

**Пример создания новой роли**

Пусть будет задача в установке docker. Реализуем из инструкции по [Docker: общая информация и установка](http://bobrobotirk.ru/books/docker-k8s/page/obshhaia-informaciia-i-ustanovka "Общая информация и установка")

Инициализируем новую роль

```
ansible-galaxy init just_new_role
```

Структура директорий создана. Проанализируем инструкцию.

- Написана для 3 вариаций ОС: Debian, Ubuntu, Alt.
- Последовательность установки: 
    - Создание пользователя
    - Добавление сертификатов
    - Непосредственно установка пакетов
    - Дополнительные удобства

В константы будем постепенно добавлять нужные. Итоговый defaults/main.yml :

```yaml
---
# defaults file for just_new_role
docker_user: webadmin
packages_certificates:
  - ca-certificates
  - curl
  - gnupg
  - lsb-release
packages_docker_debian:
  - docker-ce
  - docker-ce-cli
  - containerd.io
  - docker-compose-plugin
packages_docker_alt:
  - docker-engine
  - docker-compose-v2

```

Начнем с установки sudo и добавления пользователя в группы, то есть реализуем следующие команды:

```
apt-get install sudo
sudo apt-get update
usermod -aG sudo docker_user
```

Файл задач tasks/01\_create\_user.yml :

```yaml
- name: Install sudo
  ansible.builtin.apt:
    name: sudo
    state: present
    update_cache: true
- name: Ensure user exists and is in sudo group
  ansible.builtin.user:
    name: "{{ docker_user }}"
    groups: sudo
    append: true
    state: present
```

Да, если сопоставить количество строк команд и того, что необходимо написать для их исполнения, возникает ощущение перебора (в 3 раза). Но это плата за индепотентность.

Добавим в файл tasks/main.yml задачу старта 01\_create\_user.yml

```yaml
---
# tasks file for just_new_role
- include_tasks: 01_create_user.yml
```

**Запуск роли**

Когда роль написана, нужно создать следующую структуру директорий для playbook

```
project/
├── inventory.ini
├── playbook.yml
└── roles/
    └── just_new_role/
        ├── defaults/
        ├── tasks/
        ├── handlers/
        └── ...
```

Пример файла inventory.ini

```
[docker_hosts]
192.168.1.100

[docker_hosts:vars]
ansible_user=webadmin
```

Пример playbook.yml

```yaml
---
- name: Install Docker
  hosts: all
  become: true

  roles:
    - just_new_role

```

Итоговые файлы:

main.yml

```yaml
---
# tasks file for just_new_role
- include_tasks: 01_create_user.yml
- include_tasks: 02_append_certs.yml
```

01\_create\_user.yml

```yaml
- name: Install sudo
  ansible.builtin.apt:
    name: sudo
    state: present
    update_cache: true
  when: ansible_distribution == "Debian"
- name: Ensure user exists and is in sudo group
  ansible.builtin.user:
    name: "{{ docker_user }}"
    groups: sudo
    append: true
    state: present
- name: Install required packages
  ansible.builtin.apt:
    name: "{{ packages_certificates }}"
    state: present
    update_cache: true
  when: ansible_distribution == "Debian"

```

02\_append\_certs.yml

```yaml
- name: Create apt keyrings directory
  ansible.builtin.file:
    path: /etc/apt/keyrings
    state: directory
    owner: root
    group: root
    mode: "0755"

- name: Download Docker GPG key
  ansible.builtin.get_url:
    url: https://download.docker.com/linux/debian/gpg
    dest: /etc/apt/keyrings/docker.asc
    mode: "0644"

- name: Create binary keyring
  ansible.builtin.command:
    cmd: >
      gpg --dearmor
      --output /etc/apt/keyrings/docker.gpg
      /etc/apt/keyrings/docker.asc
    creates: /etc/apt/keyrings/docker.gpg

- name: Add Docker repository
  ansible.builtin.apt_repository:
    repo: >-
      deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg]
      https://download.docker.com/linux/debian
      {{ ansible_distribution_release }} stable
    filename: docker
    state: present

- name: Update apt cache
  ansible.builtin.apt:
    update_cache: true

- name: Install required packages
  ansible.builtin.apt:
    name: "{{ packages_docker_debian }}"
    state: present
    update_cache: true
  when: ansible_distribution == "Debian"

- name: Ensure user exists and is in sudo group
  ansible.builtin.user:
    name: "{{ docker_user }}"
    groups: docker
    append: true
    state: present

- name: Enable and start Docker service
  ansible.builtin.systemd:
    name: docker
    enabled: true
    state: started

```

# Тестирование

Лучше тестовая среда - предпрод среда - прод среда

Виды тестирования

<table border="1" id="bkmrk-e2e-%D0%A2%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BE%D1%81%D0%BD" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 27.065%;"></col><col style="width: 72.935%;"></col></colgroup><tbody><tr><td>E2E</td><td>Тестирование основного бизнес функционала. Объединенная работа нескольких сервисов</td></tr><tr><td>Системное тестирование</td><td>Тестирование одного сервиса. Обычно ручное. Варианты:

- На базе требований: тесты на основании требований к ПО
- На базе действий: тесты на основании использовании ПО

</td></tr><tr><td>Интеграционное тестирование</td><td>Тестирование взаимодействий сервисов</td></tr><tr><td>Unit тестирование</td><td>Проверка корректности отдельных модулей</td></tr><tr><td>Mock тестирование</td><td>Фиктивная реализация интерфейса для тестирования</td></tr></tbody></table>

Quality gate - модуль тестирования

Выполнение скрипта с кодом возврата:

```yaml
tasks:
  - script: test_script_1
  - script: test_script_2 --parameter1 value1 --parameter2 value2
```

Проверка наличия файлов модулем stat

```yaml
tasks:
  - stat:
      path: /path/to/something
      register: p
  - assert:
      that: 
      - p.stat.exist and p.stat.isdir
```

Использование модуля assert

```yaml
tasks:
  - shell: /usr/bin/some_command --some_parameter value
    register: cmd_result
  - assert:
    that:
    - "'not ready' not in cmd_result.stderr"
    - "'gizmo enabled' in cmd_result.stdout"
```

**Жизненный цикл тестирования:**

- Использовать один и тот же playbook при тестировании всех сред, включая production
- Запускайте тестирование каждый раз
- Использовайте тесты, написанные командой обеспечения качества
- Используйте те же самые тесты при деплое в production

Достижение непрерывного развертывания

- Использовать автоматизацию при развертывании виртуальных машин (развертывание по кнопке)
- При помощи CI системы развертывайте на тестовой среде
- Одна из задач при деплое вызывает тестирующий скрипт со статусом Прошел/ не прошел перед каждым деплоем
- Если деплой прошел успешно, то сборка повторяется на production контуре

**Пример схемы CI/CD**

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-06/scaled-1680-/image.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-06/image.png)

**Molecule**

Поддерживает текущую версию и -1 промежуточной (второй цифры). Установка:

```
pipx install molecule
```

**Инициализация molecule**

В директории с папкой roles выполнить

```
molecule init scenario name_of_role
```

При инициализации создается несколько файлов в molecule/name\_of\_role/

**Назначения файлов**

<table border="1" id="bkmrk-molecule.yml-%D0%9A%D0%BE%D0%BD%D1%84%D0%B8%D0%B3%D1%83" style="border-collapse: collapse; width: 100%; height: 297.969px;"><colgroup><col style="width: 17.0545%;"></col><col style="width: 82.9455%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">molecule.yml</td><td style="height: 29.7969px;">Конфигурация запуска фреймворка. Основной файл.

Блоки настройки:

dependency: управление зависимостями.

driver: управление сервером тестирования. По умолчанию docker. Устанавливаются отдельно.

platforms: конкретный элемент для запуска

provisioner: инструмент запуска converge.yml

verifier: инструмент запуска verify.yml для проверки перехода в нужное состояние.

lint: инструмент поиска синтаксических ошибок.

scenario: жизненный цикл тестов. Может быть несколько.

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">converge.yml</td><td style="height: 29.7969px;">Playbook для накатывания роли</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">verify.yml</td><td style="height: 29.7969px;">Тестирование того, что роль успешно применилась</td></tr></tbody></table>

В сценариях используются еще блоки, их называют программами.

molecule test запускает test\_sequence

**Karate**

OpenSource инструмент для тестирования, удобно тестировать API

[![image.png](http://bobrobotirk.ru/uploads/images/gallery/2026-06/scaled-1680-/QzHimage.png)](http://bobrobotirk.ru/uploads/images/gallery/2026-06/QzHimage.png)

Формирует отчеты.

# Файл inventory и переменные

Список хостов с тэгами (группами), на которые впоследствии ссылаются в задачах.

**Последовательность поиска файла**

По умолчанию: /etc/ansible/hosts

Однако проще указывать файл при запуске playbook

```bash
ansible-playbook -i my_inventory.ini playbook.yml
```

**Наследование переменных**

- переменные группы all
- переменные родительской группы
- переменные дочерней группы
- переменные хоста

**Варианты запуска inventory файла**

```
ansible-playbook -i prod.ini

ansible.cfg:
[defaults]
inventory = /root/ansible_playbooks/inventory/cloud_inv.py
```

Проверка интерпретации файла:

```bash
# Покажет структуру inventory в JSON
ansible-inventory -i ваш_файл --list

# Покажет в удобном для человека виде
ansible-inventory -i ваш_файл --graph
```

Можно в директории хранить несколько файлов и использовать их по необходимости.

```bash
# Посмотреть все хосты из inventory
ansible all -i inventory.ini --list-hosts

# Посмотреть переменные конкретного хоста
ansible -i inventory.ini web1 -m debug -a "var=hostvars[inventory_hostname]"

# Выполнить команду на группе серверов
ansible web_servers -i inventory.ini -m command -a "df -h"
```

**Форматы файлов:**

Есть динамический, ini и yaml формат. Определяет формат по содержанию файла, расширение файла (.ini, .yaml, .yml) не играет роли. Порядок анализа:

- Является ли файл исполняемым — если да, то запускает его как динамический inventory
- Пытается распарсить как INI — если получается, использует INI-формат
- Пытается распарсить как YAML — если получается, использует YAML-формат
- Если ничего не подошло — выдает ошибку

**Важно:**

1. Нельзя смешивать форматы в одном файле
2. Способ отличия форматов:  
    INI-формат: начинается с секций в квадратных скобках \[group\]  
    YAML-формат: начинается с --- (опционально) или со слова all: / children: / hosts:
3. Автодетект может ошибаться. Если будет файл, валидный как INI и как YAML, то Ansible выберет INI.

**ini формат:**

Ожидается, что hosts в формате ini.

```yaml
[example]
www.example.com
192.168.0.1
192.168.0.2:2222
```

Внутри \[\] название группы, далее состав этой группы (доменное имя или IP). Указание порта определяет порт ssh сервера.

**Группы по умолчанию**

- Группа all - все хосты
- Группа ungrouped - хосты без группы

**Вложенные группы**

При указании :children создается метагруппа. То есть

```yaml
[staging:children]
web_servers
db_servers
```

создаст группу staging, которая будет состоять из хостов группы web\_servers и db\_servers. Это именно группы, хосты указывать в метагруппе нельзя. Можно городить любую иерархию, одна метагруппа внутри другой.

**Диапазоны**

```
web[01:05].example.com
```

**Переменные**

Для хоста переменные через пробел в одной строке

```yaml
[databases]
192.168.1.20 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/ubuntu-key.pem
```

При помощи :vars задаются переменные для группы

```yaml
[staging:vars]
ansible_user=staging_user
env=staging
app_version=latest
```

**yaml формат**

<table border="1" id="bkmrk-%D0%9A%D0%BE%D0%B3%D0%B4%D0%B0-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C-y" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><thead><tr><td class="align-center">Когда использовать YAML</td><td class="align-center">Когда проще INI</td></tr></thead><tbody><tr><td>Сложная иерархия групп (3+ уровня)

Много переменных со сложными типами (списки, словари)

Храните инвентарь в Git (лучше для code review)

Команда привыкла к YAML

</td><td>Мало хостов (&lt;20)

Простая структура (1-2 уровня групп)

Быстрые временные инвентари

</td></tr></tbody></table>

Корневая группа одна — all. Группа all является специальной встроенной группой Ansible:

- в неё входят все хосты inventory
- все группы являются её потомками (напрямую или косвенно)

Поддерживает диапазоны аналогично ini

Если будет несколько корневых групп, то будет ошибка. Структура файла настроек:

```yaml
all:
  hosts:
  vars:
  children:
```

**Хосты:**

Внутри hosts указываются переменные для хоста.

```yaml
all:
  hosts:
    server1:
      ansible_host: 192.168.1.10
    server2:
      ansible_host: 192.168.1.11
```

**Переменные:**

```yaml
vars:
  http_port: 80
  app_user: nginx
```

Переменные из all.vars наследуются всеми хостами

Теперь переменные доступны в playbook

```yaml
- name: Show port
  debug:
    msg: "{{ http_port }}"
```

Переменные переопределяются по иерархии.

Создание дальнейшей вложенной структуры создает вложенную структуру переменной:

```yaml
all:
  hosts:
    db-master:
      replication:
        role: master
        sync_mode: synchronous
```

То есть у хоста db-master переменные replication.role и replication.sync\_mode. Можно также использовать списки:

```yaml
vars:
  allowed_ips:
    - 192.168.1.0/24
    - 10.0.0.0/8
```

**Группы:**

Внутри children указываются имена дочерних групп и их настройки. Структура дочерних групп аналогичная. Допускается множественное вложение.

```yaml
all:
  children:
    webservers:
      hosts:
        web1:
          ansible_host: 10.0.0.10
        web2:
          ansible_host: 10.0.0.11

    dbservers:
      hosts:
        db1:
          ansible_host: 10.0.0.20
```

Множественное вложение:

```yaml
all:
  children:
    msk_web:
      hosts:
        msk-web-01:
        msk-web-02:
    msk_db:
      hosts:
        msk-db-01:
    spb_web:
      hosts:
        spb-web-01:
    spb_db:
      hosts:
        spb-db-01:
    
    msk:
      children:
        msk_web
        msk_db
      vars:
        datacenter: moscow
        region: central
    
    spb:
      children:
        spb_web
        spb_db
      vars:
        datacenter: st_petersburg
        region: north-west
    
    russia:
      children:
        msk
        spb
    
    production:
      children:
        russia
      vars:
        env: production
        monitoring_enabled: true
```

Однако при усложнении правил (субъективно) теряется сама идея простоты настройки. То есть приходится объяснять правила объединения, рисовать зависимости, ... А когда переменные еще и отличаются для разных групп, и этих групп много, то все становится совсем плохо.

Выполнение playbook только для группы:

```
ansible-playbook -i inventory.yml site.yml --limit webservers
```

Можно использовать якоря

```yaml
defaults: &defaults
  ansible_user: admin
  ansible_port: 22

webservers:
  hosts:
    web1:
      <<: *defaults
      ansible_host: 192.168.1.10
    web2:
      <<: *defaults
      ansible_host: 192.168.1.11
```

**Разделение данных на файлы**

Предыдущий текст относится к ситуации, когда хосты и переменные расположены в одном файле. Есть возможность разбить проект на специально названные файлы. Например:

```
inventory/
└── testing/
    ├── hosts.yml
    ├── group_vars/
    │   ├── all.yml
└── production/
    ├── hosts.yml
    ├── group_vars/
    │   ├── all.yml
    │   ├── webservers.yml
    │   └── dbservers.yml
    └── host_vars/
        ├── web1.yml
        └── db1.yml
```

Создана директория inventory, внутри окружения testing и production, в каждом окружении файл hosts.yml и директории group\_vars и host\_vars. Запуск playbook будет выглядеть так:

```
ansible-playbook -i inventory/production/hosts.yml site.yml
```

hosts.yml: только хосты/группы, стандартная структура без переменных.

```yaml
all:
  children:
    webservers:
      hosts:
        web1:
        web2:

    dbservers:
      hosts:
        db1:
```

group\_vars/all.yml: Переменные для всех групп, все пишется на одном уровне

```yaml
ansible_user: ubuntu
timezone: Europe/Amsterdam
```

group\_vars/&lt;group\_name&gt;.yml: Переменные для группы group\_name, все пишется на одном уровне

host\_vars/&lt;host\_name&gt;.yml: Переменные только для хоста &lt;host\_name&gt;

**Динамический файл**

Это обычный исполняемый файл, который:

- Может быть написан на любом языке (Python, Bash, Go, Ruby и т.д.)
- При запуске возвращает JSON с описанием хостов и групп
- Ansible запускает его вместо чтения статического файла

```
ansible-playbook -i dynamic_inventory.py playbook.yml
```

Пример структуры JSON:

Например API отдает следующий JSON

```json
{
  "servers": [
    {
      "id": "i-12345abc",
      "ip_address": "192.168.1.10",
      "type": "web",
      "environment": "production",
      "name": "web-server-01",
      "status": "running",
      "region": "us-east-1",
      "instance_type": "t2.medium"
    },
    {
      "id": "i-12345def",
      "ip_address": "192.168.1.11",
      "type": "web",
      "environment": "production",
      "name": "web-server-02",
      "status": "running",
      "region": "us-east-1",
      "instance_type": "t2.medium"
    },
    {
      "id": "i-67890ghi",
      "ip_address": "192.168.1.20",
      "type": "db",
      "environment": "production",
      "name": "db-server-01",
      "status": "running",
      "region": "us-east-1",
      "instance_type": "r5.large"
    },
    {
      "id": "i-67890jkl",
      "ip_address": "192.168.1.21",
      "type": "db",
      "environment": "staging",
      "name": "db-server-02",
      "status": "running",
      "region": "us-west-2",
      "instance_type": "r5.large"
    },
    {
      "id": "i-99999mno",
      "ip_address": "192.168.1.30",
      "type": "web",
      "environment": "staging",
      "name": "web-server-03",
      "status": "stopped",
      "region": "us-west-2",
      "instance_type": "t2.micro"
    }
  ]
}
```

Тогда скрипт, разбирающий этот JSON в формат Ansible

```python
#!/usr/bin/env python3
import json
import requests

def get_servers():
    # Получаем список серверов из API
    response = requests.get('https://api.mycloud.com/v1/servers',
                            headers={'Authorization': 'Bearer token123'})
    servers = response.json()
    
    inventory = {
        'all': {
            'hosts': [],
            'vars': {}
        },
        'web': {
            'hosts': [],
            'vars': {'role': 'web'}
        },
        'db': {
            'hosts': [],
            'vars': {'role': 'database'}
        },
        '_meta': {
            'hostvars': {}
        }
    }
    
    for server in servers:
        ip = server['ip_address']
        inventory['all']['hosts'].append(ip)
        
        if server['type'] == 'web':
            inventory['web']['hosts'].append(ip)
        elif server['type'] == 'db':
            inventory['db']['hosts'].append(ip)
        
        # Переменные для конкретного хоста
        inventory['_meta']['hostvars'][ip] = {
            'ansible_host': ip,
            'environment': server['environment'],
            'instance_id': server['id']
        }
    
    print(json.dumps(inventory, indent=2))

if __name__ == '__main__':
    get_servers()
```

Использование скрипта

```bash
# Сделать скрипт исполняемым
chmod +x inventory.py

# Проверить, что выдает скрипт
./inventory.py

# Использовать с Ansible
ansible-inventory -i inventory.py --list
ansible-inventory -i inventory.py --graph

# Выполнить команду на всех хостах
ansible all -i inventory.py -m ping

# Выполнить команду только на web-серверах
ansible web -i inventory.py -m shell -a "nginx -v"

# Выполнить команду только на db-серверах
ansible db -i inventory.py -m shell -a "psql --version"
```

**Важные правила**

- Хост может быть в нескольких группах
- Переменные переопределяются. Приоритет (от низшего к высшему): 
    - all vars
    - Групповые vars
    - \_meta hostvars (самый высокий приоритет)
- Имена хостов и групп 
    - Могут содержать буквы, цифры, \_, -
    - Чувствительны к регистру
    - Не должны начинаться с \_ (зарезервировано для \_meta)

**Элементы JSON**

Группы ("group\_name")

Каждая группа может содержать три ключа:

```json
{
  "group_name": {
    "hosts": ["host1", "host2"],     // список хостов в группе
    "vars": {                        // переменные для всей группы
      "var1": "value1",
      "var2": "value2"
    },
    "children": ["child_group1", "child_group2"]  // вложенные группы
  }
}
```

Специальная группа "all"

Обязательная группа, которая содержит все хосты:

```json
{
  "all": {
    "hosts": ["web1", "web2", "db1"],
    "vars": {
      "ansible_python_interpreter": "/usr/bin/python3",
      "ntp_server": "pool.ntp.org"
    }
  }
}
```

Специальная группа "\_meta"

Содержит переменные для конкретных хостов:

```json
{
  "_meta": {
    "hostvars": {
      "web1": {
        "ansible_host": "10.0.0.1",
        "ansible_user": "deploy"
      },
      "web2": {
        "ansible_host": "10.0.0.2"
      }
    }
  }
}
```

При использовании \_meta, в группах можно просто перечислять имена хостов без переменных.

# Последовательность выполнения

В playbook может присутствовать несколько play, внутри каждого может быть 1+ tasks. Play отличается наличием списка хостов. То есть задачи в пределах одного play может быть только для одного и того же списка хостов. Для name критично именование для отличия одного от другого.

**Порядок выполнения внутри playbook**

Есть несколько элементов