Ansible — система управления конфигурациями, написанная на языке программирования Python. Используется для автоматизации настройки и развертывания программного обеспечения. Обычно используется для управления Linux-узлами, но есть большая коллекция плагинов для подключения к другим устройствам и ОС. Наряду с Chef, Puppet и SaltStack считается одной из наиболее популярных систем управления конфигурациями для Linux. Главное отличие Ansible от аналогов — не нужна установка агента/клиента на целевые системы. С помощью Ansible можно развернуть, удалить или сконфигурировать любое ПО на удаленных серверах.
В этой статье будет рассмотрен процесс создания роли для установки и настройки Asterisk.
Настройка параметров Ansible для подключения к серверам
Для начала создадим конфигурационный файл ansible.cfg. Ниже представлен список каталогов где он может находиться, от наиболее приоритетного к наименее:
Каталог определенный переменной окружения $ANSIBLE_CONFIG
Текущий каталог
Домашний каталог пользователя
Глобальный/общий файл в каталоге /etc/ansible
Проверить место расположения файла можно командой:
$ ansible --version
Я расположил его в каталоге ansible в домашней директории пользователя.
Содержимое файла:
[defaults]
inventory = /home/kstrakhov/ansible/hosts
host_key_checking = false
С помощью параметра inventory мы указываем файл со списком хостов и групп, на которых могут быть выполнены наши задачи.
Параметр host_key_checking позволяет отключить проверку ключа сервера при подключении по ssh.
Теперь создадим файл inventory, в котором будут перечислены целевые серверы:
[asterisk_group]
asterisk1 ansible_host=192.168.30.12
asterisk2 ansible_host=192.168.30.13
В квадратных скобках мы указываем название группы, в дельнейшем, при запуске плэйбука, мы сможем ссылаться на это название. Эти названия могут содержать в себе только буквы, цифры и нижнее подчеркивание. По умолчанию все серверы входят в группу all, те которые не принадлежат ни одной группе, входят в группу ungrouped. Группы можно объединять в группы:
[asterisk_all_group:children]
asterisk_group1
asterisk_group2
В списке хостов мы прописываем алиас и переменную ansible_host, в которой указывается ip-адрес хоста, можно указать FQDN, можно прописать FQDN без алиаса. Также с помощью переменных можно задать другие параметры подключения, например:
Порт ssh - ansible_port
Имя пользователя - ansible_user
Путь к ssh ключу - ansible_ssh_private_key_file
Пароль пользователя
Для дальнейшего удобства при использовании файла hosts некоторые параметры можно вынести в отдельный файл. Для этого создаем каталог group_vars в директории с файлом hosts и внутри создаем файл с названием группы. Добавляем в этот файл переменные, с использованием двоеточия вместо равно:
ansible_user : root
ansible_ssh_private_key_file : /home/kstrakhov/.ssh/id_rsa
Теперь Ansible будет использовать эти переменные для подключения к серверам, которые находятся в группе asterisk_group.
Посмотреть какие переменные применяются к хостам можно командой:
$ ansible-inventory --graph --vars
Создание роли
Теперь, когда подключение настроено, приступим к созданию роли:
$ ansible-galaxy init asterisk-role
В текущей директории появился каталог asterisk-role.
По умолчанию Ansible будет искать в каталогах роли файл main.yml и в зависимости от назначения файла, которое определяется его местонахождением, выполнять определенные действия.
defaults/main.yml- переменные по умолчанию для роли. Могут быть переопределены в файле inventory (hosts) или с помощью параметра –e (--extra-vars key=value) при запуске playbook.
files- файлы, которые могут быть скопированы при развертывании asterisk.
handlers/main.yml- файл обработчиков, используются в роли для выполнения действий при определенных условиях или определенных событиях.
meta/main.yml- метаданные роли.
tasks/main.yml- основной список задач, которые выполняет роль.
templates/ - шаблоны, которые развертывает роль, имеют расширение j2 (jinja2) и используются, например для генерации конфигурационных файлов. Их преимущество в том, что они могут содержать в себе переменные и/или факты (facts).
tests/ – этот каталог содержит в себе тестовый inventory файл и тестовый плэйбук (test.yml), используется для тестов.
vars/main.yml – здесь хранятся переменные, которые не переопределяются файлом inventory, но могут быть переопределены с помощью параметра -e (--extra-vars) при запуске плэйбука.
Для начала добавим переменные в файл vars/main.yml
---
SIP_MODULE: load
RTP_PORT_START: 22000
RTP_PORT_END: 23000
SOURCE_DIR: /usr/local/src/
ASTERISK_PACKAGE: "https://downloads.asterisk.org/pub/telephony/asterisk/asterisk-18-current.tar.gz"
ASTERISK_USER: asterisk
ASTERISK_GROUP: asterisk
Далее создадим первый плэйбук update_system.yml в каталоге tasks (который будет обновлять систему):
- name: Install epel repo
yum:
name: epel-release
state: present
- name: Update system
yum:
name: "*"
state: latest
register: allupdate
- name: Reboot machine after update and wait reconnect
reboot:
reboot_timeout: 300
when: allupdate.changed
В первых двух задачах мы добавляем репозиторий epel и обновляем систему. С помощью параметра state можно указать модулю yum, что делать с пакетами:
present и installed - гарантирует, что нужный пакет установлен.
latest - обновит указанный пакет, если он не последней доступной версии.
absent и removed - удалит указанный пакет.
С помощью параметра register мы помещаем результат выполнения команды в переменную allupdate. В третьей задаче, с помощью оператора when, мы проверяем значение секции changed в переменной allupdate, и если были изменения, перезагружаем сервер.
В следующем плэйбуке будут задачи, которые установят Asterisk из исходников и настроят систему для его запуска:
- name: Check if Asterisk is installed
shell: asterisk -V | grep -wo Asterisk
ignore_errors: true
changed_when: false
register: check_asterisk
- name: Create Asterisk User Group
group:
name: "{{ ASTERISK_GROUP }}"
state: present
- name: Create Asterisk User
user:
name: "{{ ASTERISK_USER }}"
shell: /sbin/nologin
group: "{{ ASTERISK_GROUP }}"
create_home: no
- name: Install Asterisk Process
block:
- name: Set SELinux to Permissive mode
command: "{{ item }}"
with_items:
- setenforce permissive
- sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
- name: Download and Extract Asterisk
unarchive:
src: "{{ ASTERISK_PACKAGE }}"
dest: "{{ SOURCE_DIR }}"
remote_src: yes
list_files: yes
register: asterisk_archive_contents
- debug:
msg: "Asterisk directory path is {{ SOURCE_DIR }}{{ asterisk_archive_contents.files[0] }}"
- name: Install Asterisk Packages
command: "{{ item }}"
with_items:
- contrib/scripts/install_prereq install
- ./configure --libdir=/usr/lib64 --without-pjproject-bundled --with-jansson-bundled
- make menuselect.makeopts
- menuselect/menuselect --disable-category MENUSELECT_CORE_SOUNDS --enable CORE-SOUNDS-RU-WAV --enable CORE-SOUNDS-RU-ALAW
- make
- make install
- make samples
- make config
args:
chdir: "{{ SOURCE_DIR }}{{ asterisk_archive_contents.files[0] }}"
- name: Set All Asterisk directory owner to asterisk user
file:
path: "{{ item }}"
owner: "{{ ASTERISK_USER }}"
group: "{{ ASTERISK_GROUP }}"
state: directory
recurse: yes
mode: 0750
with_items:
- /var/lib/asterisk
- /var/spool/asterisk
- /var/run/asterisk
- /var/log/asterisk
when:
- check_asterisk.rc != 0
- check_asterisk.stdout != 'Asterisk'
notify:
- start asterisk service
В первой задаче с помощью модуля shell, мы проверяем установлен ли Asterisk на сервере и указываем ему не выводить сообщения об изменениях (changed_when: false) и игнорировать сообщения об ошибках (ignore_errors: true), чтобы плэйбук выполнялся дальше. С помощью параметра register сохраняем результат в переменную check_asterisk.
Далее в задачах Create Asterisk User Group и Create Asterisk User мы создаем группу и пользователя, от которого будет запускаться Asterisk, и запрещаем ему вход в систему.
Далее приступаем к установке Asterisk. Задача “Install Asterisk Process” содержит в себе блок, который будет запускаться, если выполняются условия определенные оператором when (должен располагаться строго под блоком):
check_asterisk.rc != 0 - return code в переменной check_asterisk не равен нулю, если он равен нулю, значит команда была выполнена без ошибок.
check_asterisk.stdout != 'Asterisk' - stdout не содержит в себе «Asterisk», если содержит, значит он уже установлен на сервере.
Первой задачей в блоке мы переводим SELinux в режим permissive.
Далее мы скачиваем и распаковываем исходники Asterisk с помощью модуля unarchive. Параметр src в связке с remote_src, если src содержит ://, указывает удаленному серверу скачать файл с URL-адреса, а потом распаковать его и вернуть список распакованных файлов (list_files: yes). Результат выполнения этой команды помещается в переменную asterisk_archive_contents.
В следующей задаче, мы запускаем модуль debug, который выведет на экран полный путь до исходников Asterisk-а . С помощью [0] мы указываем модулю использовать первый элемент в списке (массиве) поля files ([1] – второй, [2] - третий и т.д.).
В задаче “Install Asterisk Packages” с помощью модуля command мы запускаем процесс установки Asterisk поочередно выполняя команды из директории с исходниками (chdir – указывает модулю перейти в каталог перед выполнением команд). Если вы захотите запускать команды с пайпами, символами экранирования переноса строки или другими спец символами, то лучше использовать модуль shell.
В следующей задаче с помощью модуля file и цикла with_items мы устанавливаем права и меняем владельца рабочих каталогов Asterisk.
Последним действием в этом плэйбуке будет запуск обработчика событий “start asterisk service” (который мы поместим в файл handlers/main.yml), если в предыдущем блоке задач были внесены изменения.
Переходим к созданию конфигурационных файлов Asterisk. Для этого создадим третий плэйбук configure_asterisk:
- name: Create asterisk.conf
ini_file:
path: "/etc/asterisk/asterisk.conf"
owner: "{{ ASTERISK_USER }}"
group: "{{ ASTERISK_GROUP }}"
mode: 0640
section: "{{ item.section }}"
option: "{{ item.option }}"
value: "{{ item.value }}"
with_items:
- { section: "files", option: "astctlpermissions", value: "0775" }
- { section: "files", option: "astctlowner", value: "{{ ASTERISK_USER }}" }
- { section: "files", option: "astctlgroup", value: "{{ ASTERISK_GROUP }}" }
- { section: "files", option: "astctl", value: "asterisk.ctl" }
- { section: "options", option: "runuser", value: "{{ ASTERISK_USER }}" }
- { section: "options", option: "rungroup", value: "{{ ASTERISK_GROUP }}" }
- { section: "options", option: "defaultlanguage", value: "ru" }
notify: restart asterisk service
- name: Create modules.conf and rtp.conf from templates
template:
src: "config/{{ item }}.conf"
dest: "/etc/asterisk/{{ item }}.conf"
owner: "{{ ASTERISK_USER }}"
group: "{{ ASTERISK_GROUP }}"
mode: 0640
loop:
- modules
- rtp
notify: restart asterisk service
- name: Copy extensions.conf sip.conf iax.conf
copy:
src: "{{ item }}"
dest: /etc/asterisk/
owner: "{{ ASTERISK_USER }}"
group: "{{ ASTERISK_GROUP }}"
mode: 0640
with_fileglob: "{{ inventory_hostname }}/*"
notify:
- restart asterisk service
В первой задаче мы создаем конфигурационный файл asterisk.conf. Модуль ini_file является частью коллекции community.general. Чтобы проверить, установленна она или нет, запустите:
$ ansible-galaxy collection list
Для установки используйте:
$ ansible-galaxy collection install community.general
Модуль ini_file позволяет добавлять, удалять или изменять отдельные парамеры и разделы в конфигурационных файлах формата INI. Здесь с помощью переменных мы указываем модулю: путь к файлу (path), владельца (owner), группу владельца (group), права на файл (mode), раздел (section), параметр (option), его значение (value) и перечисляем последние три в цикле with_items. В конце перезапускаем Asterisk, если были внесены изменения (restart asterisk service).
В следующей задаче мы создаем конфигурационные файлы modules.conf и rtp.conf, используя шаблоны Jinja2, которые расположены в каталоге templates/config. Здесь все аналогично предыдущей задаче, за исключением используемого цикла - loop, который появился в ansible 2.5 и является более предпочтительным для использования в плэйбуках. В шаблонах можно использовать переменные и факты (facts - собираются при запуске плэйбуков), например мы можем создать шаблон для файла конфигурации, затем развернуть этот файл на нескольких серверах и предоставить нужные нам данные в каждой среде (IP-адрес, имя хоста и т.д.). Шаблон templates/config/rtp.conf содержит в себе две переменные RTP_PORT_START и RTP_PORT_END.
Шаблон templates/config/modules.conf содержит одну переменную SIP_MODULE, с помощью которой мы указываем Asterisk загружать или нет драйвер канала chan_sip.so, в зависимости от того, был ли собран Asterisk с поддержкой pjsip или без нее.
В последней задаче нашей роли мы указываем Ansible скопировать все файлы из директорий files/asterisk1 и files/asterisk2 на сервера asterisk1 и asterisk2 соответственно.
Теперь добавим обработчики событий в файл handlers/main.yml
---
# handlers file for asterisk-role
- name: start asterisk service
systemd:
name: asterisk
state: started
enabled: yes
- name: restart asterisk service
systemd:
name: asterisk
state: restarted
enabled: yes
Здесь с помощью модуля systemd запускается или перезапускается Asterisk и заодно добавляется в автозагрузку.
Последнее, что осталось сделать, это добавить созданные плэйбуки в файл tasks/main.yml
---
# tasks file for asterisk-role
- name: Update system
import_tasks: update_system.yml
- name: Install Asterisk
import_tasks: install_asterisk.yml
- name: Configure Asterisk
import_tasks: configure_asterisk.yml
В итоге у нас получилась следующая файловая структур (лишние каталоги и файлы можно удалить):
Запуск роли
Теперь создаем плэйбук, который будет запускать нашу роль и запускаем его:
---
- name: Deploy Asterisk
hosts: asterisk_group
become: true
roles:
- asterisk-role
Параметр become:true позволяет выполнять задачи с привилегиями пользователя root, в параметре hosts мы указываем на каких серверах запустится установка Asterisk.
На рисунках ниже представлен процесс установки Asterisk.
Проверим содержимое конфигурационных файлов Asterisk:
На этом развертывание Asterisk с помощью Ansible завершено.