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 (должен располагаться строго под блоком):

  1. check_asterisk.rc != 0 - return code в переменной check_asterisk не равен нулю, если он равен нулю, значит команда была выполнена без ошибок.

  2. 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:

sip.conf, iax.conf
sip.conf, iax.conf
asterisk.conf
asterisk.conf
modules.cortp.conf
modules.cortp.conf

На этом развертывание Asterisk с помощью Ansible завершено.

Комментарии (8)


  1. click0
    30.10.2024 17:57

    Так и где можно вашу роль полностью просмотреть?


  1. RaveNoX
    30.10.2024 17:57

    То есть вместо создания нормального пакета под ОС и его последующей установки средствами ansible вы предлагаете компилиррвать Asterisk на каждой машине?

    А как вы его потом обновлять или удалять планируете?


    1. click0
      30.10.2024 17:57

      Зачем вот эти ваши обновления? Работает и не трожь! (с)


    1. slonopotamus
      30.10.2024 17:57

      Мало того, нормальный пакет уже есть.


  1. denistu10
    30.10.2024 17:57

    Прошу прощения, но смысл этой статьи не совсем ясен.  

    Если цель — показать способ установки и настройки Asterisk, материал неполный: отсутствуют многие важные моменты, такие как настройка SIP и базовая безопасность, etc. Если же статья посвящена практике работы с Ansible, то опять же мимо - здесь много плохих практик.

    Начиная от bashansible, который можно и нужно заменить встроенными модулями, также роль слишком размыта: она и систему обновляет (зачем это здесь?), и Asterisk устанавливает, разве что в магазин за пивом не сходит.


  1. c46fd3da
    30.10.2024 17:57

    Asterisk замечательно в докере живет, достаточно один раз потратить пол дня чтобы под себя образ собрать и можно пользоваться годами. Бонусом получаем переносимость АТС через rsync за 3 минуты.


    1. trabl
      30.10.2024 17:57

      Всё верно, в докере живёт нормально, с параметром network_mode: host для контейнера с asterisk. Ну или как вариант небольшой пул rtp портов указывать, что не всем подойдёт в продакшен среде.


      1. c46fd3da
        30.10.2024 17:57

        Зачем сразу host, macvlan очень даже дельная штука если уметь ей пользоваться.