从零部署后端余哥音乐到京东云 :一次完整的踩坑记录
一个自建无损音乐流媒体服务的部署实录。包含环境准备、systemd 配置、双服务共存、以及我实际踩过的 8 个坑。
背景
「道理鱼音乐」是一套自建的音乐流媒体服务:
- 后端:FastAPI + SQLite,负责扫描本地音乐库、提供 API
- 前端:单文件 HTML 播放器(
player.html),浏览器直接打开 - 特色功能:音质降级转码(原档 / 320k / 128k)、文件夹自动歌单、歌词逐行滚动、歌单封面
需求是把这套服务部署到京东云的一台 CentOS 机器上,同时保留机器上已有的老版本服务(端口 42129),新版本跑在 42126。
听起来不难,但实际踩了一路坑。记录下来,给后面的人省点时间。
一、环境清单
| 组件 | 版本 | 备注 |
|---|---|---|
| 系统 | CentOS 7.x | 京东云轻量云主机 |
| Python | 3.12 | 手动装的,系统自带的 3.6 太老 |
| ffmpeg | 7.0.2-static | 静态编译版,免依赖 |
| 项目路径 | /opt/mydaoliyu_music | |
| 音乐库 | /usr/local/music_player/music | |
| 数据目录 | /usr/local/music_player/data | |
| 服务端口 | 42126 | 老版本占 42129 |
二、部署流程
2.1 装依赖
# Python 环境
yum install -y python3 python3-pip python3-devel gcc
# 编译依赖(bcrypt、Pillow 需要)
yum install -y libffi-devel openssl-devel
2.2 装 ffmpeg(重点)
yum install ffmpeg 在 CentOS 7 上一定失败——EPEL 里没有 ffmpeg,因为专利许可问题。三种方案:
方案 A:Nux Dextop 仓库
rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
yum install -y ffmpeg
方案 B:RPM Fusion 仓库
yum install -y https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm
yum install -y ffmpeg
方案 C:静态编译版(我用的这个,最省事)
cd /tmp
wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
tar xvf ffmpeg-release-amd64-static.tar.xz
cp ffmpeg-*-static/ffmpeg /usr/local/bin/
cp ffmpeg-*-static/ffprobe /usr/local/bin/
chmod +x /usr/local/bin/ffmpeg /usr/local/bin/ffprobe
# 验证(任何目录都能跑)
ffmpeg -version | head -1
静态版最大好处:不污染系统包管理,卸载就是删两个文件。
2.3 上传代码 + 建虚拟环境
cd /opt/mydaoliyu_music
python3.12 -m venv venvmusic3.12
source venvmusic3.12/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
# 验证
python3 -c "import fastapi, uvicorn, multipart, mutagen, PIL, jwt, bcrypt; print('all ok')"
2.4 目录规划
mkdir -p /usr/local/music_player/music # 音乐库
mkdir -p /usr/local/music_player/data # SQLite + 封面缓存 + 转码缓存
2.5 配置 .env
重点:nano 在最小化安装的 CentOS 上默认没有。要么 yum install -y nano,要么用 cat 一次性写入:
cat > /opt/mydaoliyu_music/.env << 'EOF'
MUSIC_ROOT=/usr/local/music_player/music
DATA_DIR=/usr/local/music_player/data
JWT_SECRET=用 python -c "import secrets; print(secrets.token_urlsafe(48))" 生成
TICKET_SECRET=再生成一次
ADMIN_USERNAME=admin
ADMIN_PASSWORD=改成强密码
HTTP_HOST=0.0.0.0
HTTP_PORT=42126
AUTO_SCAN_ENABLED=1
SCAN_INTERVAL_SECONDS=1800
SCAN_ON_STARTUP=1
AUTO_FOLDER_PLAYLISTS=1
FFMPEG_BIN=ffmpeg
EOF
chmod 600 /opt/mydaoliyu_music/.env
<< 'EOF' 里的单引号很重要,避免 $ 被 bash 提前替换。
2.6 systemd 服务(CentOS 7 有坑,见第三节)
cat > /etc/systemd/system/mydaoliyu-music.service << 'EOF'
[Unit]
Description=My Daoliyu Music API
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/mydaoliyu_music
EnvironmentFile=/opt/mydaoliyu_music/.env
ExecStart=/bin/bash -c 'exec /opt/mydaoliyu_music/venvmusic3.12/bin/uvicorn main:app --host 0.0.0.0 --port 42126 --workers 1 >> /var/log/mydaoliyu-music.log 2>&1'
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now mydaoliyu-music
systemctl status mydaoliyu-music
2.7 放行端口
京东云控制台 → 安全组 → 加入站规则:TCP 42126,来源 0.0.0.0/0。
firewalld(如果开着):
firewall-cmd --permanent --add-port=42126/tcp
firewall-cmd --reload
SELinux(如果 Enforcing):
semanage port -a -t http_port_t -p tcp 42126
三、踩坑记录(8 个)
坑 1:CentOS 7 没预装 nano
最小化安装的 CentOS 只有 vi。或者用 cat > file << 'EOF' 直接写入,比 vi 还快。
yum install -y nano
坑 2:EPEL 里没有 ffmpeg
yum install ffmpeg 一定报 No package ffmpeg available.。用静态版最省心。
坑 3:Python 3.12 的 venv 与老 pip 冲突
系统 pip 是 3.6 时代的,直接 pip install 会装错地方。必须先激活 venv:
source /opt/mydaoliyu_music/venvmusic3.12/bin/activate
which python
# /opt/mydaoliyu_music/venvmusic3.12/bin/python
坑 4:StandardOutput=append: 在 CentOS 7 报错
CentOS 7 的 systemd 是 219 版本,StandardOutput=append: 语法是 240+ 才支持的。
我在服务文件里写了:
StandardOutput=append:/var/log/daoliyu-music.log
结果 systemd 直接报:
Failed to parse output specifier...
Current command vanished from the unit file
服务起不来,日志也不生成。
解决方案:用 bash 包装重定向:
ExecStart=/bin/bash -c 'exec /path/to/uvicorn ... >> /var/log/xxx.log 2>&1'
exec 关键字很关键——它让 uvicorn 直接替换 bash 进程,systemd 才能正确管理 PID 和信号。
坑 5:服务名冲突
本来我把服务起名叫 daoliyu-music.service,结果 systemctl enable 时报:
Failed to execute operation: File exists
查了一下才发现,机器上已经有一个老版本服务用这个名字了,而且文件在 /usr/lib/systemd/system/daoliyu-music.service(系统级),我写的是 /etc/systemd/system/daoliyu-music.service(用户级),两者同名,systemd 加载时以 /etc 优先,但 enable 操作会冲突。
解决:服务名改成 mydaoliyu-music,服务文件、日志文件名都加前缀,彻底隔离。
坑 6:systemctl status 显示的还是老进程
改完文件、systemctl daemon-reload 后,status 里 Main PID 还是老的 daoliyu-server。
原因:systemd 缓存了老的服务定义。执行:
systemctl stop daoliyu-music
systemctl daemon-reload
systemctl reset-failed daoliyu-music
systemctl start daoliyu-music
坑 7:drop-in 目录里的遗留配置
有些老部署会在 /etc/systemd/system/xxx.service.d/ 下放 drop-in 文件(比如 after-mysql.conf),它们会自动合并到同名服务里,导致你不认识的行为。
排查:
ls -l /etc/systemd/system/daoliyu-music.service.d/
systemctl cat daoliyu-music
清理:
rm -rf /etc/systemd/system/daoliyu-music.service.d
systemctl daemon-reload
坑 8:端口被占,但 netstat 查不出来
netstat -tlnp | grep 42126 没输出,但启动还是报 Address already in use。
原因:进程可能绑的是 IPv6(:::42126),要用:
ss -tlnp | grep 42126
ss 比 netstat 更现代、信息更全,优先用 ss。
四、双服务共存方案
老服务保留,新服务并存,关键原则:名字不同、端口不同、路径不同、日志不同。
| 项目 | 老服务 | 新服务 |
|---|---|---|
| 服务名 | daoliyu-music | mydaoliyu-music |
| 端口 | 42129 | 42126 |
| 项目路径 | /opt/daoliyu-music | /opt/mydaoliyu_music |
| 数据目录 | /var/lib/daoliyu-music | /usr/local/music_player/data |
| 用户 | daoliyu-music | root |
| 日志 | /var/log/daoliyu-music.log | /var/log/mydaoliyu-music.log |
| 启动方式 | 官方 serverctl | 自己写的 uvicorn |
两条服务 systemctl list-units 里同时 running,互不影响:
daoliyu-music.service active running Daoliyu Music Server
mydaoliyu-music.service active running My Daoliyu Music API
端口占用:
LISTEN *:42126 uvicorn
LISTEN [::]:42129 daoliyu-server
五、验证
本机健康检查:
curl http://127.0.0.1:42126/api/health
# {"ok":true,"time":...,"autoScan":true,"scanInterval":1800}
启动日志(应有完整路径回显):
服务启动完成
BASE_DIR = /opt/mydaoliyu_music
MUSIC_ROOT = /usr/local/music_player/music (exists=True)
DATA_DIR = /usr/local/music_player/data (exists=True)
DB_PATH = /usr/local/music_player/data/app.db
TRANSCODE_DIR = /usr/local/music_player/data/transcoded
FFMPEG_BIN = ffmpeg
HTTP = 0.0.0.0:42126
AUTO_FOLDER_PL = True
自动扫描已开启,间隔 1800 秒
开始扫描:/usr/local/music_player/music
文件夹歌单同步完成:新增 0,更新 1,删除 0
扫描完成 用时 0.23s:新增 0,更新 0,跳过 689,移除 0,错误 0
浏览器访问:http://<公网IP>:42126/web
默认账号 admin / admin(登录后立刻改密码)。
六、部署后的效果
- 689 首歌入库,扫描 0.23 秒完成
- 文件夹歌单自动生成:
music/下每个一级目录变成一个歌单,名字就是文件夹名 - 音质切换:底部播放条有「原 / 320 / 128」按钮,实时切
- 转码缓存:切到 320 或 128 后,
data/transcoded/下会生成对应 mp3,下次秒播
转码缓存看一下:
ls -lh /usr/local/music_player/data/transcoded/
# -rw-r--r-- 1 root root 7.2M xxxx_mid.mp3
# -rw-r--r-- 1 root root 2.9M xxxx_low.mp3
七、运维命令速查
# 新服务
systemctl restart mydaoliyu-music
systemctl stop mydaoliyu-music
systemctl status mydaoliyu-music
tail -f /var/log/mydaoliyu-music.log
# 老服务(不动)
systemctl status daoliyu-music
# 手动触发扫描
TOKEN=$(curl -s -X POST http://127.0.0.1:42126/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"xxx"}' | \
python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")
curl -X POST http://127.0.0.1:42126/api/library/scan \
-H "Authorization: Bearer $TOKEN"
# 数据库备份(SQLite WAL 模式要用 .backup)
sqlite3 /usr/local/music_player/data/app.db \
".backup /backup/app.db.$(date +%F)"
八、给后来者的 5 条建议
- 静态 ffmpeg 是自建音乐服务的最优解:不用折腾仓库、不污染系统、删两个文件就干净。
- CentOS 7 的 systemd 是 219,很多新语法(
StandardOutput=append:、LogNamespace=等)不支持。用bash -c 'exec ... >> log'万能。 - 同名服务会打架。部署新版本前先
systemctl list-units | grep xxx看有没有老服务;部署时改名字。 ss -tlnp比netstat靠谱。IPv6 监听、unix socket 都能列出来。- 服务文件写完后必做三连:
daemon-reload→reset-failed→enable --now。少一个都可能加载到老配置。
结语
整个部署从零到跑起来,如果不踩坑,大概 10 分钟。踩了坑大概 30小时。把过程写下来,希望下一个部署的人一路顺风。
