Модуль 06 — Роли и коллекции

Разложить плейбук на переиспользуемые части с понятными параметрами

Прогресс курса Модуль 6 из 9

Что вы освоите в этом модуле

01

Зачем роли

Плейбук модуля 05 уже длинный, а нам нужна ещё база данных. Если всё держать в одном файле, то настройку nginx для второго проекта придётся копировать — а потом исправлять ошибку в двух местах. Роль — каталог с задачами, шаблонами, обработчиками и параметрами для одной задачи: «nginx с сайтом», «PostgreSQL с базой приложения». Плейбук превращается в список: каким узлам какие роли.

---
- name: Общая настройка всех узлов
  hosts: all
  become: true
  roles:
    - common

- name: Веб-узлы
  hosts: web
  become: true
  roles:
    - role: nginx_site
      vars:
        nginx_site_title: Сервис цен

- name: База данных
  hosts: db
  become: true
  roles:
    - postgres

Три роли проекта:

$ find roles -type f | sort
roles/common/defaults/main.yml
roles/common/tasks/main.yml
roles/nginx_site/defaults/main.yml
roles/nginx_site/handlers/main.yml
roles/nginx_site/meta/main.yml
roles/nginx_site/tasks/main.yml
roles/nginx_site/templates/index.html.j2
roles/nginx_site/templates/site.conf.j2
roles/postgres/defaults/main.yml
roles/postgres/meta/main.yml
roles/postgres/tasks/main.yml
02

Устройство роли

Каталоги роли фиксированы, и Ansible ищет в них файлы сам — путь к шаблону или обработчику указывать не нужно. Заготовку создаёт ansible-galaxy role init:

$ ansible-galaxy role init --init-path /tmp/skel demo >/dev/null && (cd /tmp/skel && find demo -type f | sort) && rm -rf /tmp/skel
demo/defaults/main.yml
demo/handlers/main.yml
demo/meta/main.yml
demo/README.md
demo/tasks/main.yml
demo/tests/inventory
demo/tests/test.yml
demo/vars/main.yml
КаталогЧто внутри
tasks/main.ymlЗадачи — точка входа роли
handlers/main.ymlОбработчики
templates/, files/Шаблоны для template и файлы для copy
defaults/main.ymlПараметры роли со значениями по умолчанию — самый слабый приоритет, их переопределяет кто угодно
vars/main.ymlВнутренние значения роли — сильнее инвентаря; то, что пользователю роли менять не надо
meta/main.ymlОписание, поддерживаемые системы и зависимости от других ролей
README.mdКакие параметры есть и пример использования

Ненужные каталоги удаляют — у наших ролей нет tests и vars. Роли ищутся в каталогах из roles_path (у нас roles рядом с плейбуком):

[defaults]
inventory = inventory.ini
remote_user = ansible
private_key_file = ~/.ssh/ansible_ed25519
interpreter_python = auto_silent
roles_path = roles

[ssh_connection]
# модули передаются через stdin одного SSH-сеанса, без временных файлов на узле:
# быстрее и не нужен пакет acl для become_user (модуль 06)
pipelining = True
03

Роль nginx_site

Это задачи и шаблоны модуля 05, разложенные по каталогам роли. Главное — параметры: всё, что может отличаться у разных пользователей роли, вынесено в defaults:

---
# Всё, что пользователь роли может поменять
nginx_site_title: Сайт
nginx_site_port: 80
nginx_site_root: /srv/www/site
---
- name: Пакет nginx установлен
  ansible.builtin.apt:
    name: nginx
    state: present
    update_cache: true
    cache_valid_time: 3600

- name: Каталог сайта
  ansible.builtin.file:
    path: "{{ nginx_site_root }}"
    state: directory
    owner: www-data
    group: www-data
    mode: "0755"

- name: Стартовая страница
  ansible.builtin.template:
    src: index.html.j2
    dest: "{{ nginx_site_root }}/index.html"
    owner: www-data
    group: www-data
    mode: "0644"

- name: Конфигурация сайта
  ansible.builtin.template:
    src: site.conf.j2
    dest: /etc/nginx/sites-available/default
    mode: "0644"
  notify: nginx перечитал конфигурацию

- name: Служба nginx запущена и в автозапуске
  ansible.builtin.systemd:
    name: nginx
    state: started
    enabled: true

В плейбуке роль подключена с параметром: role: nginx_site и vars: nginx_site_title: Сервис цен. Порт и каталог остались по умолчанию. Хорошая роль работает «из коробки» с разумными значениями и не требует от пользователя знать, что у неё внутри.

Имена переменных роли начинают с имени роли: nginx_site_port, postgres_password, common_admin_users. Переменные в Ansible общие для всего прогона, и две роли с переменной port будут мешать друг другу. Линтер (модуль 08) проверяет это правило.

04

Роль postgres и коллекции

---
- name: Пароль базы задан
  ansible.builtin.assert:
    that: postgres_password | length >= 12
    fail_msg: "Задайте postgres_password (не короче 12 символов) — лучше в Ansible Vault"
    quiet: true

- name: PostgreSQL и драйвер для модулей установлены
  ansible.builtin.apt:
    name: [postgresql, python3-psycopg2]
    state: present
    update_cache: true
    cache_valid_time: 3600

- name: PostgreSQL запущен
  ansible.builtin.systemd:
    name: postgresql
    state: started
    enabled: true

- name: Пользователь приложения
  community.postgresql.postgresql_user:
    name: "{{ postgres_user }}"
    password: "{{ postgres_password }}"
  become: true
  become_user: postgres
  no_log: true

- name: База приложения
  community.postgresql.postgresql_db:
    name: "{{ postgres_database }}"
    owner: "{{ postgres_user }}"
    encoding: UTF-8
  become: true
  become_user: postgres

Что есть в коллекции и какая её версия установлена:

$ ansible-galaxy collection list community.postgresql -p /usr/lib/python3/dist-packages 2>/dev/null | tail -2
-------------------- -------
community.postgresql 4.1.0  
$ ansible-doc -l community.postgresql 2>/dev/null | head -6
community.postgresql.postgresql_alter_system       Change a PostgreSQL serv...
community.postgresql.postgresql_copy               Copy data between a file...
community.postgresql.postgresql_db                 Add or remove PostgreSQL...
community.postgresql.postgresql_ext                Add or remove PostgreSQL...
community.postgresql.postgresql_idx                Create or drop indexes f...
community.postgresql.postgresql_info               Gather information about...

Если коллекции нет в пакете или нужна другая версия, её ставят командой ansible-galaxy collection install по списку из файла requirements.yml в репозитории проекта — так у всех участников одинаковые версии. Из России Galaxy доступен не всегда; запасной путь — скачать архив коллекции (.tar.gz) там, где доступ есть, и ставить из файла.

05

Прогон

Перед прогоном одна новая настройка в ansible.cfg — pipelining = True. Без неё Ansible копирует модуль во временный файл на узле, и при become_user обычному пользователю postgres нужно дать права на чужой временный файл — на Debian для этого требуется пакет acl. С pipelining модуль передаётся через тот же SSH-сеанс без файла: проблема исчезает, а прогон заметно ускоряется. Условие одно — в sudoers узлов не должно быть requiretty (в Debian его нет).

$ ansible-playbook site.yml

PLAY [Общая настройка всех узлов] **********************************************

TASK [Gathering Facts] *********************************************************
ok: [db1]
ok: [web2]
ok: [web1]

TASK [common : Общие пакеты установлены] ***************************************
changed: [web1]
changed: [db1]
changed: [web2]

TASK [common : Учётные записи администраторов] *********************************
changed: [web1] => (item=ivanova)
changed: [db1] => (item=ivanova)
changed: [web2] => (item=ivanova)

PLAY [Веб-узлы] ****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [web1]
ok: [web2]

TASK [nginx_site : Пакет nginx установлен] *************************************
changed: [web1]
changed: [web2]

TASK [nginx_site : Каталог сайта] **********************************************
changed: [web1]
changed: [web2]

TASK [nginx_site : Стартовая страница] *****************************************
changed: [web2]
changed: [web1]

TASK [nginx_site : Конфигурация сайта] *****************************************
changed: [web1]
changed: [web2]

TASK [nginx_site : Служба nginx запущена и в автозапуске] **********************
ok: [web1]
ok: [web2]

RUNNING HANDLER [nginx_site : Конфигурация nginx проверена] ********************
ok: [web1]
ok: [web2]

RUNNING HANDLER [nginx_site : Служба nginx перезагружена] **********************
changed: [web1]
changed: [web2]

PLAY [База данных] *************************************************************

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

TASK [postgres : Пароль базы задан] ********************************************
ok: [db1]

TASK [postgres : PostgreSQL и драйвер для модулей установлены] *****************
changed: [db1]

TASK [postgres : PostgreSQL запущен] *******************************************
ok: [db1]

TASK [postgres : Пользователь приложения] **************************************
[WARNING]: Module remote_tmp /var/lib/postgresql/.ansible/tmp did not exist and was created with a mode of 0700, this may cause issues when running as another user. To avoid this, create the remote_tmp dir with the correct permissions manually
changed: [db1]

TASK [postgres : База приложения] **********************************************
changed: [db1]

PLAY RECAP *********************************************************************
db1                        : ok=9    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web1                       : ok=11   changed=7    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web2                       : ok=11   changed=7    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Задачи в выводе подписаны именем роли: nginx_site : Конфигурация сайта. Предупреждение про remote_tmp выводится один раз: Ansible создал для пользователя postgres служебный каталог.

$ ansible-playbook site.yml | tail -5
PLAY RECAP *********************************************************************
db1                        : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web1                       : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web2                       : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
$ ansible db1 -b --become-user postgres -m command -a "psql -Atc \"select datname, pg_get_userbyid(datdba) from pg_database where datname = 'price'\""
db1 | CHANGED | rc=0 >>
price|price_app

База price принадлежит price_app. Без пароля роль не работает:

$ ansible-playbook site.yml --limit db1 -e postgres_password=12345 | grep -E -A3 "TASK \[postgres : Пароль"
TASK [postgres : Пароль базы задан] ********************************************
[ERROR]: Task failed: Action failed: Задайте postgres_password (не короче 12 символов) — лучше в Ansible Vault
Origin: /tmp/anspage.VcFGqF/ex/roles/postgres/tasks/main.yml:2:3
06

Зависимости и чужие роли

В meta/main.yml можно указать dependencies — роли, которые выполнятся перед этой. Пользуются этим осторожно: зависимость выполняется неявно, и по плейбуку уже не видно, что происходит. Чаще проще и понятнее перечислить роли в плейбуке по порядку.

Готовые роли и коллекции сообщества экономят время, но это чужой код, который выполняется на ваших серверах с правами root. Перед использованием читают задачи роли, фиксируют версию в requirements.yml и обновляют её осознанно — как любую другую зависимость.

Ключевые выводы модуля

Запомните главное

  • Роль — каталоги tasks, handlers, templates, defaults, meta; плейбук — какие роли каким узлам
  • Параметры роли — в defaults с разумными значениями; имена с префиксом роли
  • Обязательные параметры без значения по умолчанию проверяет assert в начале роли
  • Модули коллекций — по полному имени; версии чужих коллекций и ролей — в requirements.yml
  • become_user + pipelining = True; задачи с секретами — no_log: true
  • Чужая роль — чужой код с root на ваших серверах: читать перед использованием
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Модуль 05: Шаблоны и обработчики Все модули Модуль 07: Ansible Vault