paulgorman.org/technical

Asterisk in a Debian LXC container

(January 2017)

We want to safely build and deploy new versions of Asterisk, and easily roll back changes. So, we package Asterisk in an LXC container. There are three stages:

  1. Build a new Asterisk container
  2. Inject the existing production config and data into the new Asterisk container
  3. Deploy the new container to production, and swap out the old container

To complicate matters, we pile FreePBX on top of Asterisk, to accommodate our more point-and-click-oriented staff.

Assumptions

We have a build/testing host with LXC installed and a NAT bridge. The external interface of this build host has the IP address 10.0.0.76. The NAT bridge device is ‘lxcbr0’ with the network 10.100.0.0/24. The host iptables FORWARD chain accepts traffic to and from lxcbr0, and the nat table has a POSTROUTING MASQUERADE rule for 10.100.0.0/24 (both likely set up by lxc-nat). See https://paulgorman.org/technical/linux-lxc.txt for more about LXC.

We have a production host set up for LXC like our build/testing host. The production host has an exteral IP of 203.0.113.100 (a placeholder for use in these notes).

We want our new container named “asterisk-debian-$(date +%Y%m%d)” to receive IP 10.100.0.10. Our host NATs for the container, like:

# iptables -t nat -A PREROUTING --dst 10.0.0.76 -p udp --dport 5060 -j DNAT --to 10.100.0.10
# iptables -t nat -A PREROUTING --dst 10.0.0.76 -p tcp --dport 5061 -j DNAT --to 10.100.0.10
# iptables -t nat -A PREROUTING --dst 10.0.0.76 -p udp --dport 10000:20000 -j DNAT --to 10.100.0.10

We may also want to NAT ports to the container’s sshd or httpd.

Building a container with Asterisk

On the build host:

# lxc-create -n asterisk-debian-$(date +%Y%m%d) -t debian -- -r jessie
# sed -i '/10.100.0.10/d' /etc/lxc/dnsmasq.conf
# echo "dhcp-host=asterisk-debian-$(date +%Y%m%d),10.100.0.10" >> /etc/lxc/dnsmasq.conf
# systemctl restart lxc-net
# lxc-start -n asterisk-debian-$(date +%Y%m%d)
# lxc-attach -n asterisk-debian-$(date +%Y%m%d)

Enter the container:

# du -hs
253M    .

# apt-get update
# apt-get dist-upgrade
# apt-get install wget build-essential vim-tiny less inetutils-ping tcpdump netcat-openbsd lsof libncurses5-dev uuid-dev libjansson-dev libxml2-dev libssl-dev libsqlite3-dev sqlite3 cron logrotate unixodbc-dev libltdl-dev
# cd /usr/local/src
# wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-13-current.tar.gz
# tar xf asterisk-13-current.tar.gz
# cd asterisk-13*
# ./configure --with-pjproject-bundled
# make menuselect

Add CORE-SOUNDS-EN-ULAW, CORE-SOUNDS-EN-G722, MOH-OPSOUND-ULAW, MOH-OPSOUND-G722, EXTRA-SOUNDS-EN-ULAW, and EXTRA-SOUNDS-EN-G722.

# make
# make install
# adduser asterisk --uid 3333 --disabled-password
# mkdir -p /etc/asterisk /usr/lib/asterisk /var/{lib,log,run,spool}/asterisk
# chown -R root:asterisk /etc/asterisk /usr/lib/asterisk /var/{lib,log,run,spool}/asterisk
# chmod -R 775 /etc/asterisk /usr/lib/asterisk /var/{lib,log,run,spool}/asterisk

# cd / ; du -hs
1.4G    .
# cd - ; make clean ; cd - ; du -hs
1011M   .
# apt-get clean; du -hs
835M    .

# cat << 'EOF' > /etc/systemd/system/asterisk.service

[Unit] Description=Asterisk PBX and telephony daemon Documentation=man:asterisk(8) Wants=network.target After=network.target

[Service] Type=simple User=asterisk Group=asterisk PermissionsStartOnly=true ExecStartPre=-/bin/mkdir -p /var/run/asterisk ExecStartPre=-/bin/chown -R root:asterisk /var/run/asterisk ExecStartPre=-/bin/chmod -R 775 /var/run/asterisk ExecStart=/usr/sbin/asterisk -g -f -C /etc/asterisk/asterisk.conf ExecStop=/usr/sbin/asterisk -rx ‘core stop now’ ExecReload=/usr/sbin/asterisk -rx ‘core reload’

Restart=always RestartSec=5

[Install] WantedBy=multi-user.target EOF # systemctl daemon-reload

And:

# systemctl enable asterisk
# systemctl start asterisk
# lsof -Pi

Stacking FreePBX on top

In our container:

# apt-get install apache2 mysql-server mysql-client php5 php5-mysql php-pear libmyodbc sudo php5-curl php5-gd sox
# sed -i 's/\(^upload_max_filesize = \).*/\120M/' /etc/php5/apache2/php.ini
# sed -i 's/^\(User\|Group\).*/\1 asterisk/' /etc/apache2/apache2.conf
# sed -i 's/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf

# ln -s /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled/rewrite.load
# ln -s /etc/apache2/mods-available/ssl.conf /etc/apache2/mods-enabled/ssl.conf
# ln -s /etc/apache2/mods-available/ssl.load /etc/apache2/mods-enabled/ssl.load
# ln -s /etc/apache2/mods-available/socache_shmcb.load /etc/apache2/mods-enabled/socache_shmcb.load
# rm /etc/apache2/sites-enabled/000-default.conf
# cat << 'EOF' > /etc/apache2/sites-enabled/freepbx.conf

ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined ServerName gab.example.com SSLEngine ON SSLVerifyClient optional SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key EOF # systemctl restart apache2 # rm /var/www/html/index.html # cat >> /etc/odbcinst.ini << EOF [MySQL] Description = ODBC for MySQL Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc.so Setup = /usr/lib/x86_64-linux-gnu/odbc/libodbcmyS.so FileUsage = 1 EOF # cat >> /etc/odbc.ini << EOF [MySQL-asteriskcdrdb] Description=MySQL connection to ‘asteriskcdrdb’ database driver=MySQL server=localhost database=asteriskcdrdb Port=3306 Socket=/var/run/mysqld/mysqld.sock option=3 EOF # pear install Console_Getopt # cd /usr/local/src # wget http://mirror.freepbx.org/modules/packages/freepbx/freepbx-13.0-latest.tgz # tar xf freepbx-13.0-latest.tgz # cd freepbx # ./install -n

Restore our production setting into our new container, assuming we’ve copied a backup from production (/var/spool/asterisk/backup/) into the container:

# fwconsole moduleadmin upgradeall
# fwconsole moduleadmin downloadinstall backup
# /var/www/html/admin/modules/backup/bin/restore.php --restore=/home/asterisk/20170127-101341-1485530021-13.0.190.11-1587130262.tgz --items=all

Since the host NAT’s for the container, we may want to change the external IP sent out by Asterisk depending on where we deploy:

# for f in /etc/asterisk/*;    do sed -i 's/203.0.113.100/10.0.0.76/g' $f;    done
# sed -i 's/localnet.*$/localnet=10.100.0.0\/24/g' /etc/asterisk/sip_general_additional.conf
# mysql -u root asterisk -e "UPDATE kvstore SET kvstore.val='10.0.0.76' WHERE kvstore.key='externip';" -p
# mysql -u root asterisk -e "UPDATE kvstore SET kvstore.val='[{\"net\":\"10.100.0.0\",\"mask\":\"24\"}]' WHERE kvstore.key='localnets';" -p

If any modules are listed as “broken”, reinstall them, like:

# fwconsole moduleadmin list
# fwconsole moduleadmin uninstall asterisk-cli callforward cidlookup configedit ivr miscapps miscdests paging parking phonebook ringgroups speeddial
# fwconsole moduleadmin downloadinstall asterisk-cli callforward cidlookup configedit ivr miscapps miscdests paging parking phonebook ringgroups speeddial

Set up log rotation in the container.

# cat << 'EOF' >> /etc/logrotate.conf
/var/log/asterisk/full /var/log/asterisk/*log {
	su asterisk asterisk
	missingok
	minsize 250k
	maxsize 20M
}
EOF

If the vi in the container misbehaves, add a minimal .vimrc:

root@asterisk:~# cat .vimrc
set nocompatible
set backspace=indent,eol,start