SRS: Cloud Native改进知多少
Posted SRS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SRS: Cloud Native改进知多少相关的知识,希望对你有一定的参考价值。
前段时间推送了,现在SRS3已经提供了完整的K8S+Docker支持,SRS正式走进Could Native时代,这意味着更便捷的部署、更高的弹性、更快的扩容和缩容、无中断服务的发布和灰度能力。
这篇文章,让我们一起看看SRS,以及一个应用,要达到弹性能力需要做出多少关键的改变吧(更详细的说明可以点阅读原文链接哦)。
Daemon
Daemon就是后台启动服务的意思,一般使用安装包和二进制部署时,都要求程序实现daemon启动的功能,这样可以防止退出terminal时进程也退出。比如nginx和SRS都实现了这个功能,是在配置文件中指定为daemon启动:
# whether start as daemon
# @remark: do not support reload.
# default: on
daemon on;
这个功能是通过两次fork,这样grandpa(当前进程)创建father,father创建son进程,然后让father和grandpa退出,son就成为孤儿进程被init(1)接管,成为了后台进程:
int pid = fork();
// grandpa
if(pid > 0) {
int status = 0; waitpid(pid, &status, 0);
srs_trace("grandpa process exit.");
exit(0);
}
// father
pid = fork();
if(pid > 0) {
srs_trace("father process exit");
exit(0);
}
// son
srs_trace("son(daemon) process running.");
这个功能在linux服务器上一直work很好,但是在K8S中却总是出现问题,总有朋友反馈说docker启动SRS会失败(参考#1594)。原因就是docker启动时是不能daemon启动,它就是一个进程,它接管了进程的生命周期;所以用docker启动SRS时,需要将daemon改成off,否则docker认为容器退出了,K8S就会不断的拉起SRS的pod。
有没有更简单的办法呢?比如SRS发现是docker就自动设置为daemon,SRS就这么实现了。不过做得更好,达到了同样的效果,同时也可以关闭这个功能,避免在linux server直接启动时出现问题:
# whether start as daemon
# @remark: do not support reload.
# default: on
daemon on;
# Whether disable daemon for docker.
# If on, it will set daemon to off in docker, even daemon is on.
# default: on
disable_daemon_for_docker on;
这两个配置的意图如下:
如果daemon设置为on,则准备进入daemon后台服务模式。
如果disable_daemon_for_docker设置为on,当检测到SRS在docker中运行,则自动将daemon设置为off。
可以看出,用户可以完全不用配置这两个参数,默认在docker和linux server上都可以运行得很好。
SRS如何检测docker呢,通过读取文件/proc/1/cgroup的内容,如果有docker则认为是在docker中运行:
[root@05181e679dc0 trunk]# cat /proc/1/cgroup
14:name=systemd:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
13:rdma:/
12:pids:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
11:hugetlb:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
10:net_prio:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
9:perf_event:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
8:net_cls:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
7:freezer:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
6:devices:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
5:memory:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
4:blkio:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
3:cpuacct:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
2:cpu:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
1:cpuset:/docker/05181e679dc0edc0ac42c19b18f2a0c96175901c5b68f6fce00cf7a5beac78ef
而在真实的linux server上运行时,这个文件的内容如下:
[winlin@SRS ~]$ cat /proc/1/cgroup
8:blkio:/
7:net_cls:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct:/
2:cpu:/
1:cpuset:/
这样就解决了daemon在docker和linux server上的支持问题,简单易用。
Service Discovery
在K8S中,源站是通过Service提供的服务,也就是域名提供的服务,边缘可以通过配置源站的内部域名(Service名称)来实现服务发现。源站集群内部,则需要使用StatefulSet或者每个Service一个Origin的方式实现服务发现。如下图所示:
源站集群的改进方案,是通过OCM(Origin Cluster Manager)将流的信息无状态化存储,同时通过内部转发实现边缘访问的无状态化,这个是在超大规模的源站集群中(比如100万路流)才需要用到,目前还未实现。
Docker Images
很早以前,SRS部署是通过安装包和脚本安装,进程和服务管理是通过linux service方式。当然这种方式现在也是支持的,比如我们可以用docker打包:
echo "Package SRS 3.0 for CentOS7" &&
git clone https://github.com/ossrs/srs.git && cd srs && git checkout 3.0release &&
docker run -it -v `pwd`:/tmp/srs -w /tmp/srs/trunk ossrs/srs:dev ./scripts/package.sh --x86-x64
下载安装包后,解压和执行安装脚本:
echo "For CentOS7" &&
unzip -q SRS-CentOS7-x86_64-*.zip && cd SRS-CentOS7-x86_64-* && sudo bash INSTALL &&
sudo /etc/init.d/srs start
就可以启动服务了:
sudo /etc/init.d/srs start # CentOS 6
sudo systemctl start srs # CentOS 7
上面的方式对于CentOS6或CentOS7比较容易实现,但对于其他linux或windows,就比较困难了。在K8S中部署,我们当然也不能用二进制方式,而要提供官方的docker镜像。
因此,SRS创建了官方的镜像项目srs-docker,并且提供了多个docker镜像:
ossrs/srs ,SRS的最新稳定版本,目前是SRS3的最新稳定版本。
ossrs/srs:3 ,SRS3.0的最新稳定版本,目前稳定版是SRS3.0-beta2。
ossrs/srs:v3.0-b2 ,SRS3.0每次发布的稳定版本,可以在releases中看发布了哪些版本,每个版本都会打一个镜像。
registry.cn-hangzhou.aliyuncs.com/ossrs/srs:v3.0-b2 ,杭州阿里云的docker镜像站,有上面所有的镜像,国内访问的速度更快,推荐用这个镜像。
ossrs/srs:2 ,SRS2.0最新的稳定版镜像。
ossrs/srs:v4.0.8 ,SRS4.0的开发版镜像,只有某些具体的版本,可以参考github上的release,或者使用git tag查看可用的版本。
ossrs/srs:dev ,开发的镜像,CentOS7环境,有比较完善的工具。
ossrs/srs:srt ,支持SRT的镜像,CentOS7环境,有编译好的SRT库。
Note: 上面所有的这些版本,可以在github官网上查看releases,或者通过git tag查看可用的版本。
Console & Demos
默认安装好SRS后,会将srs-console,还有一些静态文件比如crossdomain.xml,以及index.html等页面,都安装到html根目录。而在K8S部署源站集群时,我们需要将html目录挂载成volume,和nginx共享HTTP文件,这样可以用nginx分发HLS或DASH切片。
当我们将html根目录挂载成volume时,会将SRS自带的console等文件清空,导致flash播放器无法访问crossdomain.xml,导致播放失败。这也很不方便,没有srs-console和必要的静态文件,无法查看SRS的状态。
在K8S中,我们通过一个sidecar容器,将SRS的默认文件拷贝到挂载的volume中,如下图所示:
这个Sidecar容器很容易实现,就是将文件拷贝后等待就好了:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: srs-deploy
labels:
app: srs
spec:
replicas: 1
selector:
matchLabels:
app: srs
template:
metadata:
labels:
app: srs
spec:
volumes:
name: cache-volume
emptyDir: {}
containers:
name: srs
image: ossrs/srs:3
imagePullPolicy: IfNotPresent
ports:
containerPort: 1935
containerPort: 1985
containerPort: 8080
volumeMounts:
name: cache-volume
mountPath: /usr/local/srs/objs/nginx/html
readOnly: false
name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
containerPort: 80
volumeMounts:
name: cache-volume
mountPath: /usr/share/nginx/html
readOnly: true
name: srs-cp-files
image: ossrs/srs:3
imagePullPolicy: IfNotPresent
volumeMounts:
name: cache-volume
mountPath: /tmp/html
readOnly: false
command: ["/bin/sh"]
args:
"-c"
>
if [[ ! -f /tmp/html/index.html ]]; then
cp -R ./objs/nginx/html/* /tmp/html
fi &&
sleep infinity
EOF
这个Pod运行了三个容器,SRS主容器,还有两个Sidecar:
srs,提供流媒体源站服务,生成HLS切片到共享volume。
nginx,提供HTTP文件分发服务,从共享volume读取HLS切片。
srs-cp-files,拷贝SRS静态文件到共享volume,它没有挂载volume到html根目录,所以能将SRS的html根目录文件拷贝到共享volume。
这种方式也不需要太多改动,是解决这个问题的最简单的办法,更多信息参考#1603的描述。
Storage & ConfigMap
上面的例子中,我们使用emptyDir方式在Pod之间共享文件,但是对于源站集群,需要在多个Pod之间共享文件,比如源站集群将文件写到共享的存储,统一对外提供HLS服务,这就需要用到pv持久化存储了。如下图所示:
Note: 上图中的NAS,就是阿里云提供的一种PV持久化存储,可以在Pod之间共享存储,多个Pod写入HLS文件。
另外,对于配置文件,也是需要在多个Pod中共享的,在K8S中可以通过ConfigMap保存配置,然后挂载到Pod中。例如下面的方式:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: srs-config
data:
|- :
listen 1935;
max_connections 1000;
daemon off;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
}
vhost __defaultVhost__ {
http_remux {
enabled on;
}
hls {
enabled on;
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: srs-deploy
labels:
app: srs
spec:
replicas: 1
selector:
matchLabels:
app: srs
template:
metadata:
labels:
app: srs
spec:
volumes:
name: config-volume
configMap:
name: srs-config
containers:
name: srs
image: ossrs/srs:3
imagePullPolicy: IfNotPresent
ports:
containerPort: 1935
containerPort: 1985
containerPort: 8080
volumeMounts:
name: config-volume
mountPath: /usr/local/srs/conf
EOF
我们可以修改ConfigMap,这样文件会自动更新。之前SRS可以通过reload命令,或者给SRS发送SIGHUP实现reload加载配置文件,在K8S中无法发送命令,SRS会自动侦听配置文件的修改,自动实现reload,我们新增了两个配置:
# Whether auto reload by watching the config file by inotify.
# default: off
inotify_auto_reload off;
# Whether enable inotify_auto_reload for docker.
# If on, it will set inotify_auto_reload to on in docker, even it's off.
# default: on
auto_reload_for_docker on;
这两个配置项的意图如下:
inotify_auto_reload默认为off,也就是不会自动侦听配置文件的更新和自动reload。
auto_reload_for_docker默认为on,也就是在docker下面会自动开启上面的配置。
这样用户使用默认配置,在linux server中部署,还是在docker中部署,都可以达到同样的效果。同样这个方案也很简单,更多信息参考#1635的描述。
Canary & Gracefully Quit
K8S关键可以实现服务的无中断升级和灰度(Canary)发布,之前SRS无法实现这个功能,需要靠用户的运维平台才能实现。实现Canary发布的关键,是需要SRS实现Gracefully Quit(平滑退出),也就是收到信号SIGQUIT后会关闭侦听不再服务新的连接,当有连接时,SRS要等待一定时间才退出。
下面是两个SRS,没有连接的SRS会很快退出,有连接的SRS会等待一定时间后才会退出,或者服务完客户端后退出:
kubectl exec srs-edge-deploy-58d9999b7c-pnr2f -- tail -f objs/srs.log
[2020-02-19 11:07:20.818][Trace][1][937] sig=3, user start gracefully quit
[2020-02-19 11:07:20.960][Trace][1][937] force gracefully quit, signo=15
[2020-02-19 11:07:21.772][Trace][1][932] cleanup for quit signal fast=0, grace=1
[2020-02-19 11:07:21.772][Warn][1][932][11] main cycle terminated, system quit normally.
command terminated with exit code 137
kubectl exec srs-edge-deploy-58d9999b7c-z9gbm -- tail -f objs/srs.log
[2020-02-19 11:07:23.095][Trace][1][1009] sig=3, user start gracefully quit
[2020-02-19 11:07:23.316][Trace][1][1009] force gracefully quit, signo=15
[2020-02-19 11:07:23.784][Trace][1][1004] cleanup for quit signal fast=0, grace=1
[2020-02-19 11:07:23.784][Warn][1][1004][11] main cycle terminated, system quit normally.
[2020-02-19 11:07:24.784][Trace][1][1004] wait for 1 conns to quit
[2020-02-19 11:07:26.968][Trace][1][1010] <- CPB time=120041497, okbps=0,0,0, ikbps=252,277,0, mr=0/350, p1stpt=20000, pnt=5000
[2020-02-19 11:08:26.791][Trace][1][1004] wait for 1 conns to quit
[2020-02-19 11:08:52.602][Trace][1][1010] edge change from 200 to state 0 (init).
[2020-02-19 11:08:52.792][Trace][1][1004] wait for 0 conns to quit
command terminated with exit code 137
kubectl get po |grep edge
NAME READY STATUS RESTARTS AGE
srs-edge-deploy-58d9999b7c-z9gbm 0/1 Terminating 0 3m52s
srs-edge-deploy-76fcbfb848-z5rmn 1/1 Running 0 104s
srs-edge-deploy-76fcbfb848-zt4wv 1/1 Running 0 106s
Note: K8S终止Pod时的等待时间,也就是SRS退出的等待时间,可以通过设置terminationGracePeriodSeconds来指定,默认是30秒。
同样我们新增了几个配置,来控制SRS在linux server或者docker中的平滑退出行为,更多可以参考#1579:
# For gracefully quit, wait for a while then close listeners,
# because K8S notify SRS with SIGQUIT and update Service simultaneously,
# maybe there is some new connections incoming before Service updated.
# @see https://github.com/ossrs/srs/issues/1595#issuecomment-587516567
# default: 2300
grace_start_wait 2300;
# For gracefully quit, final wait for cleanup in milliseconds.
# @see https://github.com/ossrs/srs/issues/1579#issuecomment-587414898
# default: 3200
grace_final_wait 3200;
# Whether force gracefully quit, never fast quit.
# By default, SIGTERM which means fast quit, is sent by K8S, so we need to
# force SRS to treat SIGTERM as gracefully quit for gray release or canary.
# @see https://github.com/ossrs/srs/issues/1579#issuecomment-587475077
# default: off
force_grace_quit off;
Note: 特别需要注意的是,在K8S中,需要打开force_grace_quit,因为默认会发送SIGTERM导致SRS快速退出,开启这个配置后,SIGTERM和SIGQUIT都会认为是平滑退出。
SRS支持Gracefully Quit后,我们就可以用K8S实现Canary发布,发布前假设SRS都是v4.0.5版本,如下图所示:
spec:
replicas: 3
selector:
matchLabels:
run: srs-edge-r5
template:
metadata:
labels:
run: srs-edge-r5
app: srs-edge
我们新增一个Deployment应用,标签和前面设置成一样app: srs-edge,这样流量就可以导入到这两个Deployment应用了:
spec:
replicas: 1
selector:
matchLabels:
run: srs-edge-r6
template:
metadata:
labels:
run: srs-edge-r6
app: srs-edge
后面我们只需要控制这两个Deployment的Replicas,就能准确的控制灰度的流量,而老的应用会使用Gracefully Quit机制平滑退出,这个方案也是非常简单而强大的。
One More Thing
SRS做这么多事情,就是为了更容易使用和维护,透露下SRS4的主要目标:
已完成:支持Cloud Native,支持K8S+Docker。
已完成:支持SRT,广电行业上云,远距离推流,互联网分发。
正在MR:支持GB28181,监控行业上云,智能IoT场景互联网分发。
正在开发:支持WebRTC,会议行业上云,看来看去,WebRTC还缺也只缺一个真正的高性能服务器。
计划中:支持录制到云存储,录制是互联网流媒体应用的核心诉求。
计划中:支持AI检测,内容鉴黄,语音转文字等。
欢迎来Github给SRS点个赞(Star)吧:https://github.com/ossrs/srs
以上是关于SRS: Cloud Native改进知多少的主要内容,如果未能解决你的问题,请参考以下文章