Модуль 02 — Инвентарь и разовые команды

Один файл со списком серверов — и любая команда выполняется на всех сразу

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

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

01

Инвентарь

В модуле 01 адрес узла, пользователь и ключ писались в каждой команде. Список узлов выносят в файл — инвентарь. Самый распространённый формат — INI:

# Учебные узлы курса. Имя слева — как узел называется в Ansible, ansible_host — куда подключаться.
[web]
web1 ansible_host=172.30.50.11
web2 ansible_host=172.30.50.12

[db]
db1 ansible_host=172.30.50.21

# Группа из групп: все узлы учебного «продакшна»
[prod:children]
web
db

[web:vars]
site_title=Сервис цен

Как Ansible понял инвентарь, показывает ansible-inventory: дерево групп и все переменные конкретного узла:

$ ansible-inventory --graph
@all:
  |--@ungrouped:
  |--@prod:
  |  |--@web:
  |  |  |--web1
  |  |  |--web2
  |  |--@db:
  |  |  |--db1
$ ansible-inventory --host web1
{
    "ansible_host": "172.30.50.11",
    "site_title": "Сервис цен"
}

То же самое можно записать в YAML — длиннее, зато удобнее, когда у узлов много переменных:

# Тот же инвентарь в формате YAML
all:
  children:
    web:
      hosts:
        web1:
          ansible_host: 172.30.50.11
        web2:
          ansible_host: 172.30.50.12
      vars:
        site_title: Сервис цен
    db:
      hosts:
        db1:
          ansible_host: 172.30.50.21
    prod:
      children:
        web:
        db:
$ ansible-inventory -i inventory.yml --graph
@all:
  |--@ungrouped:
  |--@web:
  |  |--web1
  |  |--web2
  |--@db:
  |  |--db1
  |--@prod:
  |  |--@web:
  |  |  |--web1
  |  |  |--web2
  |  |--@db:
  |  |  |--db1

Инвентарь не обязательно файл. Для облаков есть динамические инвентари — плагины, которые при запуске спрашивают у облака список машин и их метки. Принцип тот же: на выходе группы, узлы и переменные.

02

ansible.cfg

Пользователь, ключ и путь к инвентарю тоже одинаковы для всех команд — их пишут в файл настроек проекта:

[defaults]
inventory = inventory.ini
remote_user = ansible
private_key_file = ~/.ssh/ansible_ed25519
# Python на узлах искать как раньше, но без предупреждения
interpreter_python = auto_silent
# сколько узлов обрабатывать одновременно (по умолчанию 5)
forks = 10

Ansible ищет настройки в таком порядке и берёт первый найденный файл: переменная окружения ANSIBLE_CONFIG, ansible.cfg в текущем каталоге, ~/.ansible.cfg, /etc/ansible/ansible.cfg. Файлы не объединяются. Поэтому каждый проект держит свой ansible.cfg рядом с плейбуками и запускает Ansible из своего каталога. Какие настройки действуют сейчас и откуда они взялись:

$ ansible-config dump --only-changed | grep -E "^(DEFAULT_FORKS|DEFAULT_REMOTE_USER|INTERPRETER_PYTHON)" | sed "s/(.*)//"
DEFAULT_FORKS = 10
DEFAULT_REMOTE_USER = ansible
INTERPRETER_PYTHON = auto_silent

interpreter_python = auto_silent убирает предупреждение из модуля 01: Ansible по-прежнему сам находит Python на узле, но больше не напоминает об этом. Строже — указать путь явно: переменная ansible_python_interpreter со значением /usr/bin/python3 в инвентаре.

Если каталог проекта доступен на запись другим пользователям (например, общий каталог на сервере), Ansible не читает ansible.cfg из него: чужой файл настроек мог бы подменить ключ, пользователя или модули. Держите проект в своём домашнем каталоге или в каталоге с правами только для себя.

Теперь хватает имени группы и модуля:

$ ansible all -m ping
web2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.13"
    },
    "changed": false,
    "ping": "pong"
}
db1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.13"
    },
    "changed": false,
    "ping": "pong"
}
web1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.13"
    },
    "changed": false,
    "ping": "pong"
}
03

Какие узлы: шаблоны и --limit

Первый аргумент ansible — шаблон узлов: имя узла, группы или их сочетание. --list-hosts показывает, на каких узлах выполнится команда, ничего не выполняя:

$ ansible web --list-hosts
  hosts (2):
    web1
    web2
$ ansible "all:!db" --list-hosts
  hosts (2):
    web1
    web2
$ ansible prod --limit web2 --list-hosts
  hosts (1):
    web2
ШаблонУзлы
all или *Все
webГруппа web
web:dbОбъединение групп
web:&prodПересечение: узлы web, которые входят и в prod
all:!dbВсе, кроме группы db (в оболочке — в кавычках, иначе ! обработает bash)
web*Имена по маске

--limit (-l) дополнительно сужает выбор. Особенно он нужен с плейбуками: плейбук написан для группы, а проверить изменение хочется сначала на одном узле.

04

command и shell

Разовая команда (ad-hoc) — один модуль на выбранных узлах, без плейбука. Модуль по умолчанию — command:

$ ansible all -m command -a "cat /etc/debian_version"
web2 | CHANGED | rc=0 >>
13.7
db1 | CHANGED | rc=0 >>
13.7
web1 | CHANGED | rc=0 >>
13.7

command запускает программу напрямую, без оболочки. Переменные окружения он подставляет, а конвейеры, перенаправления и * — нет: | и всё после него становятся обычными аргументами echo. Модуль shell выполняет строку через /bin/sh, и конвейер работает:

$ ansible db -m command -a "echo \$HOME | tr a-z A-Z"
db1 | CHANGED | rc=0 >>
/home/ansible | tr a-z A-Z
$ ansible db -m shell -a "echo \$HOME | tr a-z A-Z"
db1 | CHANGED | rc=0 >>
/HOME/ANSIBLE

Без оболочки безопаснее: если в команду попадёт значение переменной с ; или $(…), command не выполнит его как код. Поэтому shell используют, только когда действительно нужны возможности оболочки.

05

Модули вместо команд и become

Поставим nginx на веб-узлы. Модуль apt без повышения прав работает от пользователя ansible — и apt ему отказывает:

$ ansible web1 -m apt -a "name=nginx state=present" | grep -E "\| FAILED|\"msg\""
[WARNING]: Updating cache and auto-installing missing dependency: python3-apt
web1 | FAILED! => {
    "msg": "E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)\nE: Unable to lock directory /var/lib/apt/lists/",

-b (--become) выполняет модуль через sudo от root. В плейбуках это ключ become: true — для всего плейбука или отдельной задачи. Права повышают только там, где они нужны: прочитать файл или проверить адрес можно и без root.

$ ansible web -b -m apt -a "name=nginx state=present update_cache=true" | st
web1 | CHANGED => {
web2 | CHANGED => {
$ ansible web -b -m apt -a "name=nginx state=present" | st
web2 | SUCCESS => {
web1 | SUCCESS => {

Первый запуск поставил nginx (CHANGED), второй ничего не менял (SUCCESS). Модуль systemd следит, чтобы служба работала и была в автозапуске. Debian запускает nginx сразу после установки, поэтому здесь ответ SUCCESS — менять нечего; на узле, где служба остановлена, модуль её запустит. uri делает HTTP-запрос с самого узла:

$ ansible web -b -m systemd -a "name=nginx state=started enabled=true" | st
web1 | SUCCESS => {
web2 | SUCCESS => {
$ ansible web -m uri -a "url=http://127.0.0.1/" | grep -E "\| |\"status\""
web2 | SUCCESS => {
    "status": 200,
web1 | SUCCESS => {
    "status": 200,

Модуль copy кладёт файл на узлы. Содержимое здесь задано прямо в команде, и в нём работают переменные: {{ inventory_hostname }} — имя узла, {{ site_title }} — переменная группы web из инвентаря:

$ ansible web -b -m copy -a "content='{{ site_title }}: {{ inventory_hostname }}\n' dest=/var/www/html/index.html" | st
web2 | CHANGED => {
web1 | CHANGED => {
$ ansible web -m uri -a "url=http://127.0.0.1/ return_content=true" | grep -E "\| |\"content\""
web2 | SUCCESS => {
    "content": "Сервис цен: web2\n",
web1 | SUCCESS => {
    "content": "Сервис цен: web1\n",
ЗадачаМодуль
Пакетыapt (package — для любого дистрибутива)
Службыsystemd (service)
Файлы и каталогиcopy, file, template (модуль 05)
Пользователи и группыuser, group, authorized_key
Проверить HTTPuri
Документация модуляansible-doc apt, список — ansible-doc -l
06

Посмотреть, не меняя: --check

--check — режим проверки: модули сообщают, что изменили бы, но ничего не делают. CHANGED означает «пакета нет, поставил бы», и на узле его действительно по-прежнему нет:

$ ansible web -b -m apt -a "name=htop state=present" --check | st
web2 | CHANGED => {
web1 | CHANGED => {
$ ansible web -m shell -a "command -v htop || echo htop не установлен"
web1 | CHANGED | rc=0 >>
htop не установлен
web2 | CHANGED | rc=0 >>
htop не установлен

Перед изменениями на боевых узлах --check показывает, на каких узлах и что поменяется. Не все модули умеют этот режим: command и shell в нём просто пропускаются, потому что Ansible не знает, что сделает команда.

07

Факты

Модуль setup собирает о узле факты: дистрибутив и версию, адреса, память, диски, процессоры — сотни значений. Плейбуки собирают их автоматически в начале работы и используют в условиях и шаблонах (модуль 04). filter оставляет только нужные:

$ ansible db1 -m setup -a "filter=ansible_distribution*"
db1 | SUCCESS => {
    "ansible_facts": {
        "ansible_distribution": "Debian",
        "ansible_distribution_file_parsed": true,
        "ansible_distribution_file_path": "/etc/os-release",
        "ansible_distribution_file_variety": "Debian",
        "ansible_distribution_major_version": "13",
        "ansible_distribution_minor_version": "7",
        "ansible_distribution_release": "trixie",
        "ansible_distribution_version": "13.7",
        "discovered_interpreter_python": "/usr/bin/python3.13"
    },
    "changed": false
}
$ ansible all -m setup -a "filter=ansible_default_ipv4" | grep -E "\| |\"address\""
db1 | SUCCESS => {
            "address": "172.30.50.21",
web1 | SUCCESS => {
            "address": "172.30.50.11",
web2 | SUCCESS => {
            "address": "172.30.50.12",

Сбор фактов занимает время на каждом узле. Если плейбуку они не нужны, его отключают ключом gather_facts: false.

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

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

  • Инвентарь — узлы, группы, вложенные группы и их переменные; проверяйте его ansible-inventory --graph и --host
  • ansible.cfg проекта лежит рядом с плейбуками; действует первый найденный файл, а не их объединение
  • Шаблоны узлов и --limit выбирают, где выполнять; --list-hosts показывает это заранее
  • command — без оболочки и безопаснее; shell — только когда нужны конвейеры и перенаправления
  • Права повышают become и только там, где нужно; --check показывает изменения, не внося их
  • Факты узла — модуль setup; плейбуки собирают их сами
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Модуль 01: Управление конфигурацией и Ansible Все модули Модуль 03: Плейбуки