Files
silk/部署指南(物理机).md
T
2026-08-17 21:43:26 +08:00

31 KiB
Raw Blame History

部署指南(物理机)

一、环境信息

项目
机器 Acer TravelMate P249-G2-MG(物理机)
OS Ubuntu 22.04.5 LTS
内核 6.8.0-124-generic
磁盘 387GB/dev/sda6),可用 356GB
内存 16GB
网络 192.168.1.4(物理网卡 enp4s0+ 100.83.103.1NetBird VPN
SSH pan@100.83.103.1 密码 pan

与 WSL2 部署的关键区别:无需 portproxy、无需 WSL2 IP 修复、文件系统不会变只读、Ceph 需从头配置。

二、服务架构

浏览器
  │
  ├── http://100.83.103.1:5174  →  前端 (Node.js server.cjs)
  ├── http://100.83.103.1:3000  →  Go 后端 API
  ├── http://100.83.103.1:7480  →  Ceph S3 RGW
  ├── http://100.83.103.1:18080 →  WVP 管理界面
  │
  ▼
物理机 (100.83.103.1)
  ├── sshd          :22     SSH 服务
  ├── Docker
  │   ├── polaris-wvp   :18080/:18978/:8116  GB28181 信令(18080管理界面/18978HTTP API/8116 SIP
  │   ├── polaris-media :8081/:10000  ZLMediaKit 流媒体
  │   ├── polaris-mysql :3306         WVP 数据库
  │   ├── polaris-redis              WVP 缓存(Docker 内部网络)
  │   └── silk-redis    :6379        Go 后端缓存(密码 pan)
  ├── Ceph
  │   ├── Monitor (a)
  │   ├── Mgr (a)
  │   ├── OSD 0/1 (loop device, 各 50GB, 共 100GB)
  │   └── RGW           :7480        S3 兼容存储
  ├── PostgreSQL 14 :5432            业务数据库(实际版本 14)
  ├── IoTDB 2.0.8   :18081           时序数据库
  ├── VerneMQ       :1883            MQTT broker
  ├── Go 后端       :3000            silk-server-go
  ├── Go 录制服务   :9090            recorder-go
  ├── AI 推理服务   :8000            ai-serviceFastAPI + ONNX Runtimemock/onnx
  └── 前端          :5174            server.cjs

三、全新部署步骤

1. 安装系统依赖

# SSH 到服务器
ssh pan@100.83.103.1

# 更新 apt
sudo apt update

# 基础工具
sudo apt install -y curl wget git

# ffmpeg(视频转码)
sudo apt install -y ffmpeg

# PostgreSQL
sudo apt install -y postgresql postgresql-contrib

# JDK 17IoTDB 依赖)
sudo apt install -y openjdk-17-jdk-headless

# Docker(阿里云镜像源)
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
sudo curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://mirrors.aliyun.com/docker-ce/linux/ubuntu jammy stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# Docker 镜像加速
sudo mkdir -p /etc/docker
cat << 'EOF' | sudo tee /etc/docker/daemon.json
{
  "registry-mirrors": ["https://docker.1panel.live", "https://hub.rat.dev"]
}
EOF
sudo systemctl restart docker

# 将 pan 加入 docker 组
sudo usermod -aG docker pan

# Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Go 1.23
cd /tmp
wget https://golang.google.cn/dl/go1.23.4.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc

2. 拉取 Docker 镜像

sudo docker pull mysql:8
sudo docker pull redis:latest
sudo docker pull zlmediakit/zlmediakit:master

3. 启动 Docker 容器

# 创建网络
sudo docker network create media-net

# MySQLWVP 数据库)
sudo docker run -d --name polaris-mysql --network media-net --restart unless-stopped \
  -e MYSQL_DATABASE=wvp -e MYSQL_ROOT_PASSWORD=root \
  -e MYSQL_USER=wvp_user -e MYSQL_PASSWORD=wvp_password -e TZ=Asia/Shanghai \
  -v /home/pan/silk/wvp/volumes/mysql/data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --default-time-zone=+8:00 --lower-case-table-names=1

# RedisWVP 缓存,Docker 内部网络)
sudo docker run -d --name polaris-redis --network media-net --restart unless-stopped \
  -e TZ=Asia/Shanghai \
  redis:latest redis-server --appendonly yes --requirepass root

# ZLMediaKit
mkdir -p /home/pan/silk/wvp/media /home/pan/silk/wvp/volumes/video /home/pan/silk/wvp/logs/media
cat > /home/pan/silk/wvp/media/config.ini << 'EOF'
[api]
apiDebug=1
secret=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
[http]
port=80
charSet=utf-8
[rtsp]
port=5540
[rtmp]
port=10935
[rtp_proxy]
port=10000
[record]
appName=record
sampleMS=100
[hls]
segDur=2
segNum=3
[hook]
enable=0
EOF

sudo docker run -d --name polaris-media --network media-net --restart always \
  -e TZ=Asia/Shanghai \
  -p 8081:80 -p 5540:5540 -p 10935:10935 -p 10000:10000 -p 10000:10000/udp \
  -v /home/pan/silk/wvp/volumes/video:/opt/media/bin/www/record/ \
  -v /home/pan/silk/wvp/logs/media:/opt/media/log/ \
  -v /home/pan/silk/wvp/media/config.ini:/conf/config.ini \
  zlmediakit/zlmediakit:master MediaServer -c /conf/config.ini -l 0

# RedisGo 后端缓存,端口 6379,密码 pan)
sudo docker run -d --name silk-redis --restart always \
  -p 6379:6379 \
  redis:latest redis-server --requirepass pan --appendonly yes

4. 配置 PostgreSQL

sudo service postgresql start
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'pan';"
sudo -u postgres psql -c "CREATE DATABASE silk;"

5. 安装 Ceph

参考Ceph 安装文档

添加APT密钥

要安装 release.asc 密钥,请执行以下操作

wget -q -O- 'https://download.ceph.com/keys/release.asc' | sudo tee /etc/apt/trusted.gpg.d/ceph.asc

添加软件源

将 Ceph 软件包仓库添加到系统的 APT 源列表中。对于较新版本的 Debian/Ubuntu,在命令行上调用 lsb_release -sc 以获取短代号,并替换以下命令中的 {codename}

sudo apt-add-repository 'deb https://download.ceph.com/debian-tentacle/ {codename} main'

安装

# 安装 CephUbuntu 默认源,Quincy 17.2
sudo apt install -y ceph ceph-common radosgw ceph-volume

# 生成配置
FSID=$(uuidgen)
sudo bash -c "cat > /etc/ceph/ceph.conf << EOF
[global]
fsid = $FSID
mon initial members = a
mon host = 127.0.0.1:6789
osd pool default size = 1
osd pool default min size = 1
osd crush chooseleaf type = 0
mon allow insecure global id reclaim = true
auth_cluster_required = none
auth_service_required = none
auth_client_required = none

[mon.a]
host = localhost
mon addr = 127.0.0.1:6789

[mgr.a]
host = localhost

[client.rgw.a]
host = localhost
rgw frontends = beast port=7480
EOF"

# 创建 Monitor
sudo ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon "allow *"
sudo ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon "allow *" --cap osd "allow *" --cap mds "allow *" --cap mgr "allow *"
sudo ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring
sudo monmaptool --create --add a 127.0.0.1:6789 --fsid $FSID /tmp/monmap
sudo mkdir -p /var/lib/ceph/mon/ceph-a
sudo chown -R ceph:ceph /var/lib/ceph/mon/ceph-a
sudo ceph-mon --mkfs -i a --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring
sudo ceph-mon -i a
sleep 10

# 创建 Manager
sudo mkdir -p /var/lib/ceph/mgr/ceph-a
sudo chown -R ceph:ceph /var/lib/ceph/mgr/ceph-a
sudo bash -c 'ceph auth get-or-create mgr.a mon "allow profile mgr" osd "allow *" mds "allow *" > /var/lib/ceph/mgr/ceph-a/keyring'
sudo ceph-mgr -i a
sleep 3

# 创建 OSDloop device 方式)
for i in 0 1; do
  sudo fallocate -l 50G /var/lib/ceph/osd-data-$i
  LOOP_DEV=$(sudo losetup -f --show /var/lib/ceph/osd-data-$i)
  sudo pvcreate -f $LOOPfd _DEV
  sudo vgcreate ceph-osd-vg-$i $LOOP_DEV
  sudo lvcreate -l 100%FREE -n osd-$i ceph-osd-vg-$i
  sudo ceph-volume lvm prepare --bluestore --data ceph-osd-vg-$i/osd-$i
done
sudo ceph-volume lvm activate --all
sleep 15

# 启动 RGW
sudo mkdir -p /var/lib/ceph/radosgw/ceph-rgw.a
sudo chown -R ceph:ceph /var/lib/ceph/radosgw/ceph-rgw.a
sudo bash -c 'ceph auth get-or-create client.rgw.a osd "allow rwx" mon "allow rw" -o /var/lib/ceph/radosgw/ceph-rgw.a/keyring'
sudo bash -c 'nohup radosgw -n client.rgw.a -k /var/lib/ceph/radosgw/ceph-rgw.a/keyring --rgw-frontends "beast port=7480" > /var/log/ceph/radosgw.log 2>&1 &'
sleep 5

# 创建 S3 用户
sudo radosgw-admin user create --uid=silk-app --display-name="Silk App" \
  --access-key=silk-app --secret-key=Silk-App-Secret-2026!

# 验证
sudo ceph -s
curl -s -o /dev/null -w "%{http_code}" http://localhost:7480/  # 应返回 200

Ceph 关键点

  • BlueStore 需要块设备,不能直接用目录
  • fallocate + losetup 创建 loop device,再用 ceph-volume lvm prepare 创建 OSD
  • auth_*_required = none 简化认证(单节点测试环境)
  • osd pool default size = 1 适配单节点(默认 3 副本会导致 PG 一直 inactive

6. 安装 VerneMQ

# 上传 deb 包
# pscp -pw pan e:\silk\tools\vernemq-1.13.0.jammy.x86_64.deb pan@100.83.103.1:/home/pan/
sudo dpkg -i /home/pan/vernemq-1.13.0.jammy.x86_64.deb

# 接受 EULA
sudo sed -i 's/^accept_eula.*/accept_eula = yes/' /etc/vernemq/vernemq.conf
sudo bash -c 'echo "accept_eula = yes" >> /etc/vernemq/vernemq.conf'

# 启动
sudo vernemq start

7. 安装 IoTDB

# 上传 IoTDB(含已有数据)
# pscp -r -pw pan e:\silk\tools\iotdb\apache-iotdb-2.0.8-all-bin pan@100.83.103.1:/home/pan/iotdb/

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64

# 清理旧锁文件(从 Windows 上传的文件可能需要)
find /home/pan/iotdb/apache-iotdb-2.0.8-all-bin/data -name "*.lock" -delete

# 设置 REST 端口为 18081(避免与 WVP 的 18080 冲突)
IOTDB_CONF=/home/pan/iotdb/apache-iotdb-2.0.8-all-bin/conf/iotdb-system.properties
grep -q "rest_service_port" $IOTDB_CONF && sed -i 's/^rest_service_port.*/rest_service_port=18081/' $IOTDB_CONF || echo "rest_service_port=18081" >> $IOTDB_CONF

# 启动
nohup /home/pan/iotdb/apache-iotdb-2.0.8-all-bin/sbin/start-standalone.sh > /home/pan/iotdb.log 2>&1 < /dev/null &

Valkey 不可用valkey-8.1.2-noble-x86_64.tar.gz 编译于 Ubuntu 24.04GLIBC 2.38),Ubuntu 22.04 只有 GLIBC 2.35。用 Docker Redis 替代。

8. 构建 WVP Docker 镜像

# 克隆 WVP 源码
cd /home/pan/silk/wvp
git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git wvp-src
cd wvp-src

# 使用自定义 Dockerfile(带 Maven 阿里云镜像加速)
# 从 Windows 上传: pscp -pw pan e:\silk\wvp\Dockerfile pan@100.83.103.1:/home/pan/silk/wvp/wvp-src/Dockerfile
sudo docker build -t docker-polaris-wvp:latest .

# 导入 WVP 数据库初始化 SQL
sudo docker exec -i polaris-mysql mysql -uroot -proot wvp < "数据库/2.7.4/初始化-mysql-2.7.4.sql"

# 授予 MySQL 权限
sudo docker exec polaris-mysql mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'wvp_user'@'%'; FLUSH PRIVILEGES;"

9. 配置并启动 WVP

# 创建 WVP 配置
mkdir -p /home/pan/silk/wvp/wvp/wvp /home/pan/silk/wvp/logs/wvp

cat > /home/pan/silk/wvp/wvp/wvp/application.yml << 'EOF'
spring:
  application:
    name: wvp
  servlet:
    multipart:
      max-file-size: 10GB
      max-request-size: 10GB
  cache:
    type: redis
  data:
    redis:
      host: polaris-redis
      port: 6379
      password: root
      timeout: 10000
      database: 6
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://polaris-mysql:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
    username: wvp_user
    password: wvp_password
    hikari:
      connection-timeout: 20000
      maximum-pool-size: 30
      minimum-idle: 5

server:
  port: 18080

sip:
  ip: 0.0.0.0
  port: 8116
  domain: 3402000000
  id: 34020000002000000001
  password: 12345678

media:
  id:
  ip: polaris-media
  http-port: 80
  secret: su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
  rtp:
    enable: true
    port-range: 30000,30500
    send-port-range: 30000,30500
  record-assist-port: 18081
  sdp-ip: 100.83.103.1
  stream-ip: 100.83.103.1

user-settings:
  record-push-live: true
  auto-apply-play: true
  push-authority: true
  use-push-url: false
  use-wvp-host: true
  enable-fmp4-fallback: true
  record-sip: true
  enable-audio: true

logging:
  level:
    root: INFO
EOF

# 启动 WVP 容器
sudo docker run -d --name polaris-wvp --network media-net --restart always \
  -p 18080:18080 -p 8116:8116/tcp -p 8116:8116/udp \
  -v /home/pan/silk/wvp/wvp/wvp:/opt/ylcx/wvp/ \
  -v /home/pan/silk/wvp/logs/wvp:/opt/wvp/logs/ \
  -e TZ=Asia/Shanghai \
  -e Stream_IP=100.83.103.1 \
  -e SDP_IP=100.83.103.1 \
  -e ZLM_HOST=polaris-media \
  -e ZLM_SERCERT=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf \
  -e REDIS_HOST=polaris-redis \
  -e DATABASE_HOST=polaris-mysql \
  -e SIP_ShowIP=100.83.103.1 \
  docker-polaris-wvp:latest

WVP 端口变更WVP 2.7.4 的 server.port 是 18080(不是之前的 18978)。Go 后端 .envWVP_API_BASE 需改为 http://localhost:18080

10. 部署 Go 后端

# 在 Windows 上交叉编译
# cd D:\silk-master-84e4ddc\server-go
# $env:GOOS="linux"; $env:GOARCH="amd64"; go build -o silk-server-go-linux ./cmd/server/
# pscp -pw pan silk-server-go-linux pan@100.83.103.1:/home/pan/

chmod +x /home/pan/silk-server-go-linux

# 创建 .env
cat > /home/pan/silk/.env << 'EOF'
PORT=3000
APP_ENV=production
ALLOW_DEV_AUTOMIGRATE=false
PG=postgresql://postgres:pan@localhost:5432/silk
REDIS=redis://:pan@localhost:6379
MQTT=mqtt://pan:pan@localhost:1883
IOTDB_URL=http://127.0.0.1:18081
S3_ENDPOINT=http://100.83.103.1:7480
S3_ACCESS_KEY=silk-app
S3_SECRET_KEY=Silk-App-Secret-2026!
S3_BUCKET_EVENTS=silk-video-events
S3_BUCKET_ARCHIVE=silk-video-archive
WVP_API_BASE=http://localhost:18080
WVP_USERNAME=admin
WVP_PASSWORD=admin
ZLM_API_BASE=http://localhost:8081
ZLM_SECRET=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
INTERNAL_API_KEY=silk-internal-2026
RECORDER_API_BASE=http://localhost:9090
EOF

# 启动
cd /home/pan/silk
nohup /home/pan/silk-server-go-linux > server-go.log 2>&1 < /dev/null &

数据库迁移Go 后端启动时会自动执行 server-go/migrations/ 下的 SQL 迁移并校验 schema_migrations 版本;生产环境不要开启 ALLOW_DEV_AUTOMIGRATE=true。升级前先按 AGENTS.md 完成数据库备份。

当前迁移版本:开发服务器已部署 schemaVersion=8;本地代码 CurrentSchemaVersion=9v9 新增 organizationsorganization_members、设备维护、产量损失、脱敏案例、实验室结果等表,并给 rooms.org_idrearing_records 增加列。部署 v9 前必须先 pg_dump 全库备份,并确认 000009_object_authority_and_business_capabilities.up.sql 应用成功。

11. 部署 Go 录制服务

# 在 Windows 上交叉编译
# cd D:\silk-master-84e4ddc\wvp\recorder-go
# $env:GOOS="linux"; $env:GOARCH="amd64"; go build -o recorder-go-linux .
# pscp -pw pan recorder-go-linux pan@100.83.103.1:/home/pan/

chmod +x /home/pan/recorder-go-linux
nohup /home/pan/recorder-go-linux > /home/pan/recorder.log 2>&1 < /dev/null &

12. 部署前端

# 在 Windows 上构建
# cd D:\silk-master-84e4ddc\web; npm run build
# tar -czf dist.tar.gz dist
# pscp -pw pan dist.tar.gz pan@100.83.103.1:/home/pan/
# pscp -pw pan D:\silk-master-84e4ddc\wvp\recorder-go\server.js pan@100.83.103.1:/home/pan/silk/web/server.cjs

mkdir -p /home/pan/silk/web
cd /home/pan/silk/web
tar xzf /home/pan/dist.tar.gz

# 启动
nohup node /home/pan/silk/web/server.cjs > /home/pan/silk/web/web.log 2>&1 < /dev/null &

server.cjs 命名package.json"type": "module" 会导致 .js 文件被当作 ES Module 解析。server.js 使用 require()CommonJS),需重命名为 server.cjs

13. 部署 ai-service2026-08 新增)

# 上传源码 tar 包(本机 scripts/devssh.py --upload ai-service.tar.gz /home/pan/
mkdir -p /home/pan/ai-service
tar xzf /home/pan/ai-service.tar.gz -C /home/pan
cd /home/pan/ai-service

# 前置:python3-venv(如缺失)
sudo apt install -y python3.10-venv

python3 -m venv .venv
.venv/bin/pip install -r requirements.txt   # 服务器 pip 已配置清华镜像

cat > start.sh << 'EOF'
#!/bin/bash
cd /home/pan/ai-service
export MODEL_MODE=mock
export MOCK_CLASS=healthy
nohup .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 > ai-service.log 2>&1 < /dev/null &
echo STARTED
EOF
chmod +x start.sh && bash start.sh
sleep 4

curl -s http://localhost:8000/health     # {"status":"ok","model":"mock"}
curl -s http://localhost:8000/metrics    # 含请求量/延迟/GPUnvidia-smi

训练恢复后:放置 models/best.onnx 并把 MODEL_MODE 改为 onnx 重启即可(接口不变)。Go 后端通过 AI_SERVICE_BASE(默认 http://localhost:8000)对接。

四、验证

# 检查所有端口
ss -tlnp | grep -E '22|3000|5174|6379|1883|7480|8081|9090|18080|18081|8116'

# Go 后端
curl -s http://localhost:3000/api/v1/health
# 应返回 {"status":"ok"}

# 录制服务
curl -s http://localhost:9090/health
# 应返回 {"status":"ok"}

# 前端
curl -s -o /dev/null -w '%{http_code}' http://localhost:5174/
# 应返回 200

# ZLMediaKit
curl -s -o /dev/null -w '%{http_code}' http://localhost:8081/
# 应返回 200

# WVP
curl -s -o /dev/null -w '%{http_code}' http://localhost:18080/
# 应返回 404(正常,API 在具体路径下)

# Ceph S3
curl -s -o /dev/null -w '%{http_code}' http://localhost:7480/
# 应返回 200

# Docker 容器
sudo docker ps --format "{{.Names}}: {{.Status}}"
# 应显示 5 个容器全部 Up

# Ceph 集群
sudo ceph -s
# 应显示 2 osds: 2 up, 2 in

五、关键配置文件

Go 后端 .env (/home/pan/silk/.env)

PORT=3000
PG=postgresql://postgres:pan@localhost:5432/silk
APP_ENV=production
ALLOW_DEV_AUTOMIGRATE=false
REDIS=redis://:pan@localhost:6379
MQTT=mqtt://pan:pan@localhost:1883
IOTDB_URL=http://127.0.0.1:18081
S3_ENDPOINT=http://100.83.103.1:7480
S3_ACCESS_KEY=silk-app
S3_SECRET_KEY=Silk-App-Secret-2026!
WVP_API_BASE=http://localhost:18080
ZLM_API_BASE=http://localhost:8081
ZLM_SECRET=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
RECORDER_API_BASE=http://localhost:9090

WVP application.yml (/home/pan/silk/wvp/wvp/wvp/application.yml)

关键配置:

  • sip.port: 8116
  • sip.domain: 3402000000
  • sip.id: 34020000002000000001
  • media.ip: polaris-mediaDocker 内部网络)
  • media.sdp-ip / media.stream-ip: 100.83.103.1VPN IP,摄像头需要访问)
  • server.port: 18080

Ceph (/etc/ceph/ceph.conf)

  • auth_*_required = none(单节点简化认证)
  • osd pool default size = 1(单副本)

六、与 WSL2 部署的差异

项目 WSL2 物理机
文件系统 ext4 on VHDX(可能变只读) ext4 on /dev/sda6(稳定)
网络 需要 portproxy 转发 直连,无需 portproxy
IP 172.18.127.247(可能变化) 100.83.103.1WireGuard 固定)
WVP 端口 18978 18080
缓存 Valkey(裸机安装) Docker RedisValkey GLIBC 不兼容)
Ceph 已配置(可直接启动) 需从头配置(loop device OSD
WVP 镜像 已构建 需从 Gitee 克隆源码构建

七、重启后恢复

物理机重启后,systemd 管理的服务(Docker、PostgreSQL、VerneMQ)会自动启动。需要手动启动的服务:

# 1. Cephsystemd 管理,但 loop device 需要手动激活)
sudo ceph-mon -i a
sudo ceph-mgr -i a
# loop device 需要重新挂载
for i in 0 1; do
  sudo losetup /dev/loop$((i+8)) /var/lib/ceph/osd-data-$i
  sudo ceph-volume lvm activate --all
done
# RGW
sudo bash -c 'nohup radosgw -n client.rgw.a -k /var/lib/ceph/radosgw/ceph-rgw.a/keyring --rgw-frontends "beast port=7480" > /var/log/ceph/radosgw.log 2>&1 &'

# 2. IoTDB
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
nohup /home/pan/iotdb/apache-iotdb-2.0.8-all-bin/sbin/start-standalone.sh > /home/pan/iotdb.log 2>&1 < /dev/null &

# 3. Go 后端
cd /home/pan/silk
nohup /home/pan/silk-server-go-linux > server-go.log 2>&1 < /dev/null &

# 4. Go 录制服务
nohup /home/pan/recorder-go-linux > /home/pan/recorder.log 2>&1 < /dev/null &

# 5. 前端
nohup node /home/pan/silk/web/server.cjs > /home/pan/silk/web/web.log 2>&1 < /dev/null &

摄像头注册信息

摄像头 GB28181 注册参数:

参数 说明
SIP 服务器 IP 100.83.103.1 NetBird VPN IP
SIP 服务器端口 8116 TCP/UDPDocker 映射 -p 8116:8116/tcp -p 8116:8116/udp
SIP 域 3402000000
SIP 服务器 ID 34020000002000000001
SIP 密码 12345678

摄像头需能访问 100.83.103.1NetBird VPN)。若摄像头不在 VPN 网络,改用物理网卡 IP 192.168.1.4,同时修改 WVP 配置中的 sip.ipmedia.sdp-ipmedia.stream-ip

八、常见问题

1. Valkey GLIBC 不兼容

现象valkey-cli: /lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.38' not found 原因valkey-8.1.2-noble-x86_64.tar.gz 编译于 Ubuntu 24.04Noble),需要 GLIBC 2.38Ubuntu 22.04 只有 2.35。 解决:用 Docker Redis 替代(docker run -d --name silk-redis -p 6379:6379 redis:latest redis-server --requirepass pan)。

2. Ceph OSD 创建失败

现象bluestore _read_bdev_label failed to open /var/lib/ceph/osd/ceph-0/block: (2) No such file or directory 原因:BlueStore 需要块设备,不能直接用目录。 解决:用 fallocate 创建文件 + losetup 创建 loop device + ceph-volume lvm prepare --bluestore --data <vg/lv>

3. WVP 启动报 Could not resolve placeholder 'media.id'

原因application.ymlmediasip 应在顶层,不是 user-settings 下。 解决:参考步骤 9 的 application.yml 模板。

4. WVP 报 Failed to obtain JDBC Connection

原因MySQL 未导入 WVP 初始化 SQL,或 wvp_user 权限不足。 解决

docker exec -i polaris-mysql mysql -uroot -proot wvp < "数据库/2.7.4/初始化-mysql-2.7.4.sql"
docker exec polaris-mysql mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'wvp_user'@'%'; FLUSH PRIVILEGES;"

5. server.js 报 Cannot use import statement outside a module

原因package.json"type": "module" 导致 .js 文件按 ES Module 解析,但 server.js 使用 CommonJS (require())。 解决:将 server.js 重命名为 server.cjs

6. Docker 镜像拉取失败

现象could not fetch content descriptor ... not found 解决:配置 Docker 镜像加速(/etc/docker/daemon.json 中设置 registry-mirrors)。

7. Ceph OSD 重启后不自动激活

原因loop device 重启后需要重新挂载。 解决:参考"七、重启后恢复"中的 Ceph 部分。

九、前端测试

十、实时视频"等待视频流"问题排查与修复

现象

前端 /videos 页面点击查看实时视频,一直显示"等待视频流...",控制台报 POST /api/v1/video/cameras/1/live 503。点击"开始录制"同样报 503。

根因(共 4 个问题,需全部修复)

# 问题 原因
1 Go 后端调用 WVP 端口错误 config.goenvDefault:"http://localhost:18978"Go 后端用 caarlos0/envOS 环境变量而非 .env 文件,.env 未被加载
2 ZLMediaKit hooks 指向 127.0.0.1:18080 WVP 每次启动时会重置 ZLMediaKit hooks,用 127.0.0.1(容器自身)而非 polaris-wvpDocker 网络主机名)
3 ZLMediaKit mediaServerId 为空 WVP 配置 media.id: 为空,推流时 on_publish hook 报"找不到对应的mediaServer"
4 流 URL 含 Docker 内部主机名 WVP 返回的播放地址用 polaris-media:80fixZlmPort 只替换端口不替换主机名,浏览器无法解析 polaris-media

修复方法

1. Go 后端加载 .env 环境变量

Go 后端用 caarlos0/env 库读取 OS 环境变量,不自动读 .env 文件。启动时必须先 source .env

cd /home/pan/silk
set -a
source .env
set +a
nohup /home/pan/silk-server-go-linux > server-go.log 2>&1 < /dev/null &

确保 .envWVP_API_BASE=http://localhost:18080(不是 18978)。

2. 修复 ZLMediaKit hooks(每次 WVP 重启后需重新执行)

WVP 启动时会将 ZLMediaKit hooks 重置为 127.0.0.1:18080,需要手动改为 polaris-wvp:18080

SECRET="su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf"
HOOK_BASE="http://polaris-wvp:18080"
curl -s "http://localhost:8081/index/api/setServerConfig?secret=$SECRET" \
  --data-urlencode "hook.on_play=$HOOK_BASE/index/hook/on_play" \
  --data-urlencode "hook.on_publish=$HOOK_BASE/index/hook/on_publish" \
  --data-urlencode "hook.on_record_mp4=$HOOK_BASE/index/hook/on_record_mp4" \
  --data-urlencode "hook.on_rtp_server_timeout=$HOOK_BASE/index/hook/on_rtp_server_timeout" \
  --data-urlencode "hook.on_send_rtp_stopped=$HOOK_BASE/index/hook/on_send_rtp_stopped" \
  --data-urlencode "hook.on_server_keepalive=$HOOK_BASE/index/hook/on_server_keepalive" \
  --data-urlencode "hook.on_server_started=$HOOK_BASE/index/hook/on_server_started" \
  --data-urlencode "hook.on_stream_changed=$HOOK_BASE/index/hook/on_stream_changed" \
  --data-urlencode "hook.on_stream_none_reader=$HOOK_BASE/index/hook/on_stream_none_reader" \
  --data-urlencode "hook.on_stream_not_found=$HOOK_BASE/index/hook/on_stream_not_found"

注意:每次 WVP 容器重启后都需重新执行此命令。可将此命令加入启动脚本。

3. 设置固定的 mediaServerId

在 WVP application.yml 中设置 media.id

media:
  id: silk-media-001    # 不能为空

同时通过 ZLMediaKit API 设置:

curl -s "http://localhost:8081/index/api/setServerConfig?secret=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf" \
  --data-urlencode "general.mediaServerId=silk-media-001"

4. 修复流 URL 中的 Docker 内部主机名

修改 server-go/internal/service/media.go 中的 fixZlmPort 函数,增加主机名替换:

func fixZlmPort(u string) string {
    if u == "" {
        return u
    }
    // 替换 Docker 内部主机名为外部可访问 IP
    u = strings.Replace(u, "polaris-media:", "100.83.103.1:", 1)
    u = strings.Replace(u, ":80/", ":8081/", 1)
    if strings.HasSuffix(u, ":80") {
        u = u[:len(u)-2] + ":8081"
    }
    return u
}

重新交叉编译并部署:

cd D:\silk-master-84e4ddc\server-go
$env:GOOS="linux"; $env:GOARCH="amd64"
go build -o silk-server-go-linux ./cmd/server/
$env:GOOS=""; $env:GOARCH=""
pscp -pw pan silk-server-go-linux pan@100.83.103.1:/home/pan/silk-server-go-linux

验证

修复后 WVP 日志应显示:

[ZLM-连接成功] IDsilk-media-001, 地址: polaris-media:80
[ZLM HOOK]推流鉴权-允许-响应:silk-media-001
[ZLM HOOK] 流注册, silk-media-001->rtsp->rtp/...
[点播成功] 设备编号: 34020000002000000001

Go 后端日志应显示:

INFO 调用 WVP play/start url=http://localhost:18080/api/play/start/...
INFO 请求 method=POST path=/api/v1/video/cameras/1/live status=200 latency=1.7s

前端播放器收到的流地址格式:

http://100.83.103.1:8081/rtp/34020000002000000001_34020000001310000001.live.flv

WVP 重启后的完整修复脚本

将以下脚本保存为 /home/pan/fix-hooks.sh,每次 WVP 重启后执行:

#!/bin/bash
SECRET="su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf"
HOOK_BASE="http://polaris-wvp:18080"
curl -s "http://localhost:8081/index/api/setServerConfig?secret=$SECRET" \
  --data-urlencode "hook.on_play=$HOOK_BASE/index/hook/on_play" \
  --data-urlencode "hook.on_publish=$HOOK_BASE/index/hook/on_publish" \
  --data-urlencode "hook.on_record_mp4=$HOOK_BASE/index/hook/on_record_mp4" \
  --data-urlencode "hook.on_rtp_server_timeout=$HOOK_BASE/index/hook/on_rtp_server_timeout" \
  --data-urlencode "hook.on_send_rtp_stopped=$HOOK_BASE/index/hook/on_send_rtp_stopped" \
  --data-urlencode "hook.on_server_keepalive=$HOOK_BASE/index/hook/on_server_keepalive" \
  --data-urlencode "hook.on_server_started=$HOOK_BASE/index/hook/on_server_started" \
  --data-urlencode "hook.on_stream_changed=$HOOK_BASE/index/hook/on_stream_changed" \
  --data-urlencode "hook.on_stream_none_reader=$HOOK_BASE/index/hook/on_stream_none_reader" \
  --data-urlencode "hook.on_stream_not_found=$HOOK_BASE/index/hook/on_stream_not_found"
echo "Hooks fixed."

十一、录像片段查不到问题

现象

前端 /videos "历史回看"点击"查询录像"看不到任何录像片段。点击"开始录制"后录制服务立即退出,录制 0 段。

原因

Ceph RGW 只创建了 S3 用户(silk-app),但没有创建 S3 bucket。录制服务上传视频片段时 S3 返回 404 NoSuchBucket,录制线程立即退出。

录制服务日志:

CreateMultipartUpload 失败: operation error S3: CreateMultipartUpload, https response error StatusCode: 404, api error NoSuchBucket: UnknownError
录制线程退出(共完成 0 段)

修复方法

使用 Python S3 API 创建 bucketradosgw-admin 没有 bucket create 子命令):

# 上传 create-buckets.py 到服务器后执行
python3 /home/pan/create-buckets.py

create-buckets.py 内容:

#!/usr/bin/env python3
import http.client, hashlib, hmac, datetime, base64

host = "localhost:7480"
access_key = "silk-app"
secret_key = "Silk-App-Secret-2026!"
buckets = ["silk-video-archive", "silk-video-events"]

for bucket in buckets:
    conn = http.client.HTTPConnection(host)
    date = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
    string_to_sign = f"PUT\n\n\n{date}\n/{bucket}"
    signature = base64.b64encode(
        hmac.new(secret_key.encode(), string_to_sign.encode(), hashlib.sha1).digest()
    ).decode()
    conn.request("PUT", f"/{bucket}", headers={
        "Date": date,
        "Authorization": f"AWS {access_key}:{signature}"
    })
    resp = conn.getresponse()
    print(f"Bucket {bucket}: {resp.status} {resp.reason}")
    conn.close()

创建后重启录制服务:

sudo fuser -k 9090/tcp
sleep 2
nohup /home/pan/recorder-go-linux > /home/pan/recorder.log 2>&1 < /dev/null &

验证

# 检查 bucket 列表
python3 /home/pan/create-buckets.py  # 会列出所有 bucket

# 录制服务日志应显示正常录制
tail -5 /home/pan/recorder.log
# 不应有 NoSuchBucket 错误

注意Ceph RGW 的 radosgw-admin 不支持 bucket create 命令,bucket 必须通过 S3 APIPUT /bucket-name)创建。

可观测性与恢复验证

  • 每个 HTTP 请求统一记录 X-Request-ID,日志不得包含 Authorization 原始值或敏感 query。
  • 依赖指标通过 GET /api/v1/ops/metrics 查看;发布前按 docs/operations/slo-and-alerts.md 检查告警阈值。
  • 发布前备份 PostgreSQL,并参考 docs/operations/backup-restore-drill.md 记录备份时间、恢复时间和 RPO/RTO。
  • 负载和容量验证场景见 docs/operations/load-test-scenarios.md