Deploying Asterisk with Ansible

Ansible is a configuration management system written in the Python programming language. Used to automate software configuration and deployment. Typically used to manage Linux nodes, but there is a large collection of plugins for connecting to other devices and OSes. Along with Chef, Puppet and SaltStack, it is considered one of the most popular configuration management systems for Linux. The main difference between Ansible and its analogues is that there is no need to install an agent/client on target systems. With Ansible you can deploy, remove or configure any software on remote servers.

This article will cover the process of creating a role for installing and configuring Asterisk.

Configuring Ansible Settings to Connect to Servers

First, let's create a configuration file ansible.cfg. Below is a list of directories where it can be located, from highest to least priority:

  • Directory defined by environment variable $ANSIBLE_CONFIG

  • Current catalog

  • User home directory

  • Global/shared file in /etc/ansible directory

You can check the location of the file with the command:

$ ansible --version

I placed it in the ansible directory in the user's home directory.

File Contents:

[defaults]
inventory = /home/kstrakhov/ansible/hosts
host_key_checking = false
  • Using the inventory parameter, we specify a file with a list of hosts and groups on which our tasks can be executed.

  • The host_key_checking parameter allows you to disable checking the server key when connecting via ssh.

Now let's create an inventory file that lists the target servers:

[asterisk_group]
asterisk1    ansible_host=192.168.30.12
asterisk2    ansible_host=192.168.30.13

In square brackets we indicate the name of the group; later, when launching the playbook, we can refer to this name. These names can only contain letters, numbers and underscores. By default, all servers are included in the all group, those that do not belong to any group are included in the ungrouped group. Groups can be combined into groups:

[asterisk_all_group:children]
asterisk_group1
asterisk_group2

In the list of hosts we enter an alias and the ansible_host variable, which specifies the host's IP address, you can specify the FQDN, or you can specify the FQDN without an alias. You can also set other connection parameters using variables, for example:

  • ssh port – ansible_port

  • Username – ansible_user

  • Path to ssh key – ansible_ssh_private_key_file

  • User password

For further convenience, when using the hosts file, some parameters can be placed in a separate file. To do this, create a group_vars directory in the directory with the hosts file and inside create a file with the name of the group. We add variables to this file, using a colon instead of equals:

ansible_user : root
ansible_ssh_private_key_file : /home/kstrakhov/.ssh/id_rsa

Ansible will now use these variables to connect to servers that are in the asterisk_group.

You can see which variables are applied to hosts with the command:

$ ansible-inventory --graph --vars

Creating a role

Now that the connection is configured, let's start creating the role:

$ ansible-galaxy init asterisk-role

The asterisk-role directory has appeared in the current directory.

By default, Ansible will look in the role directories for the main.yml file and, depending on the purpose of the file, which is determined by its location, perform certain actions.

  • defaults/main.yml – default variables for the role. Can be overridden in the inventory (hosts) file or using the –e (–extra-vars key=value) option when running the playbook.

  • files – files that can be copied when asterisk is deployed.

  • handlers/main.yml – file of handlers, used in a role to perform actions under certain conditions or certain events.

  • meta/main.yml – role metadata.

  • tasks/main.yml – the main list of tasks that the role performs.

  • templates/ – templates that the role deploys have a j2 (jinja2) extension and are used, for example, to generate configuration files. Their advantage is that they can contain variables and/or facts.

  • tests/ – this directory contains a test inventory file and a test playbook (test.yml), used for tests.

  • vars/main.yml – Variables are stored here that are not overridden by the inventory file, but can be overridden using the -e (–extra-vars) option when running the playbook.

First, let's add variables to the vars/main.yml file

---
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

Next, let's create the first playbook update_system.yml in the tasks directory (which will update the system):

- 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

In the first two tasks we add the epel repository and update the system. You can use the state parameter to tell the yum module what to do with packages:

  • present and installed – ensures that the required package is installed.

  • latest – will update the specified package if it is not the latest available version.

  • absent and removed – will remove the specified package.

Using the register parameter, we place the result of the command in the allupdate variable. In the third task, using the when operator, we check the value of the changed section in the allupdate variable, and if there have been changes, we reboot the server.

The next playbook will contain tasks that will install Asterisk from source and configure the system to run it:

- 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

In the first task, using the shell module, we check whether Asterisk is installed on the server and tell it not to display change messages (changed_when: false) and to ignore error messages (ignore_errors: true) so that the playbook continues to execute. Using the register parameter, we save the result into the check_asterisk variable.

Next, in the Create Asterisk User Group and Create Asterisk User tasks, we create a group and a user from which Asterisk will be launched, and deny him login.

Next, we proceed to install Asterisk. The “Install Asterisk Process” task contains a block that will be launched if the conditions specified by the when statement are met (must be located strictly below the block):

  1. check_asterisk.rc != 0 – return code in the check_asterisk variable is not equal to zero, if it is equal to zero, then the command was executed without errors.

  2. check_asterisk.stdout != 'Asterisk' – stdout does not contain “Asterisk”; if it does, it means it is already installed on the server.

The first task in the block is to switch SELinux to permissive mode.

Next, we download and unpack Asterisk sources using the unarchive module. The src parameter in conjunction with remote_src, if src contains ://, tells the remote server to download the file from the URL, and then unpack it and return a list of unpacked files (list_files: yes). The result of this command is placed in the asterisk_archive_contents variable.

In the next task, we run the debug module, which will display the full path to the Asterisk sources. By using [0] we tell the module to use the first element in the list (array) of the files field ([1] – second, [2] – third, etc.).

In the “Install Asterisk Packages” task, using the command module, we start the Asterisk installation process by executing commands from the source directory one by one (chdir – tells the module to go to the directory before executing the commands). If you want to run commands with pipes, line breaks, or other special characters, it is better to use the shell module.

In the next task, using the file module and the with_items loop, we set the rights and change the owner of the Asterisk working directories.

The last action in this playbook will be to launch the “start asterisk service” event handler (which we will place in the handlers/main.yml file), if changes were made in the previous block of tasks.

Let's move on to creating Asterisk configuration files. To do this, let's create a third playbook 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

In the first task we create the asterisk.conf configuration file. The ini_file module is part of the community.general collection. To check whether it is installed or not, run:

$ ansible-galaxy collection list 

To install use:

$ ansible-galaxy collection install community.general 

The ini_file module allows you to add, delete or change individual parameters and sections in INI format configuration files. Here, using variables, we indicate to the module: the path to the file (path), owner (owner), owner group (group), rights to the file (mode), section (section), parameter (option), its value (value) and list the last three in the with_items loop. At the end, we restart Asterisk if changes have been made (restart asterisk service).

In the next task, we create the modules.conf and rtp.conf configuration files using Jinja2 templates, which are located in the templates/config directory. Everything here is similar to the previous task, with the exception of the loop used – loop, which appeared in ansible 2.5 and is more preferable for use in playbooks. Templates can use variables and facts (facts – collected when playbooks are launched), for example we can create a template for a configuration file, then deploy this file on several servers and provide the data we need in each environment (IP address, hostname, etc.). d.). The templates/config/rtp.conf template contains two variables RTP_PORT_START and RTP_PORT_END.

The templates/config/modules.conf template contains one variable, SIP_MODULE, with which we tell Asterisk to load the chan_sip.so channel driver or not, depending on whether Asterisk was built with or without pjsip support.

In the last task of our role, we tell Ansible to copy all the files from the files/asterisk1 and files/asterisk2 directories to the asterisk1 and asterisk2 servers, respectively.

Now let's add event handlers to the file 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

Here, using the systemd module, Asterisk is started or restarted and at the same time added to startup.

The last thing left to do is to add the created playbooks to the tasks/main.yml file

---
# 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

As a result, we got the following file structure (extra directories and files can be deleted):

Running a role

Now we create a playbook that will launch our role and launch it:

---
- name: Deploy Asterisk
  hosts: asterisk_group
  become: true

  roles:
    - asterisk-role

The become:true parameter allows you to perform tasks with the privileges of the root user; in the hosts parameter we indicate on which servers the Asterisk installation will run.

The pictures below show the Asterisk installation process.

Let's check the contents of the Asterisk configuration files:

sip.conf, iax.conf

sip.conf, iax.conf

asterisk.conf

asterisk.conf

modules.cortp.conf

modules.cortp.conf

This completes the deployment of Asterisk using Ansible.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *