多店铺运维
rCommerce 的设计目标是一个二进制,多家店铺。每家店铺是一个
独立实例——各自的端口、配置文件、数据库和服务——共享同一个
部署的二进制。本文以同一台主机上运行两家示例店铺 shop-a
(store-a.example.com)和 shop-b(store-b.example.com)为例。
| 店铺 | 端口 | 配置 | 数据库 | Uploads | 日志 |
|---|---|---|---|---|---|
| shop-a | 8080 | /usr/local/etc/shop-a/config.toml | shop_a | /var/db/shop-a/uploads | /var/log/shop-a/ |
| shop-b | 8081 | /usr/local/etc/shop-b/config.toml | shop_b | /var/db/shop-b/uploads | /var/log/shop-b/ |
每个 config.toml 承载该店铺的全部专属配置:自己的 [database]
凭据、[branding](名称、客服邮箱、站点 URL、logo、主题色——驱动
邮件模板和账单品牌)、自己的 [payment.*] 支付网关密钥、
[notifications.email] 下自己的邮件服务商 token,以及店铺级行为如
[orders] auto_cancel_pending_minutes。配置文件为
root:wheel 644;服务以 rcommerce 用户运行,需要能够读取它们。
每个店铺的 API 密钥保存在自己的数据库中,因此为某店铺签发的密钥 永远无法触碰其他店铺。
服务(FreeBSD rc.d)
Section titled “服务(FreeBSD rc.d)”在 FreeBSD 上每个实例是一个 rc.d 服务。按店铺复制服务脚本,修改
名称、配置路径、数据目录和日志文件。示例
/usr/local/etc/rc.d/rcommerce-b:
#!/bin/sh## PROVIDE: rcommerce_b# REQUIRE: LOGIN# KEYWORD: shutdown
. /etc/rc.subr
name="rcommerce_b"desc="rCommerce API server (shop-b)"rcommand="/usr/local/sbin/rcommerce"pidfile="/var/run/${name}/${name}.pid"
load_rc_config $name
: ${rcommerce_b_enable="NO"}: ${rcommerce_b_config="/usr/local/etc/shop-b/config.toml"}: ${rcommerce_b_data_dir="/var/db/shop-b"}: ${rcommerce_b_log_file="/var/log/shop-b/server.log"}
start_cmd="${name}_start"stop_cmd="${name}_stop"status_cmd="${name}_status"
rcommerce_b_start(){ /usr/bin/install -d -o rcommerce -g rcommerce -m 750 /var/run/${name} /usr/bin/install -d -o rcommerce -g rcommerce -m 750 ${rcommerce_b_data_dir} /usr/bin/install -d -o rcommerce -g rcommerce -m 750 $(dirname ${rcommerce_b_log_file})
echo "Starting ${desc}."
/usr/sbin/daemon \ -f \ -S \ -p ${pidfile} \ -u rcommerce \ -t ${name} \ env RCOMMERCE_CONFIG="${rcommerce_b_config}" \ IMAGE_BASE_URL="https://images.store-b.example.com" \ ${rcommand}}
rcommerce_b_stop(){ echo "Stopping ${desc}." /usr/bin/kill -TERM $(cat ${pidfile})}
rcommerce_b_status(){ if [ -f ${pidfile} ]; then if /bin/pkill -0 -F ${pidfile} >/dev/null 2>&1; then echo "${desc} is running as pid $(cat ${pidfile})." return 0 else echo "${desc} is not running but pid file exists." return 1 fi else echo "${desc} is not running." return 1 fi}
run_rc_command "$1"在 /etc/rc.conf 中启用:
rcommerce_a_enable="YES"rcommerce_b_enable="YES"在 Linux 上同样的模式对应每个店铺一个 systemd unit(复制
Systemd 服务的 unit 文件,修改
Description、ExecStart 的配置路径和 unit 名称)。
任何重启之后,务必确认新进程真的替换了旧进程
(ps aux | grep "rcommerce server" —— PID 和启动时间必须是新
的;“重启成功”背后藏着一个没死透的旧进程的坑已经踩过一次),并
对每个端口请求 /health。
反向代理(Caddy)
Section titled “反向代理(Caddy)”一个 Caddyfile 服务所有店铺。每个 vhost 终结 TLS(Caddy 自动申请
Let’s Encrypt 证书,包括 DNS 走 Cloudflare 代理时通过 HTTP-01 申请),
为 /uploads/* 服务该店铺 uploads 目录下的静态文件,其余请求
反向代理到该店铺的端口。注意 root 指向 uploads/ 的父
目录:
api.store-a.example.com { root * /var/db/shop-a file_server /uploads/* reverse_proxy 127.0.0.1:8080}
api.store-b.example.com { root * /var/db/shop-b file_server /uploads/* reverse_proxy 127.0.0.1:8081}file_server 块是 R2/CDN 图片托管之前遗留的图片 URL 的兜底——
即使图片迁移到 R2 之后也要保留。
升级共享二进制
Section titled “升级共享二进制”所有实例共享 /usr/local/sbin/rcommerce,因此一次升级需要重启
所有店铺。针对宿主机目标原生构建——绝不要在生产机上构建。
FreeBSD 示例:
# 1. 在构建机上构建(例如有源码 checkout 的远程构建机)cargo build --release --bin rcommerce --target x86_64-unknown-freebsd# 仓库的 .cargo/config.toml 可能默认其他宿主 target,# 务必显式传入 target。
# 2. 传输二进制scp target/x86_64-unknown-freebsd/release/rcommerce shopserver:/tmp/rcommerce-new
# 3. 重启所有实例(逐个停、替换、逐个起)ssh shopserver ' for s in rcommerce-a rcommerce-b; do sudo service $s stop; sleep 2; done sudo mv /tmp/rcommerce-new /usr/local/sbin/rcommerce && sudo chmod +x /usr/local/sbin/rcommerce for s in rcommerce-a rcommerce-b; do sudo service $s start; done'迁移在每次实例启动时自动执行,作用于它自己的数据库——如果有店铺 存在待执行的 schema 变更,请在低峰期升级。
部署后检查清单:
ps aux | grep "rcommerce server"—— 所有实例都显示新的 PID。- 对每个店铺
curl -sf https://api.<store>.com/health。 - 查看
/var/log/<store>/server.log有无迁移或启动错误。