☰
基于CentOS 7.9与Docker的ZLMediaKit流媒体服务器部署实战
2026/9/26 11:53:02 网站建设 项目流程

1. 项目概述与选型思路

1.1 先聊聊为什么是ZLMediaKit

做流媒体服务的人应该都有体会,2024年了,市面上能打的流媒体服务器其实就那么几个。SRS老牌但配置复杂,Nginx-RTMP模块功能单一只适合简单推拉流,而现在视频直播、安防监控、WebRTC通话这些场景越来越向低延迟、多协议融合的方向走,ZLMediaKit(以下简称ZLM)几乎是绕不开的选择。

ZLM是一个基于C++11的高性能流媒体服务器,最大的优势是协议支持极其丰富——RTSP、RTMP、HLS、HTTP-FLV、WebSocket-FLV、GB28181、SRT、WebRTC全都能一套搞定。在安防领域,GB28181国标对接几乎是硬需求;在互联网直播领域,HTTP-FLV和HLS是分发主力;在WebRTC场景,ZLM也能直接参与信令和媒体协商。这意味着一个服务就能撑起从安防摄像头到Web直播再到实时互动的完整链路,省掉了以前要同时部署多个服务才能满足的麻烦。

另外一个特点是ZLM的性能和稳定性非常出色,单机并发能力远超Nginx-RTMP这类方案,ZLM的代码质量和社区活跃度也比较可观,遇到问题能在GitHub上找到解决方案。这也是我最终选定ZLM作为流媒体核心服务的原因。

1.2 为什么选择CentOS 7.9 + Docker组合

先说CentOS。虽然CentOS 7已经停止维护了,但在服务器领域仍有大量存量环境,而且7.9是生命周期内相对最成熟的版本,很多生产环境至今跑在上面,OpenSSL、内核、glibc等依赖相对稳定,对ZLM这种依赖网络性能的C++服务没有兼容性负担。CentOS 7的YUM源虽然官方停更了,但网易、阿里云等镜像站仍然在维护,安装依赖基本不是问题。

再说Docker。ZLM自带编译安装方案其实也不复杂,但从编译到运行要装一堆开发工具链,而且后续升级要重复劳动。Docker化部署的最大价值是:环境封闭、一次打包到处运行、升级回滚方便。对流媒体服务来说,Docker的网络模式(尤其是host模式)和目录挂载机制都很友好,不会像某些应用那样容器化后性能损耗明显。

我最终选的组合是CentOS 7.9 + Docker CE + ZLM官方Docker镜像,全部通过docker-compose编排。这套方案实操下来有几个好处:第一,宿主机只需要装Docker,不用污染系统环境;第二,ZLM的版本升级只需要pull新镜像重启容器;第三,配置目录和数据目录全部挂载到宿主机,排查问题直接看文件就行,不用进容器折腾。

2. Docker环境准备与基础配置

2.1 从零安装Docker CE

CentOS 7.9安装Docker CE是比较成熟的操作,但有几个细节要注意。很多教程会让你直接用yum install docker,这样装出来的是老版本,建议还是走官方Docker CE仓库。

# 卸载系统自带的老版本docker(如果有) sudo yum remove docker docker-client docker-common docker-engine -y # 安装依赖工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加Docker官方仓库(国内服务器可以替换为阿里云镜像源) sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装Docker CE及命令行工具 sudo yum install -y docker-ce docker-ce-cli containerd.io # 启动Docker并设置开机自启 sudo systemctl start docker sudo systemctl enable docker

安装完先别急着用,有几个操作习惯能帮你少踩坑。第一,必须确认Docker服务确实起来了,很多人卡在后续步骤其实都是前面服务没启动成功,用systemctl status docker检查一下比较稳。第二,为了让当前用户免sudo操作Docker,把用户加进docker组,但生产环境要慎重,docker组权限等同于root,进组的人能操作宿主机所有容器。

sudo usermod -aG docker $USER newgrp docker

提示:修改用户组后需要重新登录或者执行newgrp,当前会话不会立即生效。如果你是在SSH里操作,重新连接一次比较省事。

2.2 CentOS 7.9下Docker的关键调参

Docker装好只是第一步,跑流媒体服务之前还得做几个系统级调优,不然高并发下会出问题。

第一个是修改Docker的数据目录。默认情况下Docker把镜像和容器数据放在/var/lib/docker,如果你的系统盘只有几十G,几个镜像加日志很快就能撑爆。建议把数据目录迁移到数据盘,操作方法是修改/etc/docker/daemon.json:

{ "data-root": "/data/docker", "log-driver": "json-file", "log-opts": { "max-size": "200m", "max-file": "5" } }

container日志限制这块特别说一句,Docker默认是无限增长日志文件的,ZLM这种每路流都会打印日志的服务,流量一大磁盘立刻告警。设置单文件200MB就单文件200MB,保留5个文件,磁盘压力会小很多。

第二个是修改网络内核参数。流媒体服务是典型的网络密集型应用,建议打开BBR拥塞控制,提升高延迟、丢包网络下的吞吐性能。

# 检查当前内核是否支持BBR lsmod | grep bbr # 修改sysctl配置 cat >> /etc/sysctl.conf <<EOF net.core.default_qdisc = fq net.core.netdev_max_backlog = 4096 net.ipv4.tcp_congestion_control = bbr EOF sysctl -p

如果lsmod查不到bbr,说明内核版本低于4.9,CentOS 7默认内核是3.10,需要换内核。网络条件好的服务器不做这个优化也跑得动,但延迟高或者跨地域传输的场景,差别会比较明显,算是顺手做的基础优化。

第三个是防火墙和SELinux。ZLM需要对外提供多个端口服务,CentOS 7默认firewalld是开启的,如果不想在防火墙上开一堆端口,比较干脆的做法是直接停掉firewalld,但如果是公网服务器不建议这么干,该开端口还得开端口。SELinux如果之前是enforcing状态,要么改成permissive,要么给Docker挂载的目录设置正确的上下文,多数人的选择是直接关闭,图个省事。

# 关闭防火墙(如果内网环境或者图省事) systemctl stop firewalld && systemctl disable firewalld # 关闭SELinux setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

改动SELinux和防火墙之后建议重启一次机器,确认重启后Docker能正常启动,避免在重启后才发现配置有问题。

3. ZLMediaKit部署方案详解

3.1 镜像选择与容器规划

ZLM官方在Docker Hub上有现成镜像,也提供Dockerfile支持自行构建。直接拉官方镜像是最快的方式,但有几个版本细节需要注意。

docker pull zlmediakit/zlmediakit:master

master标签对应的是最新开发版,如果追求稳定建议用release标签,比如zlmediakit/zlmediakit:release-6.0这类版本号标签。ZLM的迭代节奏比较快,master分支每天都会更新,生产环境选一个明确版本号更靠谱。

ZLM容器有几个关键的目录和端口。配置目录是/opt/media/conf,需要挂载到宿主机;媒体数据目录是/opt/media/bin下存放可执行文件,/opt/media/www是Web管理页面相关资源。端口方面,默认HTTP服务是80,RTSP是554,RTMP是1935,RTP代理是10000,还有其他一堆媒体端口。

我建议容器网络模式选用host,这点和普通Web应用非常不一样。ZLM需要动态监听大量RTP端口做音视频转发,如果用bridge模式,端口映射规则会极其繁琐,而且性能损耗在媒体流场景下不可忽视。直接用host模式,容器共享宿主机网络栈,所有端口天然暴露,配置简单的同时少一层NAT转发,延迟和性能都更优。

3.2 完整docker-compose编排

我把ZLM的部署用docker-compose统一管理,配置文件写清楚版本、网络模式、目录挂载、环境变量,后续维护只需要一条命令搞定。

version: "3.8" services: zlmediakit: image: zlmediakit/zlmediakit:master container_name: zlmediakit network_mode: host restart: always environment: - TZ=Asia/Shanghai volumes: - /data/zlmediakit/conf:/opt/media/conf - /data/zlmediakit/log:/opt/media/log - /data/zlmediakit/bin:/opt/media/bin - /data/zlmediakit/data:/opt/media/data logging: driver: json-file options: max-size: "200m" max-file: "5" ulimits: nofile: soft: 65535 hard: 65535

这个编排文件有几个小心思说明一下。/data/zlmediakit/conf挂载出来后,配置文件可以直接在宿主机改,改完重启容器就生效,不用进容器操作。ulimits设置文件描述符上限到65535,这个是高并发流媒体服务的刚需,默认的1024连接数很容易触底,尤其是一路流挂很多观看者的时候。

启动容器:

docker-compose up -d docker-compose logs -f zlmediakit

启动后看到类似[MediaServer] started successfully的日志,说明服务已经起来了。访问http://服务器IP/能看到ZLM自带的测试页面,能正确显示就说明部署成功。

3.3 配置文件的深度解读

ZLM的配置全部集中在config.ini里,这是整个部署过程中最需要花时间理解的部分。我挑几个关键项说一下。

[api]区块:api.debug=1可以开启调试接口,api.secret一定要设置一个自定义密钥,这个是Web API的鉴权凭证,不设置的话任何人都能通过HTTP API控制你的流媒体服务,风险很大。默认的1000端口是HTTP API端口,建议改成一个不容易被扫描到的端口。

[rtsp]区块:port=554是RTSP默认端口,sslPort=322是RTSPS端口。如果服务器上已经有其他服务占用554,就要改掉。

[rtmp]区块:port=1935是RTMP端口,handshakeSecond=15控制握手超时时间,公网环境网络抖动大,建议调大到30秒。

[http]区块:port=80是HTTP文件服务端口,用于HLS和HTTP-FLV的对外分发。如果80被Nginx占了,改成8080之类的高位端口,后续用Nginx做反向代理和负载均衡。

[hook]区块:这个是ZLM和业务系统对接的关键,当推流、拉流、流结束等事件发生时,ZLM会向业务服务器发HTTP回调。配置文件里把所有hook地址都填好,业务系统才能感知流状态变化。

最关键的一个参数是[general]区块的mediaServerId,如果你要跑多个ZLM实例做负载均衡,这个ID必须唯一,否则集群模式下媒体流路由会错乱。

3.4 验证服务是否真正跑通

部署完成后不能只看进程活着,必须实际操作一遍推拉流流程。

先用FFmpeg推一路测试流。假设服务器IP是192.168.1.100,本机有一个MP4文件:

ffmpeg -re -stream_loop -1 -i test.mp4 -c copy -f flv rtmp://192.168.1.100:1935/live/test

推流成功后,在浏览器里打开ZLM测试页面,找到RTMP标签页,拉流地址填上rtmp://192.168.1.100/live/test,能正常播放说明RTMP链路没问题。

接着测试HTTP-FLV链路,用VLC或者浏览器直接播放http://192.168.1.100:80/live/test.flv,这这是Web直播最常用的分发格式。如果这路能通,说明ZLM的核心分发链路已经完全正常。

再测试RTSP链路,用VLC打开rtsp://192.168.1.100:554/live/test。RTSP是安防摄像头对接时最常用的协议,很多IPC摄像头默认就是RTSP输出。

注意:用ffmpeg -re -stream_loop -1推流时,-re参数控制推流速率和视频实时播放一致,如果不加这个参数,FFmpeg会以最快速度推流,测试短文件时可能瞬间就推完了,看不到流。

4. 常见问题与排查技巧实录

4.1 Docker启动ZLM后端口无法访问

这是被我碰到最多的问题。Docker容器起来了,日志也显示正常,但宿主机上访问80或者554端口就是不通。排查思路按顺序来:

第一步,确认核心服务真的在监听端口。进容器或者查看日志,ZLM启动时会打印监听地址,检查信息是否包含0.0.0.0,如果你在配置里写了127.0.0.1,容器外当然访问不到。

第二步,检查firewalld。很多CentOS 7服务器默认开着firewalld,你Docker映射的端口可能被防火墙拦截了。用firewall-cmd --list-ports看看端口有没有放行,或者直接临时关防火墙验证是不是它的锅。

第三步,检查SELinux。这个比较隐蔽,CentOS的SELinux在某些状态下会拦截容器对外网络通信,虽然Docker官方声称SELinux兼容,但实践中常常出问题。临时setenforce 0测试下,能通就是SELinux的问题。

第四步,如果是云服务器,看一下安全组。这里特别提醒,云服务商的安全组和系统防火墙是两层,两层都要放行才行,很多人只在系统防火墙开了端口,忘了安全组的规则,端口依然不通。

4.2 推流成功但播放卡顿或花屏

拉流能通但画面卡顿,大多数情况不是ZLM的问题,而是码率和带宽不匹配。ZLM本身是基于转发模型,不会做转码,推流端是什么码率,拉流端就是什么码率。如果你用4K源推流,然后在一个小水管带宽的客户端拉流,卡顿是必然的。

排查方法我建议分三步:先看CPU和内存,top命令查看进程资源占用,ZLM的CPU占用如果长期超过100%,说明机器性能扛不住这个并发;其次看网卡流量,用iftop或者nload确认出网带宽是否打满;最后看磁盘IO,如果你在录制成文件,磁盘跟不上也会卡。

在实际项目中,我更推荐一个架构方案:摄像头或采集端推流到ZLM时把码率控制在合理范围,比如1080p推流控制在2-4Mbps,720p控制在1-2Mbps。如果没有转码需求,纯粹做分发,那就在推流端约束好码率,别指望ZLM给你做重编码。

4.3 GB28181设备接入失败

ZLM很大一部分应用场景是安防国标GB28181设备接入。摄像头配置了GB28181的服务器地址,但状态一直显示未注册,这个问题的排查点相对固定。

先确认ZLM的[gb28181]配置区块,port默认是5060,要确认端口没被占用,用netstat -lntp | grep 5060查一下。

然后确认摄像头配置的SIP服务器ID和ZLM的serverId是否一致,ZLM默认的服务器ID是34020000002000000001,如果摄像头那边填了别的ID,注册就被拒绝。

再看密码,GB28181设备的密码和ZLM配置的authPwd要对得上。这类问题八成是ID、密码或者端口三项配置不匹配,逐项核对基本都能解决。

[gb28181]区块里还有个registerExpires参数,控制注册有效期,默认是3600秒,摄像头注册成功后要在这个时间范围内持续发心跳续订,如果摄像头系统和ZLM时间不同步,会出现反复注册掉线的情况,建议在摄像头端开启NTP时间同步。

4.4 WebRTC拉流黑屏或无画面

ZLM的WebRTC功能对协议栈要求比较高,最容易出问题的地方是端口和信令协商。

ZLM开启WebRTC后需要监听UDP端口做媒体传输,默认端口段在配置里是[rtc]区块的portRange=30000-30050,如果你的服务器防火墙或云安全组只放行了TCP端口,UDP端口段没放行,WebRTC的媒体流就传不过来,表现出来就是信令正常但画面黑屏。

另外,ZLM的WebRTC功能要求服务器能正确返回公网IP。如果部署在NAT后面,配置里要设置[rtc]下的externIP为服务器的公网IP地址,否则客户端无法完成ICE协商。

实际验证可以用ZLM自带的WebRTC测试页面,先用内网地址测,通了以后再从公网测,能比较快地定位问题出在哪个环节。

4.5 ZLM生产环境常用故障速查表

现象可能原因排查建议
容器一直重启配置目录权限不足、端口被占用查docker logs,检查端口占用情况
推流409错误配置了鉴权但推流URL没带token检查[general]的enableVhost和鉴权配置
拉流403错误访问控制开启,URL不合法检查[http]区块的allow_IP_range等ACL规则
日志大量播放器断连异常客户端断流太频繁检查hook回调地址是否正常响应,调大网络超时参数
HLS播放不了拉流进程没触发HLS录制确认拉的是HLS格式,检查[hls]配置的fileBufSize和segDur
容器启动慢磁盘IO差、目录挂载过多用docker system df检查磁盘占用,考虑换SSD

排查时有一个比较好的习惯:先看日志,再动配置。ZLM日志比较详细,多数问题在日志里都有明确提示。

5. 生产环境进阶与性能调优经验

5.1 ZLM + Nginx分发架构

大部分线上环境不会让ZLM直接对公网提供服务,我推荐的做法是ZLM挂在内网,再上面架一层Nginx做反向代理、负载均衡和TLS终止。

比如HTTP-FLV流都是通过80端口分发,当并发观看量上来后,单台ZLM会有瓶颈,这时可以在架构上多加几台ZLM做集群,用Nginx做流媒体负载均衡。ZLM集群的部署方式是在每台机器上配置相同的mediaServerId不行,必须不同,然后通过ZLM的hook机制对接业务系统,让业务系统知道每路流注册在哪台ZLM上。

Nginx侧配置Stream模块做TCP/UDP层的负载均衡,可以实现RTMP和RTSP流的水平扩展。但对ZLM来说,所有ZLM节点要能访问共同的流媒体来源,否则负载均衡会路由到错误节点。

stream { upstream zlm_rtmp { server 192.168.1.101:1935; server 192.168.1.102:1935; } server { listen 1935; proxy_pass zlm_rtmp; } }

这个配置能做到简单的TCP层负载均衡,但要注意的是,RTMP流需要会话保持,同一个推流客户端要一直连到同一台ZLM,否则推流中断。Nginx的hash指令可以基于客户端IP做会话保持,但对流媒体来说,建议业务层做好流注册管理,更可靠。

5.2 配置文件管理的版本化实践

ZLM的配置文件是可维护性比较强的,conf目录挂载出来后,一个推荐的做法是用Git管理config.ini的变更历史。每次调整配置前先commit一个版本,出问题能快速回滚。

我之前踩过一个大坑:为了调一个WebRTC参数,直接在服务器上改了config.ini,没备份,后来又改了一堆其他地方,结果想回退发现回不去了,只能凭记忆重新配。所以现在习惯是把配置变更都记录到Git里,顺便写清楚每次改了什么、为什么改,排查问题的时候能少大量不必要的试错。

ZLM的配置支持修改后热加载吗?部分参数支持,但保险起见,改完配置还是重启容器比较稳妥。docker-compose编排下,就是一条docker-compose restart zlmediakit的事。

5.3 监控告警与日志运维

流媒体服务属于实时性要求高的业务,它挂了你可能不会立刻知道,直到用户报障。所以监控告警是必须做的。

最简单的方案,写一个定时探测脚本,每次用ffprobe请求ZLM的流地址,验证是否有流正常输出。这里有个细节,不能用简单的HTTP请求探活,而是真的去拉流,因为ZLM进程活着不代表流是通的。用ffprobe探活脚本可以纳入Zabbix或者Prometheus定时任务。

#!/bin/bash STREAM_URL="http://192.168.1.100:80/live/test.flv" ffprobe -v error -show_entries format=format_name -of default=noprint_wrappers=1:nokey=1 -rw_timeout 5000000 "$STREAM_URL" > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "stream ok" else echo "stream down" # 发送告警,这里接入你的告警系统 fi

ZLM本身也提供了HTTP API可以查询流列表、服务器状态。调用/index/api/getMediaList可以实时查看当前有哪些流在推,调用/index/api/getServerConfig可以确认运行时配置。通了API,就可以把这些数据接入Grafana做可视化监控面板,比裸奔要靠谱得多。

日志侧优先聊一下opencv那些不需要的,聊天记录从简。ZLM的日志文件在挂载的log目录里,默认按天切割。用logrotate做系统级轮转也是常规做法。有个注意点:不要在生产环境开debug级别的日志,磁盘写入量激增是小事,关键是大量日志写入会对媒体处理线程造成干扰,性能下降明显。

5.4 容器升级与回滚策略

Docker化部署最大的优点之一就是升级回滚方便。ZLM更新版本的操作流程:

# 拉取新版镜像 docker pull zlmediakit/zlmediakit:master # 重新创建容器 docker-compose up -d --force-recreate

执行前最好备份当前配置目录,虽然config.ini通常能向前兼容,但大版本更新会出现配置项废弃,有一份备份更稳妥。

升级后不要立刻把旧镜像删掉,Docker会保留旧版本镜像层,如果想回滚,只需要在docker-compose.yml里把image标签改回旧版本,然后重新up -d即可。这就是容器化部署的价值,传统二进制升级就没这么方便了。

回滚时有另一个坑要注意:ZLM运行时会生成缓存文件,比如HLS录制的ts分片,这些文件不受docker镜像影响,因为它们放在挂载数据目录里。如果新版本有问题要回滚,建议把数据目录一并恢复到升级前状态,否则旧版本可能会读取到新版本生成的异常文件。

6. 一些基于实战的最终心得

把这个部署方案完整跑过几遍,我越发觉得ZLM值得花时间研究。C++写出来的服务性能上限高,协议覆盖广是它的底色;而Docker化部署让它的上手门槛急剧降低,运维成本也被压得很低,这两者结合,其实已经满足了一个高质量流媒体基础设施的核心要求。

我个人在操作中的一个切实体会是:ZLM文档和社区已经比较成熟,绝大多问题都有解,真正的难点反而是知道问题该往哪个方向查。比如WebRTC黑屏,很多人第一时间怀疑是ZLM的问题,实际上大部分时候是UDP端口没放行。所以排查问题,还是要回到网络层面逐层检查。

最后再分享一个小技巧:ZLM默认的config.ini里注释很完整,几乎每个参数都附了说明,强烈建议在测试环境试着改一遍所有区块的参数,把日志打到debug级别,跑一路流看看每个参数对行为和日志输出有什么影响。把配置参数摸透了,生产环境的疑难杂症就会少一半。这套基于CentOS + Docker的方案,足够支撑从个人实验到中等规模生产环境的流媒体需求,也值得纳入你后续的微服务架构中作为媒体能力的基础设施。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询