Группы хостов и их вложенность

Урок 5 - учимся группировать серверы, чтобы управлять ими пачками.

«Настроить nginx на всех веб-серверах одной командой - вот ради чего нужны группы».

Когда серверов много, перечислять их по одному неудобно. Ansible позволяет объединять хосты в группы - обычно по роли: веб-серверы, базы данных, балансировщики. Дальше ты можешь адресовать задачу не отдельному хосту, а целой группе.

Группы в INI

[web]
web1.example.com
web2.example.com

[db]
db1.example.com
db2.example.com

[loadbalancers]
lb1.example.com

Теперь ansible web -m ping проверит только веб-серверы, а ansible db -m ping - только базы. Один хост может входить в несколько групп одновременно.

Группы групп

Группы можно вкладывать друг в друга через :children. Например, объединить все продакшен-серверы:

[production:children]
web
db
loadbalancers

Теперь ansible production охватит все три группы разом. Это очень удобно для разделения окружений: production, staging, dev.

                  all
                   |
        +----------+----------+
        |          |          |
       web        db     loadbalancers
      /   \      /   \         |
   web1  web2  db1  db2       lb1

   production:children = web + db + loadbalancers

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

Ansible строит граф групп. Хост наследует принадлежность ко всем родительским группам. Когда ты адресуешь production, Ansible раскрывает дерево вниз до конкретных хостов и убирает дубликаты (один хост в двух группах попадёт в выборку один раз). Группа all - корень дерева.

Смоделируем раскрытие групп в плоский список хостов.

# Раскрытие вложенных групп в конкретные хосты
groups = {
    "web": ["web1", "web2"],
    "db": ["db1", "db2"],
    "loadbalancers": ["lb1"],
    # группа групп: ссылается на другие группы
    "production": ["web", "db", "loadbalancers"],
}

def resolve(group, groups, seen=None):
    seen = seen or set()
    hosts = set()
    for member in groups.get(group, []):
        if member in groups:          # это подгруппа
            hosts |= resolve(member, groups, seen)
        else:                          # это конкретный хост
            hosts.add(member)
    return hosts

print("web ->", sorted(resolve("web", groups)))
print("production ->", sorted(resolve("production", groups)))

Попробуй сам ▶ Видишь: production рекурсивно раскрылась в пять конкретных хостов без дублей. Точно так же Ansible определяет, к каким машинам реально применить задачу.

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

  • Забыть :children для группы групп. Без него Ansible решит, что web и db - это имена хостов, а не подгрупп.
  • Слишком дробные группы. Не плоди группу на каждый сервер. Группируй по смыслу: роль, окружение, регион.
  • Циклические группы. Группа A включает B, а B включает A - так делать нельзя.

Best practices

  • Группируй по роли (web, db) и по окружению (production, staging) - это два независимых среза.
  • Используй группы групп для окружений: меньше дублирования.
  • Имена групп - в нижнем регистре, без пробелов, осмысленные.

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

Хороший приём - проектировать сразу два независимых среза группировки. Первый по роли: web, db, cache - он определяет, что ставить на сервер. Второй по окружению: production, staging - он определяет, с какими параметрами. Один и тот же хост попадает в обе плоскости (например, в web и в production), а переменные из соответствующих group_vars складываются. Так роль nginx ставится на все веб-серверы одинаково, но порт и домен берутся из переменных окружения. Эта матрица «роль × окружение» - типовая архитектура инвентаря, которая масштабируется от трёх серверов до трёхсот без переписывания.

Итоги

Группы объединяют хосты по роли или окружению, а через :children можно строить группы групп. Ansible раскрывает дерево в плоский список хостов без дублей. Дальше - как раздавать этим группам переменные.

Проверьте себя
1. Как в INI-инвентаре объявить группу, состоящую из других групп?
A[group:vars]
B[group:children]
C[group:parents]
D[group:all]
2. Может ли один хост входить в несколько групп?
AНет, только в одну
BДа, и в выборке он появится один раз, без дублирования
CДа, но тогда задачи выполнятся дважды
DТолько если группы вложенные