将有繁琐配置的开发环境打包进容器中能减少我们的开发负担,本文将介绍如何把基于 Asterisk 的 VoIP 服务容器化。
Why
如果你的 VoIP 服务开发环境、测试环境和生产环境在同一台物理机上,也许不会察觉到环境搭建的繁琐以及消耗在配置环境上的时间。让我们来设想这样一个场景:客户指定使用某一个 Linux 发行版安装 Asterisk(或者配合 FreePBX)开发一套存在特定需求的 VoIP 服务,基于这个前提你拿到一台新的设备开始工作:
- 首先安装配置某个指定 Linux 发行版(花费1、2个小时);
- 接着编译、安装、配置 Asterisk(1个小时);
- 然后安装配置 FreePBX(花费2小时解决无数个问题);
- 最后完成测试(花费1小时)。
终于在5、6个小时后你搭建好了开发环境,接着发现还有测试环境和生产环境需要搭建,于是你再花上2倍于之前的时间完成了环境搭建工作。总算可以开始开发了,你开始实现某一项功能,为了完成这项功能可能:
- 需要变动 Asterisk 的配置;
- 需要变动 FreePBX 的接口;
- 系统某些环境变量、配置参数需要改动。
于是你将这些变动都同步执行到测试和生产环境以保证各平台的环境一致,这将耗费开发人员大量的时间。同时,一个项目中面临几十上百项功能的开发需求,很快你就会发现因为环境配置失步导致的开发流程失控,比如开发环境能实现的功能在测试环境失效,在生产环境出现的问题在开发环境无法复现。
问题总结:
- 开发人员在搭建环境和保证各平台环境配置同步的过程中耗费大量时间;
- 手动操作进行配置同步难免会出错,出现运行环境失控的情况。
为了解决这2方面问题,我们可以将服务程序及其所依赖的环境打包进容器中使得整套环境容器化,然后在各个平台分发此容器以保证环境的一致性。
How
本次测试的环境是 Debian 10
,以下分4个步骤完成 VoIP 服务容器化:
- 安装并配置 LXD;
- 制作根文件系统,其中安装了 Asterisk 以及 FreePBX;
- 将此根文件系统打包作为基础镜像导入 LXD;
- 从 LXD 中的基础镜像启动一个实例,并配置网络。
安装并配置 LXD
LXD 是一下代开源系统容器、虚拟机管理器。关于 LXD 更为详细的介绍请参考官方文档。
使用 snap
安装 LXD,如果系统没有 snap 先安装 snap:
1 | sudo apt install snap |
安装 LXD
:
1 | sudo snap install lxd |
将用户添加到 lxd 组:
1 | sudo usermod -a -G lxd $YOUR_USERNAME |
配置 LXD
一般情况下直接在命令行中输入 lxd init
然后全部选择都使用默认选项即可。这里我希望使用自定义的存储设备作为 lxd 的 storage pool
。
首先创建一个大小合适的空文件:
1 | mkdir $HOME/lxd_storage |
创建新的 loop device
并将刚刚创建的空文件关联到此设备:
1 | losetup /dev/loop14 $HOME/lxd_storage/disk # 先查看 /dev 目录下是否已有 loop14,有则换一个如:loop15 |
初始化 lxd 配置:
1 | uklar@debian:~# lxd init |
制作根文件系统
这里使用 debootstrap
获取 debian 的根文件系统:
1 | sudo apt install debootstrap |
使用 systemd-nspawn
以 chroot
模式切换到 /tmp/debian
中:
1 | sudo apt install systemd-container # 此软件包中包含了 systemd-nspawn |
做一些基本配置,如设置 root 用户密码、添加普通用户以及配置网络等,然后退出:
1 | passwd |
接着使用 systemd-nspawn
的容器模式启动 /tmp/debian
以 root 身份登陆:
1 | sudo systemd-nspawn -D /tmp/debian --boot |
在容器中安装 Asterisk 以及 FreePBX,过程请参考:
配置 hostname:
1 | apt install dbus |
访问服务器 Web 页面验证容器中的 VoIP 服务是否正常运行:
从 systemd-nspawn
容器中退出:
1 | shutdown now |
创建 LXD 镜像
将刚刚配置完成的根文件系统打包并压缩:
1 | mkdir -p ~/container/images/voip_base |
为镜像创建 metadata 文件:
1 | cat <<EOF >./metadata.yaml |
将压缩包作为镜像导入 LXD:
1 | lxc image import \ |
查看镜像是否成功导入:
1 | lxc image list |
从镜像启动实例
有了基础镜像之后,启动一个新的 VoIP 服务实例只需一行命令:
1 | lxc launch voip-base test |
查看实例运行状态:
1 | lxc list |
可以看到 VoIP 服务实例(名字为 test)正在运行,分配的内部 IP 地址是 10.72.18.48
。
可用以下命令登陆到实例中:
1 | lxc exec test bash |
查看各类服务使用的端口:
1 | lsof -i -P -n |
宿主机网络配置
让宿主机(IP 为 192.168.0.107
)所在网络(192.168.0.0/24
)的其他设备能够访问 VoIP 服务(IP 为 10.72.18.48
),需要在宿主机上配置网络地址转换(NAT):
- 发送到宿主机
5060
端口的 UDP 包(PJSIP)转发到容器实例 - 发送到宿主机
5160
端口的 UDP 包(SIP)转发到容器实例 - 发送到宿主机
10000 - 20000
端口的 UDP 包(语音)转发到容器实例 - 发送到宿主机
80
端口的 TCP 包(Web 管理服务)转发到容器实例
以上配置可根据实际需求更改。
这里使用 iptables
来完成,使用 exit
从实例退出返回宿主机,执行:
1 | sudo iptables -t nat -A PREROUTING --dst 192.168.0.107 -p udp --dport 5060 -j DNAT --to 10.72.18.48 |
查看 NAT 配置状态:
1 | sudo iptables -t nat -v -L PREROUTING -n |
现在即可通过宿主机的 IP 访问宿主机上容器化的 VoIP 服务了。
Asterisk 网络配置
配置 Asterisk 的 SIP 参数:
externip
为192.168.0.107
localnet
为10.72.18.0/24
这样 VoIP 服务才能向 SIP 终端正确地发送 contact
参数,否则终端与服务器的 SIP 协议交互会出错。
直接使用 FreePBX 完成配置(Settings -> Asterisk SIP Settings):
总结
本文详细介绍了如何将 VoIP 服务容器化,涉及的内容包括 LXD 的使用、systemd-nspawn 的使用、根文件系统制作、容器镜像制作以及和 VoIP 服务相关的网络配置。通过容器化的操作能让各平台的运行环境保持一致,减少开发人员不必要的时间损耗。至于选择 LXD 作为实现容器化的平台原因有2:
- VoIP 服务软件组成复杂,部署在一个带根文件系统的容器中更为方便;
- LXD 的实现性能较好,关于业界几种容器化工具的性能对比分析请参考论文 Performance analysis of multi services on container Docker, LXC, and LXD