Установка и использование 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 параметра, связанных с проверкой:
- Опция –check (dry run)
- Check mode в модулях
- Параметр 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-выражение>
Примеры
- Проверка ОС
- name: Установить пакет на Debian
apt:
name: htop
state: present
when: ansible_os_family == 'Debian'
- Сравнение версии
- name: Вывести сообщение, если версия Python < 3.8
debug:
msg: "Обновите Python"
when: ansible_python.version.major < 3 or ansible_python.version.minor < 8
- Проверка наличия переменной
- name: Использовать кастомный порт, если задан
debug:
msg: "Порт = {{ custom_port }}"
when: custom_port is defined
- Комбинация условий
- name: Выполнить только на продакшн-серверах
shell: /usr/local/bin/deploy.sh
when:
- environment == 'production'
- inventory_hostname in groups['web']
Параметр loop
Используется для повторного выполнения одной и той же задачи с разными параметрами.
Синтаксис
loop: <список_значений>
Примеры
- Простой список
- name: Создать директории
file:
path: "/data/{{ item }}"
state: directory
loop:
- logs
- backups
- temp
- Список словарей
- name: Добавить пользователей
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups | default(omit) }}"
loop:
- { name: alice, uid: 1001, groups: wheel }
- { name: bob, uid: 1002 }
- Использование диапазона
- name: Настроить worker_*
debug:
msg: "Конфиг для worker_{{ item }}"
loop: "{{ range(1, 5) | list }}"
- Цикл с проверкой результатов
- 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 }}"
Лучшие практики
- Объединять похожие задачи в один блок с
loop
вместо копирования кода. - Использовать
when
для отключения необязательных шагов и повышения скорости выполнения. - Регистировать результаты (
register
) и применятьfailed_when
/changed_when
для точного контроля. - Применять
loop_control
для ясности логов и отладки. - Использовать теги (
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 }}
Лучшие практики
- Используйте значения по умолчанию:
{{ variable | default('default_value') }}
- Проверяйте существование переменных:
{% if variable is defined %}
{{ variable }}
{% endif %}
- Делайте шаблоны читаемыми:
{# Комментарий о назначении блока #}
{% if condition %}
# Хорошо отформатированный код
{% endif %}
- Используйте циклы для повторяющихся блоков:
{% for item in items %}
{{ item.config }}
{% endfor %}
- Валидируйте данные в шаблонах:
{% if users is defined and users | length > 0 %}
{% for user in users %}
{{ user.name }}
{% endfor %}
{% endif %}
Эта документация предоставляет основные знания для работы с шаблонами Jinja2 в Ansible и позволяет создавать гибкие, переиспользуемые конфигурационные файлы для различных сценариев автоматизации.