Установка и использование Ansible: полное руководство по автоматизации - docs.devboxops.com

Установка и использование Ansible: полное руководство по автоматизации

Полное руководство по установке и настройке Ansible: от базовой установки до создания playbook и управления ролями

Установка и использование Ansible

Что такое Ansible

Ansible — простая и гибкая система управления конфигурациями. Подразумевает минимум два файла для начала работы:

  • inventory-файл — список хостов, разделенных на группы
  • файл задач (playbook) — указатель на нужные роли, написанный в YAML-формате

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

Модули Ansible

В состав Ansible входит огромное количество модулей для развёртывания, контроля и управления различными компонентами:

  • Мониторинг (Nagios, monit)
  • Управление пакетами (apt, yum, rhn-channel, npm, pacman, pip, gem)
  • Работа с утилитами (git, hg)
  • Система (LVM, SELinux, ZFS, cron, файловые системы, сервисы, модули ядра)
  • Сеть и сетевая инфраструктура (OpenStack, Arista)
  • Файлы (шаблонизация, регулярные выражения, права доступа)
  • Базы данных (MySQL, PostgreSQL, Redis, Riak)
  • Облачные ресурсы и виртуализация (OpenStack, libvirt)
  • Оповещения (Jabber, IRC, почта, MQTT, HipChat)

О том, с чем умеет работать Ansible “из коробки”, можно прочитать в официальной документации.

Установка и подготовка к работе

На рабочей машине подключаем PPA и устанавливаем актуальную версию:

apt update
apt install software-properties-common
add-apt-repository --yes --update ppa:ansible/ansible
apt install ansible

Важно: Привилегии root для работы с Ansible не нужны, поэтому все операции выполняются под обычным пользователем.

Применение на практике

Добавление сервера в inventory-файл

Пример готового файла inventory.yml:

---
all:
  vars:
    ansible_user: 'root'
    ansible_python_interpreter: /usr/bin/python3
  children:
    group1:
      hosts:
        server1:
          ansible_host: 'server1.example.com'
        server2:
          ansible_host: 'server2.example.com'
    group2:
      hosts:
        server3:
          ansible_host: 'server3.example.com'

Рекомендуем использовать именно YAML формат для inventory файла, т.к. он более читаем.

Обеспечение доступа к серверу для Ansible

Чтобы Ansible смог подключиться к серверу, необходимо предварительно добавить в /root/.ssh/authorized_keys на сервере публичную часть вашего ssh-ключа.

Проверка доступности хостов

Проверить доступность хоста для Ansible можно с помощью модуля ping:

ansible -m ping all -i ~/ansible_hosts

Пример вывода:

server-tester | SUCCESS => {
    "changed": false,
    "failed": false,
    "ping": "pong"
}

Подготовка playbook

Перед подготовкой необходимо ознакомиться с синтаксисом YAML. В playbook указываем:

  • Название
  • Хост
  • Пользователя, под которым Ansible будет работать
  • Путь к файлам с переменными
  • Нужные роли

Когда Ansible видит название роли (например, nginx), он ищет одноименный каталог в ~/ansible-project/roles/. Там должна быть готова структура роли: ~/ansible-project/roles/nginx/tasks/main.yml.

Пример playbook:

---
- name: General playbook
  hosts: all
  become: true
  become_method: sudo
  vars_files:
    - vault.yml

  roles:
    - role: nginx
      nginx_letsencrypt_cert_renewal : true
      nginx_vhosts:
        - main_server_config.j2
        - proxied_server_config.j2
      domain_name: "XXXYYYZZZ-students.devboxops.xyz"
      proxied_domain_name: "XXXYYYZZZ-proxied-students.devboxops.xyz"

    - role: user
      user_name: "dev-user"
      user_password: "prod_user_password"
      user_sudo: "yes"
      user_sudoers: ["/bin/systemctl stop systemd-journald", "/bin/systemctl start systemd-journald"]
      user_ssh_key: "key1"

    - role: user
      user_name: "prod-user"
      user_password: "dev_user_password"
      user_sudo: "yes"
      user_sudoers: ["/bin/systemctl stop rsyslog", "/bin/systemctl start rsyslog"]
      user_ssh_key: "key2"

Разбор playbook’a

  • General playbook - название
  • hosts: all - будет применен ко всем хостам из inventory, так же можно указывать группу.
  • become: true - все команды будут выполнены от рута
  • become_method: sudo - метод получения привелений рута - sudo
  • vars_files: [vault.yml] - список vars файлов(файлов с переменными). Сюда так же можно подключать ansible-vault сикреты.
  • roles: ... - список ролей и переменные которые передаются им для выпоолнения.

Запуск playbook

Когда inventory-файл и playbook готовы, запускаем Ansible командой:

ansible-playbook -i inventory.yml --ask-vault-pass playbook.yml

--ask-vault-pass - нужен только если используется ansible-vault

где playbook.yml — подготовленный playbook.

Пример минимального playbook с одной ролью:

- name: testsrv server
  hosts: testsrv
  remote_user: root
  roles:
    - nginx

Получаемый вывод:

$ ansible-playbook testsrv.yml -i ~/ansible_hosts

PLAY [testsrv server] *****************************************************************

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

TASK [nginx : install packages] *******************************************************
changed: [testsrv] => (item=[u'nginx'])

TASK [nginx : nginx service check] *****************************************************
ok: [testsrv]

TASK [nginx : nginx enable] ************************************************************
changed: [testsrv]

RUNNING HANDLER [nginx : nginx restart] ************************************************
changed: [testsrv]

PLAY RECAP ***************************************************************************
testsrv                     : ok=2   changed=3    unreachable=0    failed=0

Результаты выполнения модулей

В выводе отображается результат работы каждого модуля:

  • ok — модуль отработал корректно, изменения на сервере не требуются
  • changed — модуль отработал корректно, внесены изменения на сервер
  • unreachable — не удалось подключиться к серверу по SSH
  • failed — что-то пошло не так

Повторный запуск

Если какие-то модули отработали некорректно и причины были исправлены, playbook можно запустить повторно. Запуск пройдет быстрее, так как Ansible не будет повторно обновлять кэш apt, устанавливать пакеты и т.д.

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

$ ansible-playbook testsrv.yml -i ~/ansible_hosts

PLAY [testsrv server] *****************************************************************

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

TASK [nginx : install packages] *******************************************************
ok: [testsrv] => (item=[u'nginx'])

TASK [nginx : nginx service check] *****************************************************
ok: [testsrv]

TASK [nginx : nginx enable] ************************************************************
ok: [testsrv]

RUNNING HANDLER [nginx : nginx restart] ************************************************
ok: [testsrv]

PLAY RECAP ***************************************************************************
testsrv                     : ok=5    changed=0    unreachable=0    failed=0

Запуск с опцией –check

Ansible поддерживает режим Dry Run с опцией --check. При запуске playbook с этой опцией на сервере не будет произведено никаких изменений.

Особенности режима –check

  • Модули, поддерживающие check mode — сообщат об изменениях, которые должны быть внесены (без фактического применения)
  • Модули, не поддерживающие check mode — не будут осуществлять изменения, но и не сообщат о том, что должно быть изменено

Ограничения Dry Run

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

Пример проблемы с предварительно скачиваемым бинарником:

TASK [test-binary : extract binary to /usr/local/bin] ***************************
fatal: [testsrv]: FAILED! => {
  "changed": false,
  "msg": "Source '/tmp/test-binary.tar.gz' does not exist"
}

Типичные проблемы при использовании –check:

  • Скачивание файлов и последующее их использование
  • Проверка checksum различными шагами
  • Создание симлинков на несуществующие файлы

Параметры проверки в Ansible

В Ansible существует 3 параметра, связанных с проверкой:

  1. Опция –check (dry run)
  2. Check mode в модулях
  3. Параметр task’ов check_mode

Таблица поведения check mode

Таблица ниже демонстрирует что, происходит при их использовании(взято здесь)

|============|=========|======================|==================|
| check_mode | dry run | check mode supported | result           |
|============|=========|======================|==================|
|   true     |   no    |   no                 |   skip           |
|   false    |   no    |   no                 |   run            |
|   absent   |   no    |   no                 |   run            |
|------------|---------|----------------------|------------------|
|   true     |   no    |   yes                |   report changed |
|   false    |   no    |   yes                |   run            |
|   absent   |   no    |   yes                |   run            |
|============|=========|======================|==================|
|   true     |   yes   |   no                 |   skip           |
|   false    |   yes   |   no                 |   run            |
|   absent   |   yes   |   no                 |   skip (!)       |
|   true     |   yes   |   yes                |   report_changed |
|   false    |   yes   |   yes                |   run            |
|   absent   |   yes   |   yes                |   report_changed |
|============|=========|======================|==================|

Некоторые подробности доступны тут

Примеры работы с тасками

Структура Task

- name: Описание задачи
  module_name:
    параметр1: значение1
    параметр2: значение2
  when: <условие>
  loop: <список>
  loop_control:
    параметр: значение
  register: <переменная>
  tags:
    - имя_тега
  • name — читаемое описание задачи.
  • module_name — любой модуль Ansible (например, apt, copy, shell и т.д.).
  • when — условие, при котором задача выполняется.
  • loop — итерация по списку значений.
  • loop_control — настройки цикла (индексы, метки и т.д.).
  • register — сохранение результата выполнения задачи в переменную.
  • tags — теги для выборочного запуска.

Параметр when

Позволяет выполнять задачу только при истинности условия. Поддерживаются все Jinja2-выражения.

Синтаксис

when: <Jinja2-выражение>

Примеры

  1. Проверка ОС
- name: Установить пакет на Debian
  apt:
    name: htop
    state: present
  when: ansible_os_family == 'Debian'
  1. Сравнение версии
- name: Вывести сообщение, если версия Python < 3.8
  debug:
    msg: "Обновите Python"
  when: ansible_python.version.major < 3 or ansible_python.version.minor < 8
  1. Проверка наличия переменной
- name: Использовать кастомный порт, если задан
  debug:
    msg: "Порт = {{ custom_port }}"
  when: custom_port is defined
  1. Комбинация условий
- name: Выполнить только на продакшн-серверах
  shell: /usr/local/bin/deploy.sh
  when:
    - environment == 'production'
    - inventory_hostname in groups['web']

Параметр loop

Используется для повторного выполнения одной и той же задачи с разными параметрами.

Синтаксис

loop: <список_значений>

Примеры

  1. Простой список
- name: Создать директории
  file:
    path: "/data/{{ item }}"
    state: directory
  loop:
    - logs
    - backups
    - temp
  1. Список словарей
- name: Добавить пользователей
  user:
    name: "{{ item.name }}"
    uid: "{{ item.uid }}"
    groups: "{{ item.groups | default(omit) }}"
  loop:
    - { name: alice, uid: 1001, groups: wheel }
    - { name: bob,   uid: 1002 }
  1. Использование диапазона
- name: Настроить worker_*
  debug:
    msg: "Конфиг для worker_{{ item }}"
  loop: "{{ range(1, 5) | list }}"
  1. Цикл с проверкой результатов
- name: Попробовать подключиться к сервису
  shell: nc -zv {{ item.host }} {{ item.port }}
  register: result
  loop:
    - { host: db1, port: 5432 }
    - { host: db2, port: 5432 }
  failed_when: result.rc != 0 and loop.last

loop_control

Управляет поведением цикла и предоставляет доступ к служебным переменным.

Опции

  • index_var — имя переменной для текущего индекса (начиная с 0).
  • label — шаблон метки для вывода item.
  • pause — пауза между итерациями (секунд).

Пример

- name: Установить пакеты по порядку
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - git
    - curl
    - vim
  loop_control:
    index_var: pkg_index
    label: "{{ item }} (номер {{ pkg_index + 1 }})"

Проверки условий и управление выходом

failed_when и changed_when

Контролируют, считается ли задача неудачной или изменяющей состояние.

- name: Проверить доступность хоста
  ping:
  register: ping_result
  failed_when: ping_result.ping is not defined
  changed_when: false

ignore_errors

Игнорирует ошибки задачи.

- name: Попытка удалить файл
  file:
    path: /tmp/old_file
    state: absent
  ignore_errors: yes

retries и delay (с block/rescue)

Позволяют повторить блок задач при ошибке.

- block:
    - name: Попытка запустить сервис
      service:
        name: myapp
        state: started
  rescue:
    - name: Подождать и повторить
      pause:
        seconds: 10
    - name: Ещё одна попытка запустить сервис
      service:
        name: myapp
        state: started

Полезные примеры

Пример 1: Установка пакетов с условием и циклом

- hosts: all
  vars:
    packages:
      - git
      - htop
      - tree
  tasks:
    - name: Установить пакеты на RedHat
      yum:
        name: "{{ item }}"
        state: present
      loop: "{{ packages }}"
      when: ansible_os_family == 'RedHat'

Пример 2: Управление сервисами с проверками

- hosts: db
  tasks:
    - name: Проверить статус PostgreSQL
      shell: pg_isready
      register: pg_status
      ignore_errors: yes

    - name: Перезапустить службу при недоступности
      service:
        name: postgresql
        state: restarted
      when: pg_status.rc != 0

Пример 3: Создание пользователей и директорий

- hosts: all
  vars:
    users:
      - alice
      - bob
  tasks:
    - name: Создать пользователей
      user:
        name: "{{ item }}"
        state: present
      loop: "{{ users }}"

    - name: Создать домашние директории
      file:
        path: "/home/{{ item }}"
        state: directory
        owner: "{{ item }}"
        mode: '0755'
      loop: "{{ users }}"

Лучшие практики

  1. Объединять похожие задачи в один блок с loop вместо копирования кода.
  2. Использовать when для отключения необязательных шагов и повышения скорости выполнения.
  3. Регистировать результаты (register) и применять failed_when/changed_when для точного контроля.
  4. Применять loop_control для ясности логов и отладки.
  5. Использовать теги (tags) для выборочного запуска задач.

Шаблоны Jinja2

Ansible Template - это мощный механизм для создания динамических конфигурационных файлов, использующий шаблонизатор Jinja2. Шаблоны позволяют создавать переиспользуемые конфигурационные файлы с переменными, условиями и циклами, что делает автоматизацию более гибкой и эффективной.

Основы модуля template

Модуль ansible.builtin.template обрабатывает файлы через шаблонизатор Jinja2 и развертывает их на удаленных системах. Все шаблоны обрабатываются на управляющем узле Ansible до отправки на целевые хосты, что минимизирует требования к целевым системам.

Основной синтаксис

- name: Применение шаблона
  template:
    src: template.j2
    dest: /путь/к/файлу
    mode: '0644'
    owner: root
    group: root

Синтаксис Jinja2

Основные элементы

  • {{ переменная }} - подстановка значений переменных
  • {% управляющие_конструкции %} - условия, циклы, макросы
  • {# комментарий #} - комментарии в шаблонах

Условия (if/else)

Базовый синтаксис условий

{% if условие %}
  # код для выполнения если условие истинно
{% elif другое_условие %}
  # код для выполнения если другое условие истинно
{% else %}
  # код для выполнения если все условия ложны
{% endif %}

Примеры условий

1. Проверка существования переменной

{% if mysql_port is defined %}
port = {{ mysql_port }}
{% else %}
port = 3306
{% endif %}

2. Условие с логическими операторами

{% if ansible_hostname == 'master' and environment == 'production' %}
# Конфигурация для продакшн мастера
{% elif ansible_hostname == 'slave' or environment == 'staging' %}
# Конфигурация для слейва или staging
{% endif %}

3. Проверка вхождения в список

{% if 'web' in group_names %}
# Конфигурация для веб-серверов
{% endif %}

Операторы сравнения

  • == - равно
  • != - не равно
  • > - больше
  • < - меньше
  • >= - больше или равно
  • <= - меньше или равно
  • in - содержится в
  • not in - не содержится в

Циклы (for)

Базовый синтаксис циклов

{% for элемент in коллекция %}
  # код для каждого элемента
{% endfor %}

Примеры циклов

1. Простой цикл по списку

{% for user in users %}
- name: {{ user.name }}
  uid: {{ user.uid }}
  groups: {{ user.groups }}
{% endfor %}

2. Цикл с условием

{% for host in groups['web'] %}
{% if hostvars[host]['environment'] == 'production' %}
server {{ hostvars[host]['ansible_default_ipv4']['address'] }};
{% endif %}
{% endfor %}

3. Цикл по словарю

{% for key, value in database_config.items() %}
{{ key }} = {{ value }}
{% endfor %}

4. Цикл с использованием range

{% for i in range(1, 6) %}
worker_{{ i }} = /var/log/worker{{ i }}.log
{% endfor %}

5. Вложенные циклы

{% for database in databases %}
[{{ database.name }}]
{% for user in database.users %}
  user = {{ user.name }}
  password = {{ user.password }}
{% endfor %}
{% endfor %}

Полезные фильтры и функции

Фильтры для работы с данными

# Значение по умолчанию
{{ variable | default('default_value') }}

# Преобразование в JSON/YAML
{{ data | to_json }}
{{ data | to_yaml }}

# Объединение списка
{{ items | join(', ') }}

# Разделение строки
{{ path | split('/') }}

# Замена по регулярному выражению
{{ string | regex_replace('^(.*)$', 'prefix-\1') }}

# Обработка списков
{{ users | map(attribute='name') | list }}

Лучшие практики

  1. Используйте значения по умолчанию:
{{ variable | default('default_value') }}
  1. Проверяйте существование переменных:
{% if variable is defined %}
{{ variable }}
{% endif %}
  1. Делайте шаблоны читаемыми:
{# Комментарий о назначении блока #}
{% if condition %}
    # Хорошо отформатированный код
{% endif %}
  1. Используйте циклы для повторяющихся блоков:
{% for item in items %}
{{ item.config }}
{% endfor %}
  1. Валидируйте данные в шаблонах:
{% if users is defined and users | length > 0 %}
{% for user in users %}
{{ user.name }}
{% endfor %}
{% endif %}

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

Ссылки

Top