通过Docker可以极大降低应用多次部署的工作量,特别是用C,C++开发的应用,搭建环境往往是个非常繁复的过程,经常出现依赖的包不全,版本不对导致安装失败。本文以在阿里云ECS上,利用Docker实际部署成功一套完整的Janus-Gateway应用为例,展示了一些编写Dockerfile的实用技巧,真正实现一次编写多次部署。
项目地址:https://github.com/jasony62/tms-janus-streaming
阿里云主机:CentOS 7.7 64位,设置了公网ip和域名,安装了docker。
项目简介
Janue-Gateway是一个用C语言开发的开源WebRTC服务器,通过它可以实现实时流媒体服务。
如果Janue-Gateway部署在公网上(例如:阿里云),客户端之间要实现通信,必须(WebRTC是一种点对点通信)解决Nat穿越问题。因此,需要使用TURN服务,有一些可以免费使用TURN服务,本项目用开源项目cotrun自行搭建了TURN服务。
浏览器使用WebRTC时必须用https访问(localhost可以不用),因此,需要支持在运行环境上安装ssl证书,并且开启ssl端口。(注:可以通过配置项chrome://flags/#unsafely-treat-insecure-origin-as-secure
关闭浏览器的限制)
Janus-Gateway自带了用于演示功能的前端代码,但是后续项目中Janus只是作为后端服务,前端的代码要单独开发。本项目中将Janus代码复制出来,单独放在nginx中运行。
本项目中,实现了Janus-Gateway,coturn和nginx三个模块的容器化,实现了在运行环境上的一键部署和运行。
配置SSL
基于安全隐私问题,现在Webkit内核的浏览器共享视频、语音、经纬度坐标等必须通过HTTPS形式访问。项目中使用Let's Encrypt
申请了ssl证书。
参考:
https://letsencrypt.org
https://certbot.eff.org
https://www.jianshu.com/p/eab4e21c21f5
coturn
本想直接用coturn项目中docker目录中的例子,但是没有成功,在网上发现已经有人做好了例子,就直接拿来试了一下,可用!
其实,coturn到底怎么用目前完全没有搞明白,只是按照例子跑起来了。下面的文章中提到了生成用户名和密码进行测试,经过实践,测试的时候需要,但是在阿里云中运行时并不需要生成的用户名和密码(完全不知道有什么用)。
参考:
https://meetrix.io/blog/webrtc/turnserver/long_term_cred.html
https://meetrix.io/blog/webrtc/coturn/installation.html
https://github.com/coturn/coturn
Janus-Gateway
Janus的安装就是按照官网文档中的步骤进行安装,但是中间还是碰到了一堆坑,特别是和docker的结合,下面挑出Dockerfile中的部分内容进行说明。
# 更新curl>7.62
RUN rpm -Uvh http://www.city-fan.org/ftp/contrib/yum-repo/rhel6/x86_64/city-fan.org-release-2-1.rhel6.noarch.rpm && \
yum --showduplicates list curl --disablerepo="*" --enablerepo="city*" && \
sed -i '5s/enabled=0/enabled=1/' /etc/yum.repos.d/city-fan.org.repo && \
yum -y install curl
在安装各种包的过程中,经常会碰到需要修改配置文件,例如安装curl包时要修改配置文件/etc/yum.repos.d/city-fan.org.repo
,将第5行的内容从enabled=0
修改为enabled=1
。如果是手动安装,通常用vi打开修改,但是docker中不能进行交互,怎么办?解决的办法是用sed
命令直接修改。
sed -i '5s/enabled=0/enabled=1/' /etc/yum.repos.d/city-fan.org.repo
# 找到nice包
ENV PKG_CONFIG_PATH /usr/lib/pkgconfig
有时候,执行脚本需要设置环境变量,如果是手工操作会写成export ENV_XXX=YYY
或者写到.bashrc
这类文件中,但是在Dockerfile中这些写法都是无效的(亲测的结论),必须用ENV
指令进行设置。
ADD start_janus.sh start_janus.sh
RUN chmod +x start_janus.sh
CMD ["./start_janus.sh"]
Dockerfile中无法根据不同条件执行不同的内容,但是我们经常需要根据不同的环境变量,执行不同的命令。这种情况,可以将根据条件执行不同分支的需求放到外部的shell脚本中执行。例如start_janus.sh
就是根据,是否指定了ssl证书的环境变量,决定是否修改janus的配置文件开启ssl端口。
#!/bin/bash
echo "启动 Janus server"
if [ "$ssl_certificate" != "" -a "$ssl_certificate_key" != "" ]
then
echo "启用 Janus ssl 端口"
sed -i "s/https = false/https = true/g" /opt/janus/etc/janus/janus.transport.http.jcfg
sed -i "s/#secure_port = 8089/secure_port = 8089/g" /opt/janus/etc/janus/janus.transport.http.jcfg
sed -i "s?#cert_pem = \"\/path\/to\/cert.pem\"?cert_pem = \"$ssl_certificate\"?g" /opt/janus/etc/janus/janus.transport.http.jcfg
sed -i "s?#cert_key = \"\/path\/to\/key.pem\"?cert_key = \"$ssl_certificate_key\"?g" /opt/janus/etc/janus/janus.transport.http.jcfg
fi
if [ "$stun_server" != "" ]
then
p_stun_server="--stun-server=$stun_server"
fi
/opt/janus/bin/janus $p_stun_server
另外,这里需要注意如果shell中用到环境变量,这些环境变量必须用ENV
指令定义,否则无效(后面会提到在docker-compose文件中用env_file设置环境变量)。
nginx
nginx的Dockerfile主要是解决设置nginx.conf
的问题。通常采用的方法是在运行docker时指定一个写好的nginx.conf
替换掉容器内的文件,但是如果nginx.conf
不放在版本库中,会导致每次部署都要手工生成一个nginx.conf
,又麻烦又容易出错。在本项目中采用了编写好模版文件(nginx.conf.template),根据指定的环境变量自动完成修改。
ADD nginx.conf.template /etc/nginx/nginx.conf.template
先将模版文件添加到容器中,在start_nginx.sh
中进行修改。
模版文件中有如下第一段代码
#ssl_server server {
#ssl_server listen 443 ssl;
#ssl_server ssl_certificate $ssl_certificate;
#ssl_server ssl_certificate_key $ssl_certificate_key;
#ssl_server ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#ssl_server ssl_ciphers HIGH:!aNULL:!MD5;
#ssl_server location / {
#ssl_server root /usr/share/nginx/html;
#ssl_server index index.html index.htm;
#ssl_server }
#ssl_server }
因为是否开启ssl访问是一段代码,默认不开启,所以就先把这一段代码都注释掉,如果需要开启就把注释去掉。start_nginx.sh
中对应的代码如下:
if [ "$ssl_certificate" != "" -a "$ssl_certificate_key" != "" ]
then
echo "启用 Nginx ssl 端口"
sed -i "s/#ssl_server//" /etc/nginx/nginx.conf.template
fi
配置文件中有多个位置要用环境变量替换,采用逐个替换的方式太麻烦,是否有更简便的方式?
#ssl_server ssl_certificate $ssl_certificate;
#ssl_server ssl_certificate_key $ssl_certificate_key;
配置文件模版中ssl证书的位置需要用环境变量替换。
envsubst '$ssl_certificate $ssl_certificate_key' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && exec nginx -g 'daemon off;'
在shell脚本中用envsubst
可以实现用环境变量自动输入文件中的内容后输出。
In normal operation mode, standard input is copied to standard output, with references to environment variables of the form
$VARIABLE
or${VARIABLE}
being replaced with the corresponding values. If a shell-format is given, only those environment variables that are referenced in shell-format are substituted; otherwise all environment variables references occurring in standard input are substituted.
These substitutions are a subset of the substitutions that a shell performs on unquoted and double-quoted strings. Other kinds of substitutions done by a shell, such as (command-list) or
command-list
, are not performed by the envsubst program, due to security reasons.
When
--variables
is used, standard input is ignored, and the output consists of the environment variables that are referenced in shell-format, one per line.
上面是envsubst
的说明,不做翻译了,关键是要注意两点:1、$VARIABLE
or ${VARIABLE}
这两种写法都代表要替换的环境变量;2、shell-format
可以指定哪些是要进行替换的变量。为什么要注意这两条?看配置文件模版。
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
设置日志输出格式的部分中包含了$
开头的变量,它们不是环境变量,但是envsubst
根据第1点把它们当作变量,因为没有定义就被替换成空了。解决这个问题的方法是用第2点,设置要替换的环境变量,本例中:envsubst '$ssl_certificate $ssl_certificate_key'
。
参考
http://nginx.org/en/docs/http/configuring_https_servers.html
https://superuser.com/questions/1148950/what-is-shell-format-in-envsubst
docker-compose
因为janus和coturn都需要动态开放端口,进行端口映射比较麻烦,所以在docker-compose.yml
中将它们的network_mode
设置为host
,直接使用宿主机网络,这样不需要端口映射了。需要注意的是host
方式只在linux下有效,Window和Mac都不支持。
前面多次提到了设置环境变量,那么到底如何指定?本项目中使用env_file
指定环境变量文件。
env_file:
- ./sample.env
前面也提到了开启ssl端口的问题,ssl证书是janus和coturn公用的,放在了宿主机中,需要在容器运行时进行绑定。但是因为ssl设置是可选项,所以不能在docker-compose写死目录映射关系。
上面的3个问题都涉及到在不同的运行环境中需要进行不同的docker-compose设置,因为我们引入docker-compose.override.yml
文件。下面的文件是在我的Mac环境上的文件。
version: "3.7"
services:
janus:
network_mode: "bridge"
ports:
- "8088:8088"
# volumes:
# - /etc/letsencrypt:/etc/letsencrypt
env_file:
- ./local.env
ue_client:
# volumes:
# - /etc/letsencrypt:/etc/letsencrypt
env_file:
- ./local.env
在override文件中我们重新指定了网络模式bridge
和要映射的端口;因为在本地运行时不开启ssl,所以这里没有进行ssl证书目录的挂载;将环境变量定义文件指定为本地的local.env
文件。
下面的是在阿里云上的override文件。
version: "3.7"
services:
janus:
volumes:
- /etc/letsencrypt:/etc/letsencrypt
env_file:
- ./local.env
ue_client:
volumes:
- /etc/letsencrypt:/etc/letsencrypt
env_file:
- ./local.env
没有修改网络模式;指定了ssl证书位置;将环境变量定义文件指定为本地的local.env
文件。
local.env
文件内容如下:
# ssl证书位置
ssl_certificate=/etc/letsencrypt/live/云主机的域名/fullchain.pem
ssl_certificate_key=/etc/letsencrypt/live/云主机的域名/privkey.pem
# janus stun-server
stun_server=云主机的公网ip:3478
总结
利用docker确实可以实现一次编写多次使用,但还是有非常多的具体问题需要不断进行实践和积累。我认为docker的核心是让应用与运行环境解耦,但是对于运维工作来说运维工作自动化是核心,docker只是支撑手段之一。
参考
https://github.com/atyenoria/janus-webrtc-gateway-docker/blob/master/Dockerfile
https://github.com/voxbone-workshop/janus_gateway
http://webrtc.ventures/2017/10/janus-webrtc-gateway-as-a-sip-gateway-how-to-monitor-it/