
做国标摄像头接入这一块也有五六年了最头疼的事就是一套流程搭下来平台、流媒体、录像、反代各管各的文档还东一榔头西一棒子。这次把WVP-PRO加ZLMediaKit加录像再加Nginx反代整套用Docker Compose串起来全程不说废话直接照着抄就能跑通。适合园区安防、养殖场、仓库监控这种需要国标GB28181摄像头统一接入、浏览器实时预览和录像回放的场景也适合第一次接触WVP的集成商和运维朋友。先说清楚这套东西是干嘛的。WVP-PRO是个国标GB28181信令平台负责摄像头通过SIP协议注册上来、设备列表管理、录像计划下发简单说就是“管设备和信令”的大脑。ZLMediaKit下面统一叫ZLM是流媒体服务器负责收摄像头推过来的RTP流转成RTMP、HLS、WebRTC这些浏览器能直接播放的格式同时负责把流落盘成录像文件。Nginx在这套里面做统一入口外面只开80和443把WVP的web界面、ZLM的流地址、WebSocket全部反代进去省得给客户开一堆乱七八糟的端口。1. 整体部署思路与组件分工1.1 为什么要用Docker Compose而不是手动部署WVP-PRO后端是Java Spring Boot依赖MySQL和RedisZLM是C写的依赖一堆编译环境和库文件手动部署光装环境就得折腾半天。CentOS上装个OpenSSL版本不对编译ZLM就能把你劝退。用Docker Compose的好处是镜像拉下来就是完整运行环境Compose文件定义好服务之间的依赖关系一条docker compose up -d全部搞定。这套Compose编排里一共五个服务MySQL存业务数据、Redis做缓存、WVP-PRO跑平台、ZLM跑流媒体、Nginx做反向代理。WVP和ZLM是核心MySQL和Redis是WVP的依赖Nginx是额外的入口层。生产环境不要图省事把WVP直接暴露公网加Nginx这一层能统一做HTTPS证书、WebSocket升级、访问日志后面接域名也方便。1.2 组件之间的协作关系和数据流理解这套系统最关键的是搞清楚三条链路。信令链路摄像头通过GB28181协议用SIP报文向WVP的SIP端口默认18080发起注册WVP校验国标ID和密码后把设备信息写入MySQL通过Redis缓存设备状态。我们通过WVP的web界面给设备下发指令比如邀请推流WVP把指令转成SIP信令发给摄像头。媒体链路WVP告诉摄像头往ZLM的RTP接收端口推流ZLM收到RTP流后根据流的app和streamId做转封装把PS流转成RTMP/HLS/WebRTC格式。前端播放器通过web界面请求WVP拿播放地址WVP向ZLM发起拉流代理或直接给流地址播放器从ZLM拉流。录像链路ZLM按照流ID和日期把收到的流写成mp4文件落盘到挂载的volume目录。WVP通过ZLM的RESTful API查询录像文件列表web界面展示给用户点播回放。这三条链路一个都不能断排查问题的时候先判断是信令问题还是媒体问题方向对了效率高得多。2. 部署前的准备工作和目录规划2.1 服务器要求和系统初始化建议至少2核4G内存磁盘看录像需求这个系统本身占不了多少空间但是录像文件非常吃盘。一天24小时不间断录像单路按2Mbps码率算一天约21GB记住这个公式录像容量 路数 x 码率 / 8 x 3600 x 24。比如20路摄像头、2Mbps码率一天就是432GB老老实实挂数据盘。系统建议Ubuntu 22.04或者CentOS 7.9以上Docker版本不低于20.10Compose用v2插件。有些老系统内核版本低跑ZLM的容器可能遇到内核模块问题Ubuntu 22.04实测最稳。防火墙要放行的端口后面专门讲先把SELinux关了CentOS或者确认没有额外防火墙拦截。2.2 安装Docker和Compose插件Docker官方源在国内可能慢这里直接配置清华镜像源。apt方式装完Docker后把镜像加速写入daemon.json这里不说怎么找加速地址阿里云容器镜像服务控制台有专属加速地址申请一个填进去就行。# Ubuntu/Debian curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo deb [archamd64 signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin # 配置镜像加速 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json -EOF { registry-mirrors: [https://你的专属加速地址] } EOF sudo systemctl daemon-reload sudo systemctl restart dockerCentOS那边把apt换成yumgpgkey路径换成CentOS的其他一致。验证安装用docker compose version看到v2版本号就行。2.3 目录结构设计和配置文件规划部署之前先把目录规划好否则后面配置文件挂载、录像目录、日志目录全部乱套。我的习惯是/data/wvp下面按服务分目录一个项目一个文件夹方便迁移和备份。/data/wvp ├── docker-compose.yml ├── mysql │ └── my.cnf ├── wvp │ └── application.yml ├── zlm │ └── config.ini ├── nginx │ ├── nginx.conf │ └── conf.d │ └── wvp.conf └── storage └── zlm-recordMySQL配置放一卷数据目录放一卷WVP的application.yml放一卷ZLM的config.ini放一卷录像目录单独一卷。这样做的好处是升级镜像的时候配置不丢备份只需要打包这几个目录。坏处是配置文件不能乱改改错了整个服务起不来所以每改一个配置之前先备份。3. 编写Docker Compose编排文件和核心配置3.1 docker-compose.yml完整内容拆解先看完整的Compose文件然后逐段解释关键参数。注意ZLM的镜像有两个官方zlmediakit/zlmediakit和WVP作者封装的648540858/wvp_pro我这里都用官方镜像减少第三方打包的不确定性。ZLM官方镜像默认不带ffmpeg如果需要录像转码、HLS切片这些功能可能要自己装后面说。version: 3.8 services: mysql: image: mysql:8.0 container_name: wvp-mysql restart: always environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: Wvp2024 MYSQL_DATABASE: wvp command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci --default-authentication-pluginmysql_native_password --lower_case_table_names1 volumes: - /data/wvp/mysql/data:/var/lib/mysql - /data/wvp/mysql/conf:/etc/mysql/conf.d healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -pWvp2024] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.0 container_name: wvp-redis restart: always environment: TZ: Asia/Shanghai command: redis-server --requirepass Wvp2024 --appendonly yes volumes: - /data/wvp/redis/data:/data healthcheck: test: [CMD, redis-cli, -a, Wvp2024, ping] interval: 10s timeout: 5s retries: 5 zlm: image: zlmediakit/zlmediakit:master container_name: wvp-zlm restart: always network_mode: host environment: TZ: Asia/Shanghai volumes: - /data/wvp/zlm/config.ini:/opt/media/conf/config.ini - /data/wvp/storage/zlm-record:/opt/media/bin/www/record depends_on: - wvp wvp: image: 648540858/wvp_pro:latest container_name: wvp-pro restart: always environment: TZ: Asia/Shanghai volumes: - /data/wvp/wvp/application.yml:/opt/wvp-pro/application.yml - /data/wvp/wvp/logs:/opt/wvp-pro/logs ports: - 8080:8080 - 18080:18080/tcp - 18080:18080/udp depends_on: mysql: condition: service_healthy redis: condition: service_healthy healthcheck: test: [CMD, curl, -f, http://localhost:8080] interval: 30s timeout: 5s retries: 5 nginx: image: nginx:1.25 container_name: wvp-nginx restart: always environment: TZ: Asia/Shanghai ports: - 80:80 - 443:443 volumes: - /data/wvp/nginx/nginx.conf:/etc/nginx/nginx.conf - /data/wvp/nginx/conf.d:/etc/nginx/conf.d - /data/wvp/nginx/cert:/etc/nginx/cert - /data/wvp/storage/zlm-record:/var/www/record:ro depends_on: - wvp - zlm这里一个关键决策是ZLM用network_mode: host而不是端口映射。ZLM做RTP收流的时候需要监听30000到30500这一段UDP端口做端口复用如果用bridge模式得把整个端口段都映射出来Docker的iptables规则会变得非常臃肿而且ZLM回源地址会有问题。host模式直接共享宿主机网络栈性能最好端口也不用映射。缺点是端口冲突风险高8080、1935、10000这些端口宿主机不能被占用。3.2 WVP的application.yml核心配置项WVP镜像内自带的application.yml是默认配置我们需要把它复制出来改成实际环境。先创建空文件然后填入以下内容。这个配置文件是WVP运行的灵魂数据库连接、Redis密码、SIP参数、ZLM对接全部在这里。spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://mysql:3306/wvp?useUnicodetruecharacterEncodingUTF8useSSLfalseserverTimezoneAsia/Shanghai username: root password: Wvp2024 druid: initial-size: 8 min-idle: 8 max-active: 20 redis: host: redis port: 6379 password: Wvp2024 database: 0 timeout: 3000 server: port: 8080 servlet: context-path: / sip: ip: 你的服务器内网IP port: 18080 domain: 3402000000 id: 34020000002000000001 password: 12345678 media: id: ZLMediaKit secret: admin123 ip: 你的服务器内网IP port: 10000 rtmp: port: 1935 rtsp: port: 10554 rtc: port: 8000 hook: port: 8081 streamPort: 30000-30500 record: enable: true path: /opt/media/bin/www/record zlm: api: http://你的服务器内网IP:8081/index/api每个参数都解释一下为什么这么配。sip.ip和media.ip要填宿主机内网IP因为ZLM是host模式回调地址必须是宿主机可达地址。media.port 10000是ZLM的HTTP API端口WVP通过这个端口调用ZLM的RESTful接口做推流控制media.hook.port 8081是ZLM的hook回调端口ZLM收到流、录像文件生成之后通过这个端口通知WVP。这两个端口不是公网服务端口是内部通信用的后面Nginx配置的时候不能漏掉。media.zlm.api这个地址专门给WVP调ZLM的RESTful接口用的host模式下ZLM运行在宿主机网络上所以用服务器内网IP加8081端口。secret要和我们后面改的ZLM config.ini里的api.secret保持一致否则WVP调ZLM接口会认证失败界面上看到的就是获取流地址报401。3.3 ZLM的config.ini改动点ZLM的config.ini是流媒体服务器的核心配置内容很长值得改的就几个地方其他的保持默认不要乱动。把默认的config.ini复制到宿主机/data/wvp/zlm/config.ini然后修改以下内容。; API接口配置 [api] apiDebug1 secretadmin123 ; HTTP服务器配置ZLM内置的HTTP服务播放HLS、拉流都用这个 [http] port8081 sslport0 notifyOnStreamChanged1 ; RTSP服务器配置 [rtsp] port10554 ; RTP代理服务器配置 [rtp_proxy] port10000 timeoutSec15 ; 录像相关配置 [record] appNamerecord mp4AsPlayer0 mp4MaxSecond3600 fastStart1 [general] enableVhost0 flowThreshold1024 maxStreamWaitMS15000http.port默认是80必须改成8081不然和WVP的8080端口不冲突但会和宿主机其他服务抢80端口而且WVP里media.hook.port配置的就是8081两边必须对齐。secret设置为admin123和WVP的media.secret一致。rtsp.port默认554改成10554因为非root进程不能用1024以下端口Docker容器虽然可以用但host模式下容器内进程的权限受容器限制。实际上ZLM镜像内以非root运行绑定554会失败。rtp_proxy.port 10000是ZLM接收RTP流时的回调端口这个端口是TCP/UDP都用。录像配置里mp4MaxSecond 3600表示每小时切一个文件方便检索不要设太大否则一天一个文件回放的时候拖动进度条要卡半天。4. Nginx反代配置详解4.1 为什么必须加反代层WVP的web界面走8080端口播放器拉流走ZLM的1935、8081、10000SSH到服务器管理还要开22如果这些端口全部裸奔暴露公网安全审计直接不合格。Nginx反代的思路是对外只暴露80和443通过不同的路径转发到不同服务。具体来说/代表WVP的web界面/record/映射到录像文件目录可以HTTP直接下载/rtp/和/rtsp/这类流路径转发到ZLM。另外WVP界面是一个前后端分离的SPA应用浏览器和WVP后端之间有WebSocket长连接Nginx必须要配置WebSocket升级否则界面上设备状态一直离线、播放画面黑屏。还有一个细节录像回放功能里WVP返回给播放器的地址如果是内网IP浏览器访问不到必须在WVP的配置里把对外访问地址改成域名同时Nginx把流路径代理出去。这一层不做功能全通但体验就是播放器转几秒就断。4.2 Nginx配置文件和服务配置先写主配置文件这个文件缺省配置其实能用但有几个全局参数要调一下。user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; map $http_upgrade $connection_upgrade { default upgrade; close; } include /etc/nginx/conf.d/*.conf; }map那块是WebSocket升级的关键没有这段配置WVP界面的实时状态推流过不来。然后是站点配置文件server { listen 80; server_name wvp.example.com; client_max_body_size 100m; # WVP-PRO web界面 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; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; } # ZLM流媒体服务 location /rtp/ { proxy_pass http://127.0.0.1:8081/rtp/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /record/ { proxy_pass http://127.0.0.1:8081/record/; proxy_http_version 1.1; proxy_set_header Host $host; } # HLS播放路径 location /hls/ { proxy_pass http://127.0.0.1:8081/hls/; proxy_http_version 1.1; proxy_set_header Host $host; } }注意proxy_pass后面带不带/有讲究。/rtp/到 /rtp/ 是完整路径映射/record/同样。如果漏了后面的路径会404。proxy_read_timeout 3600s是给WebSocket用的默认60秒超时超过这个时间界面上的在线状态就不刷新了。如果要用HTTPS加一个443的server块证书放到/data/wvp/nginx/cert目录关键配置就是加ssl on、ssl_certificate、ssl_certificate_key然后location里把80端口跳转到443。不想买证书可以用acme.sh申请免费的这里不展开。4.3 反代层常见端口冲突排查Nginx反代最常见的坑是WVP和Nginx装同一台机器WVP的8080和ZLM的8081都是可以对外服务的但Nginx本身监听80三者端口不冲突前提是宿主机上没有其他服务占用这些端口。用ss -lntp可以检查端口占用情况。另一个坑是WVP的application.yml里配置的media.zlm.api地址是内网IP加8081Nginx反代外网请求到8081没问题但是WVP内部调用ZLM接口走的是内网IP直连如果服务器的防火墙把8081内网访问也拦了WVP控制台会显示流媒体服务离线。所以防火墙规则要同时允许内网访问8081。5. 启动部署、初始化验证和设备接入5.1 启动服务和初始化数据库所有配置文件准备好之后在/data/wvp目录下执行docker compose up -d启动过程中用docker compose logs -f wvp观察日志。第一次启动WVP会自动创建数据库表结构看到如下日志说明数据库初始化完成Servlet WebApplicationContext: initialization completed in xxx ms Tomcat started on port(s): 8080如果WVP一直报数据库连接失败多半是MySQL健康检查没过先看mysql容器状态docker compose ps确认healthy再查WVP日志里的具体报错。因为WVP的depends_on配了condition: service_healthyMySQL没起来之前WVP容器会一直重启。数据库初始化完成后浏览器访问http://服务器IP:8080默认账号admin/admin第一次登录会强制修改密码。登录后进入“系统管理”里的“流媒体服务”页面检查ZLM的状态是否在线在线的话会显示ZLM的版本号和服务器IP离线的话按下面的常见问题排查。5.2 国标设备接入和验证推流WVP平台起来之后接入摄像头的流程是在设备管理里添加国标设备填设备的国标编号20位数字填写设备密码要和摄像头里的密码一致然后在摄像头侧配置SIP服务器地址为WVP服务器IP端口18080。摄像头一般支持注册有效期、心跳周期这些参数默认即可。设备注册上来之后WVP的设备管理列表里状态会从前面的灰色变成绿色。然后点“通道”页签选择视频通道点“播放”按钮会提示播放成功并弹出视频画面。这里如果播放失败判断思路是先看ZLM的日志有没有收到RTP流再确认WVP有没有给ZLM发play命令。ZLM日志里出现invalid rtsp url说明流地址构造有问题往往是host模式下IP配置不对。验证ZLM收流是否成功可以直接调RESTful API去查浏览器访问 http://服务器IP:8081/index/api/getMediaList?secretadmin123返回JSON里能看到当前活跃的流列表。如果这个接口返回401就是secret配置不一致改完config.ini要重启ZLM容器。5.3 录像计划配置和录像文件验证ZLM默认不会给所有流都录像需要在WVP的“录像计划”里配置。打开WVP管理界面进入“设备管理”找到对应的通道点击“录像计划”标签新建一个计划录像类型选“全时录像”时间选择00:00到23:59确定之后WVP会下发录像指令给ZLM。ZLM收到指令后对应该通道的流开始落盘。录像文件默认写到/data/wvp/storage/zlm-record目录目录结构是app/streamId/YYYY-MM-DD/HH-mm-ss.mp4。到目录下确认有文件生成然后回到WVP的web界面在“设备管理-通道-录像”页签选择日期能列出录像片段就可以点击回放了。如果列表是空的但目录里有文件说明WVP查录像用的appName和ZLM写录像的appName对不上把config.ini里record.appName和WVP的media.record.path对齐就行。6. 实战踩坑记录和排查手册6.1 排错速查表现象可能原因排查命令/操作WVP界面流媒体服务离线media.zlm.api地址不可达或secret错误curl http://127.0.0.1:8081/index/api/getServerConfig?secretadmin123摄像头注册不上SIP端口未放通或国标ID、密码错误抓包确认18080端口的SIP报文播放视频黑屏ZLM未收到RTP流检查ZLM日志看rtp_proxy端口区间是否被占用有视频无音频摄像头音频编码格式不兼容摄像头配置改为AAC编码录像列表为空但有文件appName或录像时间格式不对比对ZLM config.ini的record.appName和WVP配置播放器一直转圈WebSocket反代没配置upgrade看浏览器F12控制台有无WebSocket连接报错HLS播放不了Nginx的/hls/路径没配置缓存头检查proxy_pass路径是否带末尾/端口冲突导致容器重启宿主机端口被占用ss -lntp | grep 端口改宿主机服务或改映射端口6.2 端口放通清单防火墙这一块最容易被忽略。很多服务器商的安全组默认只放通22、80、443WVP部署完本地访问正常外网访问就是不通。整理一下需要放通的端口端口协议用途80/443TCPNginx入口必开8080TCPWVP web界面如果通过Nginx反代可不对外开18080TCP/UDPGB28181 SIP信令端口必开8081TCPZLM HTTP API和HLS/流媒体服务通过反代可不对外开1935TCPRTMP流如不对外播放可不对外开10554TCPRTSP流如不对外播放可不对外开30000-30500TCP/UDPZLM RTP收流端口段必开至少UDP要开6.3 升级和备份经验最后聊一下维护。这套系统升级的时候先把所有容器的配置文件备份一遍然后docker compose pull拉新镜像docker compose up -d重新创建容器。配置文件只要卷挂载没变服务起来之后配置还在但要注意WVP镜像大版本升级之后数据库表结构可能有变化WVP启动的时候会自动执行Flyway迁移脚本。升级之前一定要先dump数据库。docker exec wvp-mysql sh -c exec mysqldump -uroot -pWvp2024 wvp /data/backup/wvp_$(date %F).sql备份的时候除了数据库还把/data/wvp目录整个打压缩包因为里面还有ZLM的配置文件、Nginx的证书。恢复的时候相当于整个环境还原比单容器重建要快得多。个人实际操作中的体会是这套部署最大的门槛不在技术上而在配置细节的耦合上。WVP的secret、ZLM的secret、Redis的密码、MySQL的密码这四个值只要有一个不对系统启动之后就是各种莫名其妙的离线、401、黑屏。所以我在每次部署的时候都会整理一个参数对照表从Compose文件里的环境变量到application.yml再到config.ini所有密码和端口编号全部列出来对着检查一遍再启动能省下大半天排查时间。另外一个建议是刚部署完成的时候先用vlc直接拉ZLM的RTSP流验证一下ZLM本身是通的vlc打开rtsp://服务器IP:10554/rtp/你的流ID。如果这一步通了说明流媒体链路没问题问题出在WVP的配置上如果这一步都不通重点排查ZLM的config.ini和网络。这样二分法定位问题比在界面上瞎点快得多。这套东西跑起来之后很省心稳定运行几个月都不用管但要达到“省心”的状态前期配置的苦功夫值得下。