简介:面向需要在离线、内网或等保隔离环境中部署ZLMediaKit流媒体服务的运维与开发人员,这里提供了一份以Docker方式打包的zlm完整运行环境,可免去手动编译和依赖配置带来的不确定性。压缩包共2个文件,tar归档文件承载docker镜像,sh脚本负责自动化安装,整体约208.73MB,体积精简,拷贝到目标机器即可开始部署。目前已有375人学习下载,适合熟悉容器基础、希望快速完成zlm上线的工程技术人员。借助这份资源,使用者可以绕开镜像仓库不可达的障碍,直接获得离线镜像和脚本化安装流程;镜像内已包含必要组件,脚本进一步降低了指令输入错误的风险,部署过程更稳妥、更高效。资源包结构紧凑,文件类型清晰,便于在项目交付或生产变更时留档备查。
1. zlm docker 离线安装:真正要解决的并不是 Docker 命令,而是镜像从哪里来
zlm docker 离线安装是内网部署流媒体服务时绕不开的一步。ZLM(ZLMediaKit)是高性能开源流媒体服务器,支持 RTSP、RTMP、HLS、HTTP-FLV 和 WebRTC,很多安防项目、视频中台、直播系统都拿它做接入和分发。内网交付和别的场景不同:现场服务器不连外网,不能直接 docker pull,也不能指望在部署时临时下载依赖。所以离线安装的核心问题只有一个——把镜像在能联网的机器上准备好,再以文件形式带进内网,最后用 docker load 跑起来。这篇文章按一线交付的顺序拆解整个流程:镜像怎么拉、怎么导出、端口怎么映射、配置怎么改、怎么验证,以及我踩过的五个典型坑。适合要去做内网项目交付的运维同事,也适合刚接触 ZLM、想在隔离环境里复现的团队。
2. 离线镜像制作:在一台联网机器上完成拉取、导出与架构核对
2.1 拉取镜像并锁定版本:不要默认去拉 latest
离线部署的起点是有一台能访问 Docker Hub 的机器。常见做法是在这台机器上把 zlmediakit/zlmediakit 镜像拉下来,保存成 tar 包,再拷贝到内网。这里第一个忠告就是:不要顺手拉 latest。ZLM 迭代速度很快,latest 标签在不同时间拉下来的内容可能完全不同,配置文件字段、可执行程序、Web 页面都会变。今天你在联网机器上拉 latest 做出来的包,到三个月后现场再用,镜像本身没问题,但和你之前调试时用的版本可能已经对不上了。
我一般会先到 Docker Hub 确认 zlmediakit/zlmediakit 有哪些标签,固定一个明确的 tag。比如官方长期维护的 master 标签,它在 Docker Hub 上会持续构建,但比 latest 要可预期。如果你们项目有内部统一版本,建议以这个为准。
# 在能上网的机器上执行 docker pull zlmediakit/zlmediakit:master # 查看镜像基本信息,记录架构和版本字段 docker image inspect zlmediakit/zlmediakit:master | grep -E '"Architecture"|"Os"|"Created"'这里解释一下关键参数:zlmediakit/zlmediakit是官方镜像名,master是标签。docker image inspect输出的Architecture和Os会在后面做架构核对时用到,Created可以辅助判断镜像构建时间。很多人在这个阶段漏掉记录,到了内网才发现镜像架构不对,最后只能重新折腾一遍。
2.2 导出与压缩:用 docker save 把镜像变成单个文件
镜像拉下来后,下一步是导出。docker save会把镜像的所有层打包成一个 tar 文件,可以用-o指定输出路径。如果你在内网服务器上只有一块 40G 的系统盘,就得先算好 tar 包体积,避免拷贝过去解压时把 docker 的存储目录撑爆。
# 导出镜像到当前目录 docker save zlmediakit/zlmediakit:master -o zlm-master.tar # 看一眼文件大小 ls -lh zlm-master.tar # 计算 sha256,后面传输完要核对 sha256sum zlm-master.tar | tee zlm-master.tar.sha256这个 tar 文件包含完整的镜像层信息,拷到内网后可以直接docker load。如果文件太大,建议用 gzip 压缩一次,实测 ZLM 镜像压缩后体积能明显减小。还有一个更省事的办法:docker load 本身支持 gzip,压缩后的 tar.gz 不需要解压,直接 load 即可。
# 压缩并生成校验值 gzip -k zlm-master.tar sha256sum zlm-master.tar.gz | tee zlm-master.tar.gz.sha256参数说明:-k保留原始 tar 文件,防止压缩后原文件被删掉;sha256 文件要和 tar 包放在一起传,到了内网第一件事就是先核对校验值。这一步虽然简单,但很多线上事故都是因为传输中途文件损坏,等到 docker load 报错才回头检查。
2.3 架构核对:x86 和 ARM 的镜像千万别当成同一个包
内网服务器常见两类 CPU 架构:x86_64 和 ARM64。Docker 拉镜像时默认拉取与当前机器架构匹配的镜像。如果你在 x86 电脑上 save 出来的镜像,拷贝到 ARM 开发板上 load,启动时会直接报可执行文件格式错误。这个错误在 docker images 阶段完全看不出来,只有运行容器时才暴露。
所以导出前务必确认现场机器的架构。最稳妥的做法是先在目标机器上执行uname -m,拿到确认的架构后,回到联网机器用--platform拉取对应架构的镜像。
# 在内网机器上确认 CPU 架构 uname -m # 在联网机器上拉取 ARM64 架构的镜像(假设现场是 ARM) docker pull --platform linux/arm64 zlmediakit/zlmediakit:master # 再次确认镜像架构,避免拉错 docker image inspect zlmediakit/zlmediakit:master | grep -E '"Architecture"|"Os"'需要注意:--platform拉取的前提是镜像仓库里有对应架构的镜像。如果现场机器不是常见的 amd64 或 arm64,而是小众架构,最好在同类架构的机器上先试跑一次,不要等到交付现场再试。我自己的习惯是,任何离线交付任务,先从现场拿到uname -m结果再开始做包。
3. 内网导入与容器启动:从 tar 包到正式运行的 ZLM 服务
3.1 docker load 导入镜像:先校验再导入
拿到 tar 包后,第一步不是急着 load,而是先核对 sha256。常见做法是把传输前记录的 sha256 值与现场文件重新计算的结果对比,一致再导入。如果校验值对不上,说明文件在拷贝过程中损坏,重传一次最保险,别强行导入。
# 在内网机器上执行,重新计算文件的 sha256 sha256sum -c zlm-master.tar.gz.sha256 # 校验通过后导入镜像 docker load -i zlm-master.tar.gz # 确认镜像列表里有 zlmediakit docker images | grep zlmediakitsha256sum 命令的-c参数表示从文件读取期望校验值并自动比对。导入成功后,docker images 输出会显示zlmediakit/zlmediakit和对应的 tag,如果看到REPOSITORY一列为空,说明 load 出来的是 untagged 镜像,需要用docker tag补上一个标签。这种情况通常是因为原镜像没有完整的仓库名,不影响运行,但会影响后续管理。
3.2 启动容器:端口映射是 ZLM 可用性的关键
ZLM 工作时要监听多个端口,启动容器时这些端口必须都映射到宿主机。下面是我在一个内网交付里实际用过的启动命令,端口分配和注释都标清楚了。
# 创建宿主机配置目录 mkdir -p /data/zlm/conf # 启动 zlm 容器,映射主要协议端口 docker run -d --name zlm \ --restart=always \ -p 1935:1935 \ -p 554:554 \ -p 8080:80 \ -p 10000:10000/udp \ -p 30000-30500:30000-30500/udp \ -v /data/zlm/conf:/opt/media/conf \ zlmediakit/zlmediakit:master挑几个容易理解错的参数说明一下:-p 1935:1935是 RTMP 推拉流端口,-p 554:554是 RTSP 端口,-p 8080:80把容器内 HTTP 端口映射到宿主机 8080,用于 HTTP-FLV、Web 管理页面和 API 调用。10000/udp是 RTP 接收端口,30000-30500/udp是 RTP 代理端口范围,如果涉及 RTSP 的 UDP 传输,这两个映射缺一不可。-v /data/zlm/conf:/opt/media/conf把配置目录挂载到宿主机,这是后面改配置的基础。
--restart=always很重要。内网服务器重启后,容器能自动拉起,否则一次断电就可能让整个流媒体服务瘫在那里,你还要找现场的人帮忙手动启动,那很被动。
3.3 配置文件挂载:让容器里的 config.ini 不再是黑匣子
容器启动后,ZLM 会在/opt/media/conf下自动生成默认配置文件config.ini。因为我们挂载了宿主机目录,可以从宿主机直接看到并修改它。如果启动后这个目录是空的,先看容器日志,确认 MediaServer 是否成功运行。
# 查看容器启动日志,确认没有异常退出 docker logs zlm 2>&1 | tail -n 30 # 等待几秒后,列出挂载目录内容 ls -l /data/zlm/conf正常情况下,/data/zlm/conf下会出现config.ini。这时我建议立刻把这文件备份一份,比如cp config.ini config.ini.default,后面改乱了还能恢复。如果目录一直为空,可以从容器里手工拷贝默认配置出来:
# 如果自动生成失败,手动从容器拷贝配置 docker cp zlm:/opt/media/conf/config.ini /data/zlm/conf/config.ini # 重启容器让配置生效 docker restart zlm这里有个容易翻车的点:挂载路径必须是/opt/media/conf,不是/opt/media/conf/config.ini。我有一次把-v /data/zlm/config.ini:/opt/media/conf/config.ini直接挂成单文件,启动后容器内目录被文件覆盖,ZLM 直接找不到配置,日志里全是配置加载失败。正确做法是把宿主机目录和容器目录建立绑定,配置文件放到目录里维护。
4. 配置、推流与 API 验证:确认 ZLM 真的在为你工作
4.1 第一次推流前至少要改的两个配置
ZLM 默认配置可以跑起来,但内网环境建议至少改两处。第一处是[api]段的secret,这是 HTTP API 的访问密钥。默认是空的,内网里其他机器可以直接访问/index/api/接口,存在风险。第二处是[http]段的端口,容器内默认 80,我们启动时已经映射到宿主 8080,所以不用改。如果你现场 8080 被其他服务占用,改宿主机映射即可,不用动容器内配置。
用编辑器打开/data/zlm/conf/config.ini,找到[api]段,设置一个足够复杂的密钥:
[api] secret=myPrivateSecret123保存后重启容器:
docker restart zlm注意,重启后容器内进程会重新加载配置。如果改错了配置项,MediaServer 可能起不来,此时用docker logs zlm查看日志。日志中会明确提示哪一行配置解析失败,按提示修正即可。
4.2 用 ffmpeg 推流,再用 ffplay 拉流验证
配置改好后,整个链路是否真的可用,最简单的方式是用 ffmpeg 推一路流,再用 ffplay 去拉。先在现场服务器或同网段的机器上准备一个测试视频文件。
# 推流端:把 test.mp4 通过 RTMP 推到 ZLM ffmpeg -re -stream_loop -1 -i test.mp4 \ -vcodec copy -acodec aac \ -f flv rtmp://192.168.1.10:1935/live/test参数说明:-re按文件原始帧率读取,模拟实时流;-stream_loop -1无限循环推流;-vcodec copy -acodec aac表示视频流直接拷贝不转码,音频压成 aac,这样 CPU 占用低。推流地址中的192.168.1.10是 ZLM 所在宿主机的局域网 IP,live是应用名,test是流名,这两段可以自己定。
推流命令执行后,在另一个终端拉流验证:
# 拉流端:用 RTMP 播放验证 ffplay -fflags nobuffer rtmp://192.168.1.10:1935/live/test-fflags nobuffer可以降低播放延迟,适合快速验证。能出画面,说明 RTMP 推拉流链路没有问题。再试一下 RTSP 协议:
# 用 RTSP 协议拉流验证 ffplay rtsp://192.168.1.10:554/live/test这一步很关键,因为有些现场只允许用 RTSP 接流,如果 RTMP 正常但 RTSP 不通,问题多半出在 UDP 端口映射上。
4.3 用 HTTP API 检查流列表和服务器状态
ZLM 内置了 HTTP API,不用额外装监控插件。推流成功后,调用getMediaList接口能看到当前在线流列表,这是判断服务是否健康的直接依据。
# 查看当前在线流列表(注意带上 secret) curl "http://192.168.1.10:8080/index/api/getMediaList?secret=myPrivateSecret123"返回的 JSON 里会包含流的app、stream、online等字段。如果返回空列表,说明推流没上来或者推流配置有问题。再进一步检查服务器配置是否正常被 API 读取:
# 读取服务器配置,验证 API 鉴权是否生效 curl "http://192.168.1.10:8080/index/api/getServerConfig?secret=myPrivateSecret123" | head -c 500这个命令只为验证两件事:HTTP 服务正常监听 8080,secret 鉴权生效。如果返回401或403,说明密钥不对;如果连接超时,说明端口映射或防火墙有问题,回到第 3 章排查。
5. 避坑与常见问题:五个让我熬夜的 ZLM 离线部署坑
5.1 现象:容器启动立刻退出,报错 exec format error
第一次在 ARM 开发板上做 ZLM 离线安装时,我按 x86 机器上的镜像直接 load,结果docker run拉起来后容器秒退,docker logs里写着exec format error。当时第一反应是镜像损坏,但重新导入还是报同样的错。
原因其实很简单:镜像架构和宿主机架构不匹配。docker load只负责把镜像放进本地镜像仓库,它不校验宿主机能不能运行,真正执行的时候操作系统才发现 ELF 文件格式不对。
解决方法是回到联网机器,用--platform拉取目标架构镜像,导出后再来一次。同时我每次都会在导出前把docker image inspect输出的架构信息写进交付文档,现场导入前先对照一次,省得白跑一趟。
5.2 现象:容器状态正常,但宿主机 8080 端口怎么都不通
有一次在客户机房,docker ps显示 zlm 容器运行中,但外部访问 8080 一直超时。我先在宿主机本机访问curl http://127.0.0.1:8080/index/api/getMediaList也是超时,说明问题不在防火墙,而在容器或端口映射。
原因后来找到:我启动容器时把-p 8080:80写成了-p 8080:8080,容器内 ZLM 监听的是 80,于是映射端口和实际监听端口错位。还有一次是宿主机上 nginx 占用了 8080,导致 docker 启动时写映射成功但请求被 nginx 吃掉。
解决时先看监听端口:ss -tlnp | grep 8080,确认监听进程是谁;再用docker logs zlm看 MediaServer 实际监听端口;最后用curl http://127.0.0.1:8080/在宿主机上做本地探测,逐步缩小范围。
5.3 现象:改好的 config.ini 重启容器后变成默认配置
这是配置挂载最容易踩的坑。我在一次交付中把宿主机配置文件直接挂在/opt/media/conf/config.ini单文件上,结果容器启动后 ZLM 重新生成了默认配置,我改的密钥全部丢失,API 访问全都 401。
原因就是挂载点不对。ZLM 的配置目录是/opt/media/conf,容器启动时如果发现目录下没有 config.ini,会自动生成一个默认配置。单文件挂载会让容器把这个文件当成整个目录,导致默认配置写不进去。
解决方法是始终挂载目录,也就是-v /data/zlm/conf:/opt/media/conf,而不是挂文件。确认方式也很简单:重启容器后执行docker exec zlm ls -l /opt/media/conf/config.ini,看到文件存在且修改时间是对应的,就说明挂载正确。
5.4 现象:tar 包传到内网后 docker load 报 invalid tar header
有次交付时,现场工程师用 U 盘从 Windows 机器拷贝 tar 包,docker load 直接报archive/tar: invalid tar header。我一开始怀疑是导出过程有问题,但重新导出验证 sha256 又对不上现场文件的哈希。
原因基本可以锁定在传输环节。U 盘拷贝、FTP 传文件、微信传文件都可能破坏二进制完整性,尤其 FTP 如果配置成 ASCII 模式,tar 包必坏。docker load 对 tar 结构严格校验,任何截断都会报这个错。
解决方式就是回头检查 sha256。我在第 2 章就强调过,sha256 文件必须和 tar 包一起传输,现场先跑sha256sum -c,不一致就直接重传,别在坏文件上浪费时间。另外,gzip压缩过的包还可以用gzip -t测试完整性:
# 压缩包完整性质检 gzip -t zlm-master.tar.gz && echo "ok"5.5 现象:RTMP 推流正常,但 RTSP 拉流一直黑屏
一个现场项目里,ffmpeg 推 RTMP 很顺利,但用 RTSP 拉同一路流,客户端始终黑屏或一直转圈。排查应用配置、鉴权、防火墙之后,问题落在 UDP 端口映射上。
RTSP 拉流走 UDP 传输时,RTP 数据需要 ZLM 监听 UDP 端口。我启动容器时只映射了10000/udp,漏掉了30000-30500/udp这个 UDP 端口段,导致 RTP 包送不进来。
解决方式就是在第 3 章的启动命令里补上端口段,并确保防火墙放行:
# 检查防火墙是否有放行这两段端口 firewall-cmd --list-ports # 临时放行 UDP 端口段 firewall-cmd --add-port=30000-30500/udp --permanent firewall-cmd --reload从那之后,我每次做 ZLM 离线部署都会把10000/udp和30000-30500/udp写进部署文档,再也不用现场临时查端口。
6. 让离线安装变得可复现:版本锁定、一键自检与部署留痕
6.1 一键自检脚本:把验证步骤固化到脚本里
第 4 章的验证步骤虽然清晰,但现场不可能每次都手敲。我通常会把检查写成一个 shell 脚本,放在服务器上,交付时直接执行。脚本内容不复杂,但覆盖了进程、端口、API、流列表四个维度。
#!/bin/bash # ZLM 离线部署自检脚本 echo "1. docker 服务状态" systemctl is-active docker echo "2. zlm 容器状态" docker ps --filter name=zlm --format '{{.Status}}' echo "3. 关键端口监听" ss -tlnp | grep -E ':(1935|554|8080)\b' || echo "端口未监听" echo "4. HTTP API 可用性" curl -s http://127.0.0.1:8080/index/api/getServerConfig?secret=myPrivateSecret123 | head -c 300 echo "5. 当前在线流列表" curl -s http://127.0.0.1:8080/index/api/getMediaList?secret=myPrivateSecret123 | head -c 500这个脚本用法很简单:bash check_zlm.sh,每个步骤都输出明确结果。如果看到docker 服务未启动,直接systemctl start docker;如果看到容器状态为空,先用docker logs zlm查原因;如果端口未监听,回到端口映射检查。脚本里的 secret 要和配置保持一致,否则 API 那几步会返回鉴权错误,这也是一种验证配置一致性的手段。
6.2 部署留痕:把版本号、架构、端口写进交付记录
离线部署最大的痛点不是第一次装,而是半年后现场出问题,没人记得当时装的是哪个版本、改了哪些配置。我现在做任何内网交付,都会顺手生成一份部署记录,跟着镜像包一起留在服务器上。记录内容固定几项:镜像 tag 和 digest、宿主机架构、容器端口映射、config.ini 的 secret、zlm 容器启动命令、自检脚本执行结果。
也有同事嫌麻烦,觉得现场能跑就行。我的回答是:离线包不像在线包,出了问题没法当场 docker pull 重新拉一个,所有排障都依赖历史记录。有一次我在远程处理现场问题,现场只有一位基础很弱的同事,我让他执行记录里的自检脚本,再照着文档逐项报结果,半小时就把端口映射问题定位了。如果没有那页记录,远程排查会非常痛苦。
从那以后,我每次做完离线安装都会强制自己走一遍这个流程:写记录、跑自检、备份 config.ini。希望帮到你。
本文还有配套的精品资源,点击获取