1. 第一步先搞清楚:SRS 到底是什么,为什么非要用 Docker 跑

聊到流媒体服务器,很多人的第一反应是 Nginx 加 RTMP 模块、或者直接上商用云服务。但如果你需要一套开源、可控、支持协议足够全的服务端,SRS(Simple Realtime Server)绝对是绕不开的名字。这个项目最早是单机版的 RTMP 推流拉流服务,后来一路演进,到现在已经能同时扛 RTMP、HTTP-FLV、HLS、SRT、WebRTC 等多种协议,很多在线教育、直播带货、安防监控、远程庭审的场景,底层跑的都是它。

那问题来了:SRS 本身安装并不算难,官方提供了编译安装的方式,为什么我还要专门写一篇用 Docker 部署的文章?因为我在实际项目里吃过亏。编译安装意味着你需要在服务器上准备一堆编译工具链和依赖库,升级版本的时候还要重新编译一遍,万一机器上还跑着别的业务,各种库版本冲突真的能把人折腾到怀疑人生。尤其是当你需要在三五台服务器上同时部署的时候,每台机器手动编译一次,那个时间成本和维护成本是实打实的。

Docker 的价值在这种场景下体现得淋漓尽致。镜像直接拉下来就能跑,依赖全部封装在容器里,不会污染宿主机环境。升级 SRS 版本就是换个镜像标签、重新起一个容器的事。更重要的是,Docker 的端口映射和资源隔离让整个服务的运维变得非常干净利落。

这篇文章适合谁看?如果你是那种打算在个人服务器上搭一个直播服务、或者公司内部需要快速验证流媒体方案、又或者是运维同学要在生产环境标准化交付 SRS 的,这篇文章都能给你一条完整的、经过实践检验的路径。我会从最基础的镜像拉取开始讲,一直讲到鉴权、HTTPS、多节点扩展这些进阶操作,保证你照着做就能跑起来。

2. 部署前必须想明白的三件事:端口、目录、网络模式

2.1 端口的规划比你想的更重要

SRS 不是一个只监听一个端口的服务。它的端口规划直接决定了你的防火墙规则、安全组策略怎么写。默认情况下,SRS 会用 1935 端口处理 RTMP 推流和拉流,8080 端口提供 HTTP-FLV、HLS 和 Web 控制台,1985 端口是 HTTP API,8000 端口是 WebRTC over UDP 的端口。如果你还需要 SRT 协议,那还要额外开放 9000 端口(UDP)。

我见过太多人部署完 SRS,本地 curl 一下发现 8080 端口通了,就以为大功告成,结果手机上的播放器死活拉不了流,排查半天发现是云服务器的安全组没放行 1935 和 8000。记住,云服务器的安全组、系统自带的 firewalld 或 iptables、以及 Docker 的端口映射这三层都要放行对应的端口,缺一层都不行。

还有一个很容易被忽略的点:WebRTC 的信令走的是 TCP,但媒体流走的是 UDP。如果你要支持 WebRTC 拉流,UDP 8000 端口必须要通,而且这个端口在 SRS 的配置里是可以改的,但改了之后要确保客户端那边能拿到正确的端口信息,否则就会出现信令通了、媒体流却迟迟不来的诡异现象。

2.2 目录挂载:容器销毁了,数据不能丢

Docker 容器的特性是“一切皆可丢弃”,但流媒体服务有配置、有日志,有时候你还会把录制的视频文件存在本地。这些数据如果在容器内部,那容器一删就全没了。所以在启动容器之前,一定要规划好目录挂载。

我个人习惯是把配置文件和日志目录挂出来,如果开启了录制功能,再单独挂一个数据目录。这样做的好处是,你升级镜像版本的时候,旧容器删掉、新容器起来,配置还是那一份,日志也能延续,不会有“查历史日志发现是空的”这种尴尬情况。

有一个细节要特别注意:SRS 官方镜像默认的工作目录是 /usr/local/srs ,配置文件在 /usr/local/srs/conf 下面。挂载配置目录的时候,要么把整个 conf 目录挂出来,要么只挂载你自己写的那个配置文件。如果你只挂载了一个文件,要确保容器启动时指定的配置文件路径和挂载的路径是一致的,否则容器会用镜像里的默认配置,你改了半天发现根本没生效。

2.3 网络模式:host 还是 bridge,这是个问题

Docker 的网络模式对于 SRS 来说,不是随便选的。SRS 涉及多种协议、多个端口,如果用默认的 bridge 模式,每个端口都要用 -p 参数映射出去,多而且容易漏。更关键的是,WebRTC 场景下媒体流的传输端口是动态协商的,bridge 模式下这些动态端口的映射会非常麻烦。

所以对于 SRS,我强烈推荐用 --network=host 模式。这个模式下容器直接复用宿主机的网络栈,SRS 监听的端口就是宿主机的端口,不需要做端口映射,防火墙规则也只需要写一次。缺点嘛,就是容器失去了网络层面的隔离,但考虑到 SRS 本身就是要对外提供服务的,这个代价换来的便利是完全值得的。

如果你出于某些原因必须用 bridge 模式,那就要把所有端口一个一个映射出来,而且 WebRTC 的 UDP 端口段也要映射完整,不然就会出现某些用户能通、某些用户不能通的奇怪问题。

3. 实操环节:从零到一跑起你的第一个 SRS 容器

3.1 环境确认和镜像选择

在拉镜像之前,先确认一下 Docker 环境是好的。终端里执行 docker version ,能正常输出客户端和服务端版本信息就说明没问题。如果你连 Docker 都还没装,不同系统的安装方式不太一样:

  • Ubuntu / Debian 系可以直接用官方脚本安装: curl -fsSL https://get.docker.com | bash
  • CentOS / RHEL 系可以用 yum install docker-ce 来装
  • Windows 和 macOS 则建议直接安装 Docker Desktop

这些基础的安装步骤网上资料很多,我重点想说的是镜像选择。SRS 官方在 Docker Hub 上有镜像仓库,最新的大版本是 SRS 5.0,镜像标签是 ossrs/srs:5 。这个版本和 4.0 相比,对 WebRTC 的支持更加完善,SRT 的性能也有提升,如果你是新部署,直接用 5 就行。

我之前见过有些教程让人用 ossrs/srs:latest ,这个标签其实也指向 5.0,但我个人不太建议在生产环境用 latest 标签,因为“最新版”是一个移动的目标,你很难确定今天拉下来的镜像和昨天拉下来的有什么区别。用 ossrs/srs:5 这种固定大版本的标签,确定性会高很多。

3.2 第一行命令:最小化启动一个 SRS

拉镜像的过程没什么好说的, docker pull ossrs/srs:5 等它下载完就行。真正有意思的是启动命令。如果你只是想快速验证一下 SRS 能不能跑,用最精简的方式启动:

docker run -d --name srs \
  --network=host \
  -v /data/srs/conf:/usr/local/srs/conf \
  -v /data/srs/log:/usr/local/srs/objs \
  ossrs/srs:5

这里有一点要说清楚: -v /data/srs/conf:/usr/local/srs/conf 是把本机的配置目录挂到容器里,但如果你本机这个目录是空的,容器里原来的配置会被隐藏掉(不是删除,只是看不到了)。所以第一次启动的时候,我建议先不挂配置目录,让容器用默认配置跑起来,确认服务正常之后,再把容器里的配置拷贝出来:

docker cp srs:/usr/local/srs/conf/srs.conf /data/srs/conf/srs.conf

把默认配置复制到宿主机之后,你就可以随意修改这个文件了,改完重启容器就好。这样做的好处是,你永远有一份“官方原始配置”作为对照,改出问题还能对比排查。

启动之后,用 docker ps 看一下容器状态,再用 docker logs srs 看看日志输出。正常情况下你会看到类似这样的日志:

srs 5.0.0.0 (v5.0-release) build ...
config: /usr/local/srs/conf/srs.conf
startup: http listen on :8080
startup: rtmp listen on :1935
startup: webrtc listen on :8000

看到这几行就说明 SRS 已经起来了。这时候打开浏览器访问 http://你的服务器IP:8080 ,你能看到一个简洁的管理后台页面,里面显示了 SRS 的版本号、运行时间、当前的连接数等信息。

3.3 验证拉流和推流:第一次真正跑通数据流

服务起来了,但 SRS 的核心价值是流媒体的转发,所以你至少要验证一次完整的推流和拉流链路。我用一个最简单的方式演示。

先准备一个视频文件,比如一个 MP4 文件。然后宿主机上装好 ffmpeg,执行推流命令:

ffmpeg -re -i /path/to/video.mp4 -c copy -f flv rtmp://localhost:1935/live/test

看到 ffmpeg 在持续输出,没有报错,说明视频正在推流。这时候再用 ffplay 或者 VLC 去拉流验证:

ffplay rtmp://localhost:1935/live/test

如果你能正常播放视频,就说明 RTMP 推拉流的链路是通的。接着验证 HTTP-FLV:

ffplay http://localhost:8080/live/test.flv

再验证 HLS,HLS 有个特点是有延迟,大概会有 10 秒到 30 秒的延迟,需要等一会儿才能看到画面:

ffplay http://localhost:8080/live/test.m3u8

三条链路的验证都通过之后,你的 SRS 基本就算是部署成功了。这几个协议对应的 URL 地址,后面配置推流端和播放端的时候都要用到,建议截图保存一下。

3.4 用配置文件的视角重新审视:为什么默认配置能直接跑

SRS 之所以能一条命令跑起来,是因为它的默认配置 srs.conf 里已经内置了一套很典型的配置:RTMP 监听 1935,HTTP 监听 8080,HLS 在 objs/nginx/html 目录下生成切片文件,HTTP-FLV 流可以直接从 8080 端口访问。这套默认配置覆盖了最常见的直播场景,非常适合做“先跑起来再做定制”的起点。

但默认配置不可能是万能的。比如它默认的 HLS 切片时长是 10 秒,如果你的业务对延迟要求比较高,这个值就得调;再比如它没有开启鉴权,任何人只要知道你的服务器地址和流名称就能推流,这在公网环境是不可接受的。所以后面我单独讲一下几个最常改的配置点。

4. 进阶配置:从“能跑”到“好用”

4.1 开启鉴权:别让你的服务器变成公共直播源

如果你的 SRS 暴露在公网上,不开启鉴权就意味着任何一个人都可以往你的服务器上推流、拉流。有人可能会说,流名称是随机生成的,别人猜不到不就行了?大错特错。扫描公网 IP 的自动化脚本到处都是,1935 端口开放的服务很快就会被人发现。

SRS 支持两种维度的鉴权:推流鉴权(publish)和播放鉴权(play)。配置方式是在 vhost 里加回调配置,让 SRS 在收到推流或播放请求时,回调你自己的鉴权服务,根据回调结果决定是允许还是拒绝。

以推流鉴权为例,配置段大致长这样:

vhost __defaultVhost__ {
    # 推流鉴权
    on_publish http://127.0.0.1:8080/api/v1/auth_publish;
    on_play http://127.0.0.1:8080/api/v1/auth_play;
}

当有客户端推流时,SRS 会向 on_publish 指向的地址发起 HTTP 请求,把你的鉴权服务返回的 HTTP 状态码作为判断依据。200 表示允许,其他状态码表示拒绝。你的鉴权服务内部怎么实现都行,可以验签名、可以查数据库、可以对接企业内部的统一认证,灵活性非常强。

我不建议在公网环境省掉这一步。哪怕你的鉴权逻辑很简单,只是校验一个固定的 token,也要做。这就像你家里装了个门锁,虽然挡不住专业的入室盗窃,但至少能挡住随手拉门的路人。

4.2 配置 HTTPS:现在的主流播放器都要求安全连接

以前 RTMP 和 HTTP 协议跑得好好的,为什么非要折腾 HTTPS?因为现在主流浏览器对非安全连接的限制越来越严格,尤其是涉及摄像头权限的时候, getUserMedia API 在非 HTTPS 环境下根本不可用。如果你要做的是在线课堂、视频会议这种需要采集本地摄像头的场景,HTTPS 是硬性要求。

SRS 自身不提供 HTTPS 服务,最常见的做法是在前面加一层 Nginx 或者 Caddy 做反向代理,把 443 端口的 HTTPS 流量代理到 SRS 的 8080 端口。Nginx 的配置大致是这样:

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate     /etc/nginx/ssl/yourdomain.com.crt;
    ssl_certificate_key /etc/nginx/ssl/yourdomain.com.key;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

这里有一个很多人踩过的坑:WebRTC 的信令走的是 WSS(WebSocket over TLS),如果让 Nginx 直接把 /rtc 路径代理给 SRS,需要额外配置 WebSocket 的升级头。Nginx 正确的写法是:

location /rtc {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

不写这两个头,WebRTC 的客户端连不上就会一直转圈、报错,而且错误信息还不直观,排查起来非常折磨人。

证书的获取也值得多说一句。现在 Let's Encrypt 提供免费证书,配合 certbot 工具,整个申请、续期流程都是自动化的。不要再去买什么付费 SSL 证书了,除非你的企业有合规要求必须用特定 CA 签发的证书。用 certbot 的命令大概是:

certbot --nginx -d yourdomain.com

certbot 会自动帮你修改 Nginx 配置、申请证书、配置自动续期,一条命令搞定。跑完之后你再访问 https://yourdomain.com 就能看到地址栏的小锁头了。

4.3 对接 WebRTC:低延迟场景的必由之路

RTMP 和 HTTP-FLV 本质上都是基于 TCP 的,延迟再优化也有天花板,通常在一秒到三秒之间。如果你的业务场景是直播连麦、视频会议、远程操控这类需要亚秒级延迟的,那就得上 WebRTC。

SRS 5.0 对 WebRTC 的支持已经比较成熟了。要让 WebRTC 正常工作,除了确保 UDP 8000 端口通畅之外,还需要在配置里显式声明候选 IP。如果你的服务器有多个 IP,或者部署在 NAT 后面,这一步特别容易出错。

在 srs.conf 里找到 webrtc 相关的配置段,设置候选地址:

rtc_server {
    enabled on;
    # 设置对外候选IP
    candidate $CANDIDATE;
}

$CANDIDATE 是一个环境变量,你可以在启动容器的时候通过 -e 参数传入。比如:

docker run -d --name srs \
  --network=host \
  -e CANDIDATE=你的公网IP \
  -v /data/srs/conf:/usr/local/srs/conf \
  ossrs/srs:5

如果你的服务器在 NAT 后面,这个候选 IP 要填公网 IP,而不是内网 IP。填错了的典型症状是:WebRTC 的推流端显示连接上了,但拉流端看不到画面,或者延迟明显异常。原因就是 SRS 在信令协商阶段告诉客户端一个错误的候选地址,客户端试图去连内网 IP,自然连不上。

还有一点,WebRTC 对时间戳的要求非常高,如果宿主机的系统时间和实际时间偏差过大,会导致音画不同步。建议在宿主机上开启 NTP 时间同步服务,保证系统时间准确。这个细节一般没人提醒,但我在实践中遇到过,排查到最后发现是时间问题,那种感觉真的五味杂陈。

4.4 录制功能:把直播变成可回放的内容资产

很多业务场景下,直播结束之后还需要回放,比如在线教育的课程回放、带货直播的录屏留存。SRS 原生支持把直播流录制为 FLV 文件或者 MP4 文件。

配置方式也很简单,在 srs.conf 里加上:

vhost __defaultVhost__ {
    dvr {
        enabled on;
        dvr_apply all;
        dvr_path /data/record/[2006]/[01]/[02]/[15]/[04][05][09].flv;
        dvr_plan segment;
        dvr_duration 3600;
        dvr_wait_keyframe on;
    }
}

这段配置的意图是:开启录制,直播片段按小时切分,存放路径按年月日时组织。 dvr_path 里的 [2006] 这种写法是时间占位符,不会真的出现 [2006] 这样的字面路径,而是会被替换为当前的年份。这样录制的文件就被自动归档到 2024/06/15/ 这样的目录结构里了。

需要注意的是,录制文件是存在容器里的。所以如果你开启了录制,一定要在启动容器的时候挂载目录,比如 -v /data/srs/record:/data/record ,否则容器一销毁,你录下来的东西就全部打水漂了。

5. 在实际部署中踩过的坑:问题排查实录

5.1 推流失败:明明配置看起来没问题

现象:ffmpeg 推流时提示握手失败,或者连接被重置。看到这个问题,我先怀疑端口。检查宿主机上 ss -lnt 看 1935 端口有没有在监听,再确认云平台的安全组和宿主机防火墙是否放行。

这里分享一个我惯用的检查顺序:先在服务器本地用 curl http://localhost:8080/api/v1/versions 看 API 通不通,再在本地推流试试,如果本地正常、远程失败,那问题基本可以锁定在防火墙上,而不是 SRS 本身。依次排查云安全组、firewalld/iptables、SELinux。

SELinux 是一个很少有人注意的坑。CentOS 默认开启的 SELinux 会限制 Docker 容器对挂载目录的读写权限,表现症状是配置文件挂载不生效、录制的文件写不进去。快速排查方法是用 getenforce 命令看 SELinux 当前状态,如果是 Enforcing ,可以临时用 setenforce 0 关掉试试。如果关掉之后问题消失,说明确实和 SELinux 有关,建议要么把对应的目录改成容器可以访问的 SELinux 上下文,要么干脆在确认安全的前提下把 SELinux 关掉。

5.2 拉流黑屏或一直缓冲:排查链路的思路

现象:推流端显示正常推流,但拉流端黑屏或者一直转圈。这种问题在直播场景里非常常见,排查思路有一个先后顺序。

先看服务端是否真的收到了流。访问 http://你的IP:8080/api/v1/streams ,这个 API 会返回当前所有活跃的流。如果这里没有你要的流,说明推流根本没上来,问题在推流端;如果有流,再确认客户端访问的地址是否正确。

HTTP-FLV 的地址要带 .flv 后缀: http://你的IP:8080/live/test.flv 。HLS 的地址要带 .m3u8 后缀: http://你的IP:8080/live/test.m3u8 。很多人把这两个协议弄混,拉流地址写错,自然拉不到。

如果地址也对,但还是拉不了,就要检查防火墙是否放行了对应的端口。ICMP 的 ping 通只说明了主机在线,和端口开放没有直接关系。测试端口是否开放,用 telnet 你的IP 8080 这种方式更准确。

5.3 容器反复重启:启动失败的一个常见原因

现象: docker ps 里看到 SRS 容器的状态是 Restarting ,过几秒又重启,反复循环。这通常是配置文件的语法错误导致的。

SRS 启动时如果配置文件有语法错误,进程会直接退出。容器里的进程退出了,容器就停了,如果设置了 restart=always 策略,Docker 就会反复尝试重启,形成一个死循环。

遇到这种情况,先别急着看配置,用 docker logs srs 看看日志里的报错信息。SRS 的报错信息还算友好,一般会直接指出是第几行配置有问题。定位到问题之后,修改完重启 docker restart srs 即可。

我个人的经验是,改完配置之后先在容器里做一次语法检查再重启,SRS 有这样一个命令行参数可以验证配置:

docker exec srs ./objs/srs -t

-t 参数表示只检查配置文件而不真正启动服务。养成改完配置先检查再重启的习惯,可以省掉很多无谓的重启时间。

5.4 时好时坏的诡异问题:多半是端口映射和网络模式搞的鬼

有一种场景很让人抓狂:SRS 部署好了,推流和拉流大部分时间是正常的,但偶尔会有用户反馈拉不到流,重启容器又好了,过一会儿又有问题。

我之前曾经遇到过一次,排查到最后发现是容器用了 bridge 网络模式,端口映射不完整。当时的场景是 WebRTC 播放,TCP 信令通了,但媒体流走 UDP 8000 端口,而这个端口没有映射到宿主机上。所以表现就是信令正常、媒体流迟迟不来。

解决方案也很直接,把容器换成 host 网络模式,所有端口自然就全部暴露出来了。如果你的部署环境不允许用 host 模式,那就需要把 1935、8080、1985、8000 和所有动态 UDP 端口都完整映射出来,缺一个就可能导致这种间歇性的问题。这种问题最难排查,因为没有固定的复现步骤,往往是在最忙的时候给你来一下。

5.5 磁盘空间暴涨:日志和录制文件的双重考验

SRS 运行一段时间之后,你可能会发现磁盘空间在快速减少。这个问题主要由两个方面贡献:一是 SRS 的日志文件,默认是写到 objs/srs.log 的;二是如果开了录制,录制的视频文件会持续占空间。

对于日志问题,Docker 本身有日志轮转机制,但默认的 json-file 日志驱动是不限制日志大小的。推荐在 daemon.json 里配置 Docker 的日志轮转:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

这样单个日志文件超过 100M 就会自动轮转,最多保留 3 个文件。

对于录制文件,这是业务逻辑的一部分,不能简单地“轮转”,而是要有明确的归档和清理策略。比如设置一个定时任务,把超过 30 天的录制文件转移到冷存储,然后定期清理。这块可以用 cron 配合 find 命令实现:

find /data/srs/record -type f -mtime +30 -delete

不用写太复杂,关键是养成定期检查磁盘空间的习惯,不要等到磁盘满了才发现,那时候 SRS 会直接拒绝新的推流,线上业务就挂了。

6. 多节点扩展:流量大了之后怎么办

单机 SRS 的并发能力其实不弱,处理几百路并发流、几千路在线观看是没问题的。但如果你做的是那种体量很大的业务,比如大型直播活动,单机的带宽和连接数撑不住,就需要考虑多节点扩展了。

SRS 支持原生的 Origin 和 Edge 架构。Origin 节点负责接收推流,Edge 节点负责向用户分发播放流。这个架构下,推流端只连接 Origin,播放端就近连接 Edge,实现负载均衡。

用 Docker 部署多节点的操作和单机类似,只是每个节点启动时的角色配置不同。Origin 节点在配置里设置:

srs {
    mode origin;
    origin {
        # 默认vhost配置
    }
}

Edge 节点设置:

srs {
    mode edge;
    edge {
        # 回源到Origin节点
        origin 10.0.0.1:1935;
    }
}

Docker 部署时,Edge 节点通过 --add-host 或者直接把 Origin 的 IP 写进配置,就能实现 Origin 与 Edge 的联动。如果你用的是 Docker Compose 或者 Kubernetes,这种多节点扩展的管理会更方便,但核心原理是一样的。

对于绝大多数场景来说,单机加一个 Edge 节点已经能撑起不小的并发量了。再往上,就要考虑用专业的 CDN 厂商的推流和分发网络了,SRS 可以作为一个接收推流并转推给 CDN 的源站。这时候 SRS 的“简单”反而成了一种优势——它做源站非常稳,不追求花哨的功能,就是老老实实地转发流。

7. 最后,说几句大实话

写了这么多,核心想表达的就一句话:用 Docker 部署 SRS,是当前搭建流媒体服务性价比最高的方式之一。它足够简单,一条命令就能起服务;它也足够强大,RTMP、HLS、HTTP-FLV、SRT、WebRTC 一个不少;它还有足够的生态支撑,文档完善、社区活跃、GitHub 上的 issue 响应也及时。

但我也想说句真心话:部署本身只是万里长征第一步。真正考验人的是后面的配置调优、故障排查、容量规划这些脏活累活。服务跑起来之后,你还要持续关注磁盘、带宽、连接数、延迟这些指标,SRS 的 HTTP API 本身可以暴露这些数据,配合 Prometheus 和 Grafana 可以搭一套很漂亮的监控面板。

如果你只是在个人服务器上折腾着玩,那从本文的 3.2 节开始照着敲命令就行。如果你是在为生产环境做准备,那至少要把鉴权和 HTTPS 配好,再认真规划一下端口和目录。流媒体这个东西,调试的时候最磨人,但只要链路跑通一次,后面就会顺畅很多。我在数次部署中反复踩坑摸索出来这些要点,今天写出来分享给你,希望你能少走一些弯路。

Logo

邀请您加入社区

更多推荐