Параметры ролей и зависимости

Урок 16 - делаем роли по-настоящему переиспользуемыми через параметры и зависимости.

«Хорошая роль - как функция: вызываешь с разными аргументами и получаешь разный результат, не трогая её код».

Роль становится по-настоящему полезной, когда её можно настраивать снаружи, не редактируя. Для этого роли передают параметры - переменные, которые перекрывают её defaults. А когда одна роль требует другую - используют зависимости.

Передача параметров

- hosts: web
  become: true
  roles:
    - role: nginx
      vars:
        nginx_port: 8080
        nginx_worker_processes: 8

В самой роли в defaults/main.yml заданы значения по умолчанию (nginx_port: 80), а при подключении мы их переопределяем. Роль не меняется - меняется её вызов. Это и есть «роль как функция с аргументами».

defaults роли

# roles/nginx/defaults/main.yml
nginx_port: 80
nginx_worker_processes: 4
ssl_enabled: false
server_domain: localhost

Все настраиваемые значения здесь имеют низкий приоритет - поэтому их легко переопределить из плея, group_vars или extra-vars.

Зависимости ролей

Если роль webapp не может работать без nginx, объяви зависимость в meta/main.yml:

# roles/webapp/meta/main.yml
dependencies:
  - role: nginx
    vars:
      nginx_port: 8080

Теперь при подключении webapp Ansible сначала выполнит nginx. Альтернатива - явное подключение в задачах через include_role (динамически, в нужный момент) или import_role (статически, на этапе разбора).

Как работает под капотом

Зависимости роли выстраиваются в граф: перед выполнением роли Ansible выполняет все её зависимости (в порядке объявления), затем саму роль. Параметры (vars при подключении) накладываются поверх defaults роли по правилам приоритета. Если две роли зависят от одной и той же третьей, по умолчанию она не дублируется в рамках одного «прохода».

Смоделируем разрешение зависимостей ролей и подстановку параметров.

# Разрешение зависимостей ролей + слияние параметров с defaults
role_meta = {
    "webapp": {"deps": ["nginx"], "defaults": {"app_port": 3000}},
    "nginx":  {"deps": [],        "defaults": {"nginx_port": 80}},
}

def run_order(role, seen=None):
    seen = seen or []
    for dep in role_meta[role]["deps"]:
        run_order(dep, seen)        # сначала зависимости
    if role not in seen:
        seen.append(role)
    return seen

def merge_params(role, overrides):
    final = dict(role_meta[role]["defaults"])
    final.update(overrides)         # переданные параметры перекрывают defaults
    return final

print("Порядок выполнения:", run_order("webapp"))
print("nginx с параметром:", merge_params("nginx", {"nginx_port": 8080}))

Попробуй сам ▶ Сначала выполняется nginx (зависимость), потом webapp. А переданный параметр nginx_port=8080 перекрыл дефолтный 80 - роль настроилась снаружи, без правки своего кода.

Частые ошибки

  • Менять код роли вместо параметров. Если правишь tasks роли под конкретный проект - роль перестаёт быть переиспользуемой. Используй переменные.
  • Циклические зависимости. Роль A зависит от B, B от A - так нельзя.
  • Путать include_role и import_role. import - статически на этапе разбора (видны теги, циклы плея); include - динамически в момент выполнения (можно по условию/в цикле).

Best practices

  • Все настраиваемые значения - в defaults, чтобы их легко перекрывать параметрами.
  • Жёсткие зависимости («без этого роль не работает») - в meta/main.yml; ситуативное подключение - через include_role.
  • Документируй переменные роли в README - так ей смогут пользоваться другие.

В реальной работе

Принцип «роль как функция» определяет качество роли. Плохая роль зашивает значения в задачи, и под каждый проект её приходится форкать и править. Хорошая выносит всё настраиваемое в defaults и документирует переменные в README - тогда её подключают как чёрный ящик и конфигурируют снаружи. Зависимости через meta удобны, но ими не злоупотребляют: жёсткая цепочка зависимостей делает роли связанными и менее гибкими. Чаще предпочитают явную композицию в playbook - перечислить роли в нужном порядке прямо в плее, чтобы порядок и параметры были видны на одном экране. А include_role с условием позволяет подключать роль динамически - например, ставить роль мониторинга только на продакшен-хостах.

Итоги

Роли настраиваются параметрами поверх своих defaults - как функции аргументами, без правки кода. Зависимости в meta гарантируют порядок выполнения, а include_role/import_role дают ручное подключение. Это делает роли переносимыми. Дальше - где брать готовые роли и как довести проект до прода.

Проверьте себя
1. Как переопределить значение по умолчанию роли, не редактируя её код?
AИзменить tasks/main.yml роли
BПередать переменную через vars при подключении роли (или group_vars/extra-vars) - она перекроет defaults
CУдалить defaults роли
DПереписать сам модуль
2. Где объявляют жёсткую зависимость одной роли от другой?
AВ tasks/main.yml
BВ meta/main.yml в секции dependencies - тогда зависимость выполнится перед ролью
CВ инвентаре
DЗависимости в Ansible невозможны