Files
rakuten-api/docker-compose.yml

156 lines
7.6 KiB
YAML

# 抓取与有状态端部署
#
# 默认起抓取服务 scraping(:31107)与交易服务 trading(:31108):
# docker compose up -d
# 生产环境仍可按部署位置分别启动:服务器侧 `docker compose up -d scraping`,
# 本地机侧 `docker compose up -d trading`。抓取服务匿名无状态,可独立多开。
# 下单任务网关按设计部署在**服务器侧**(与抓取服务同机),是另一个部署单元,
# 所以放在 gateway profile 里,不会被默认启动。真要在这台机上一起跑(自包含
# 联调 / 单机部署)时:
# docker compose --profile gateway up -d
#
# 两个服务都**只能单实例**:登录态 cookie 全局唯一、订单监控是常驻轮询、SQLite
# 单连接 + 全局并发度 1。不要 `--scale`,不要在前面挂多副本。
#
# 部署前置(缺一样服务就跑不通,不是可选项):
# 1. cp .env.example .env,至少填 RAKUTEN_BEARER_TOKEN / RAKUTEN_ORDER_GATEWAY_URL /
# RAKUTEN_SCRAPER_BASE_URL(后两个留空则 worker 不启动,只剩购物车与登录态接口)。
# 2. cp account.yaml.example account.yaml 并填真实凭据(session upgrade 复核密码、
# 手机号补录、支付方式核对都要读它)。**必须先建文件再 up**,否则 Docker 会
# 把这个挂载点当目录创建,容器里读到的是个空目录。
# 3. 登录态 cookie(.auth/rakuten_state.json):**不必**先在宿主机准备——
# RAKUTEN_AUTO_LOGIN_ON_START 默认开,容器起来就按 account.yaml 自己登一次。
# 撞 reCAPTCHA / 设备验证会停在浏览器里等人工,这时把 RAKUTEN_VNC_ENABLED 设成
# true(配 RAKUTEN_VNC_PASSWORD)连 127.0.0.1:5900 接管,完成后:
# curl -X POST -H "Authorization: Bearer $TOKEN" http://127.0.0.1:31108/api/auth/login
# 也可以照旧在宿主机跑 scripts/login.py 再挂载进来,两条路都行。
#
# 敏感面:.auth/(可直接冒充账号的 cookie)、account.yaml(明文密码+卡号)、
# data/(真实姓名地址等 PII 与证据快照)全在宿主机目录里,注意宿主机权限与备份加密。
services:
scraping:
# 商品获取 API:乐天市场与ラクマ抓取,匿名无状态,可独立扩容。
image: ${RAKUTEN_SCRAPING_IMAGE:-git.jerryyan.net/jp/rakuten-api}:${RAKUTEN_SCRAPING_TAG:-latest}
build:
context: .
dockerfile: Dockerfile
container_name: rakuten-api
restart: unless-stopped
env_file:
- .env
environment:
RAKUTEN_APP_HOST: 0.0.0.0
RAKUTEN_APP_PORT: 31107
RAKUTEN_APP_ENV: prod
ports:
# 默认仅供本机反向代理访问;需要直连时显式设 RAKUTEN_SCRAPING_BIND。
- "${RAKUTEN_SCRAPING_BIND:-127.0.0.1}:31107:31107"
volumes:
- ./logs:/app/logs
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
trading:
# 交易服务:加购 → 下单 → 付款 → 订单监控,以及 /api/auth/* /api/cart/*
image: ${RAKUTEN_TRADING_IMAGE:-git.jerryyan.net/jp/rakuten-trading}:${RAKUTEN_TRADING_TAG:-latest}
build:
context: .
dockerfile: Dockerfile.trading
container_name: rakuten-trading
restart: unless-stopped
# PID 1 用 docker-init:Chromium 会派生一堆子进程,崩溃时需要有人收尸
init: true
# Chromium 默认 /dev/shm 只有 64MB,渲染稍重的结算页会直接 crash
shm_size: 1gb
# 停容器时给正在执行的下单任务留出收尾时间(uvicorn 关 lifespan → worker 停轮询
# → 取消订单监控 → 关浏览器)。**停容器前最好先确认没有在途任务**:进程被硬杀
# 时站点侧可能已经提交成功,本地没记录,恢复要走 verify_on_site 人工核对。
stop_grace_period: 120s
env_file:
- .env
environment:
# 容器内必须监听 0.0.0.0,对外只从 127.0.0.1 映射(见 ports)
RAKUTEN_TRADING_HOST: 0.0.0.0
RAKUTEN_TRADING_PORT: 31108
RAKUTEN_HEALTH_PORT: 31108
RAKUTEN_APP_ENV: prod
# worker 标识:默认取主机名,容器主机名是随机 ID,重建就变——显式固定,
# 便于在网关侧对上是哪台机在领任务
RAKUTEN_WORKER_ID: ${RAKUTEN_WORKER_ID:-rakuten-trading-docker}
# 有头 Chromium 需要虚拟显示(镜像内 Xvfb),别关
RAKUTEN_XVFB_ENABLED: "true"
# 启动即按 account.yaml 自动登录(后台跑,不阻塞端口)。撞 reCAPTCHA / 设备验证
# 时会停在浏览器里等人工——那时开 VNC 接管,或事后调 POST /api/auth/login 重试。
RAKUTEN_AUTO_LOGIN_ON_START: ${RAKUTEN_AUTO_LOGIN_ON_START:-true}
RAKUTEN_RELOGIN_ENABLED: ${RAKUTEN_RELOGIN_ENABLED:-true}
# 需要远程盯着浏览器(登录验证码、3DS、needs_human 人工接管)时改成 true,
# 并设 RAKUTEN_VNC_PASSWORD;端口只映射到 127.0.0.1,走 SSH 隧道访问
RAKUTEN_VNC_ENABLED: ${RAKUTEN_VNC_ENABLED:-false}
RAKUTEN_VNC_PASSWORD: ${RAKUTEN_VNC_PASSWORD:-}
ports:
# 这些接口能操作真实账号,只对本机开放;要跨机访问请走内网地址或 SSH 隧道
- "127.0.0.1:31108:31108"
- "127.0.0.1:5900:5900"
volumes:
# 登录态 cookie:容器要能写(自动重登会重写 storage_state)
- ./.auth:/app/.auth
# 账号凭据:只读挂载。文件必须先存在,见文件头「部署前置」第 2 条
- ./account.yaml:/app/account.yaml:ro
# login.py 的持久化浏览器目录(保住设备指纹,减少重登触发风控核验)
- ./.browser-data:/app/.browser-data
# 订单 SQLite(执行事实的权威记录)+ 证据快照,丢了没法追溯下过什么单
- ./data:/app/data
- ./logs:/app/logs
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
# Chromium 的 namespace sandbox 在容器里可能被 seccomp/AppArmor 挡住(表现为
# 浏览器起不来、Playwright 报 Target closed)。宿主机内核不允许非特权 user
# namespace 时,按下面两条择一放行;两条都不想放,则需要在
# site_interact.py 的 launch(args=...) 里加 --no-sandbox(改生产代码,需另行决定)。
# security_opt:
# - seccomp:unconfined
# cap_add:
# - SYS_ADMIN
gateway:
# 下单任务网关:任务队列 + 状态镜像,上游业务系统在这里提交下单任务与查任务状态。
# 正式拓扑里它在服务器侧、trading 在本地,两者靠出站长轮询联通(docs/order-gateway.md);
# 这里只是为了单机自包含跑起来,默认不启动。
profiles: ["gateway"]
image: ${RAKUTEN_TRADING_IMAGE:-git.jerryyan.net/jp/rakuten-trading}:${RAKUTEN_TRADING_TAG:-latest}
build:
context: .
dockerfile: Dockerfile.trading
container_name: rakuten-gateway
restart: unless-stopped
init: true
command: ["python", "-m", "app.gateway.main"]
env_file:
- .env
environment:
RAKUTEN_GATEWAY_HOST: 0.0.0.0
RAKUTEN_GATEWAY_PORT: 31109
RAKUTEN_HEALTH_PORT: 31109
RAKUTEN_APP_ENV: prod
# 网关不碰浏览器,跳过 Xvfb
RAKUTEN_XVFB_ENABLED: "false"
ports:
# 上游业务系统要能调到;对外暴露时请在反代上收紧来源,鉴权只有一个 Bearer token
- "${RAKUTEN_GATEWAY_BIND:-127.0.0.1}:31109:31109"
volumes:
# 任务队列 SQLite(data/gateway.db)必须持久化,丢了等于丢一批下单任务
- ./data:/app/data
- ./logs:/app/logs
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"