22 KiB
浏览器环境去 Docker:变更评审
1. 状态与结论
- 日期:2026-09-18。
- 评审基线:已同步的
main/origin/main/7e3808cf4ce453b3583079680a3b59ca2ed64ad4;评审时的旧 Docker 入口仅作为历史对照,不能将本文当作真实平台能力证明。 - 评审阶段的契约已在独立实施 worktree 执行。当前状态以实施计划、验证文档和证据记录为准。
- **方向成立,但不是把 Docker 启动命令换成 Xvfb 命令即可。**必须同时替换进程生命周期、运行代次、Profile 路径、端口分配、代理接入及版本管理。
- 下文保留必要的风险、边界和历史对照;不表示真实平台、真实代理或 LAN 已全部验收。详细实现范围见实施计划,验收见验证文档。
已确认的需求
- 不再用 Docker 创建、启动浏览器环境;每台机器由已有 Python gateway 管理本机浏览器,Xvfb 提供虚拟显示。
- 保留已有多 gateway 控制方式,账号/环境仍绑定指定机器。
- 采集浏览器按任务按需启动,任务完成后清理本次创建的临时资源;失败、取消、超时及异常退出也必须处理。
- 不删除需要保留的账号登录状态、稳定指纹、采集结果与正式素材;重启与普通浏览器升级后仍使用原 Profile、指纹及账号身份,不因采集结束关闭无关账号的长期监听。
- 用实测比较启动耗时、内存及磁盘占用;不预先承诺节省比例。
本次边界
- 移除的是浏览器运行环境对 Docker 的依赖。PostgreSQL 或控制面的部署方式不是本次强制重写对象;联调仍优先裸启动 Go、Python、Vite。
- 不增加自动登录、浏览器池、远程桌面新协议、Redis、任务框架、自动跨机迁移或通用资源调度平台。
- 不借此次变更增加认证、网络隔离等安全策略;保留现有身份校验、凭据处理与平台写操作约束。
- 先抖音,再小红书;不能以运行环境改造为由删减
plan01.md的业务范围。
2. 现有实现及其影响
以下是代码定位,不是对真实平台能力的背书。实施前须针对最新工作区复核。
| 现有事实/入口 | 对本次变更的影响 |
|---|---|
internal/environment/store.go 的 Gateway 保存名称、Endpoint;环境仅保留 gateway 绑定 |
多机器注册与路由已存在,应复用;浏览器运行时由目标 gateway 本机配置,不新增第二套节点管理 |
internal/controlplane/api/environments.go 的 startBrowserRuntime、createGatewayRuntime、removeGatewayRuntime 管理启停与清理 |
必须整体改造生命周期,不能仅替换 Python 中一个 Docker 调用 |
gatewayCreatePayload 下发 creatorhub-profile-{alias} 卷名、指纹、代理和绑定版本 |
卷改成 gateway 管理的持久目录;控制面不能下发任意宿主机路径 |
runtimeCleanupStore 及启动前清理逻辑已有 cleanup-pending 概念 |
保留“未清理完成不能冒充已停止”的语义,改用本机资源身份,不再依赖容器/网络 ID |
历史 cmd/docker_gateway/gateway.py 曾初始化 DockerClient 并读取 DOCKER_SOCKET、BROWSER_NETWORK |
已由 browser_gateway/server/http.py 的 native runtime 管理替换;当前浏览器入口不依赖 Docker。该行只保留迁移前事实 |
历史 docker/browser-wrapper/docker-entrypoint.sh 曾固定容器内 Xvfb/端口/Profile |
浏览器 wrapper 已从生产入口删除;native gateway 为每个 runtime 独立分配 display、端口和 Profile,历史行不构成当前能力证明 |
历史 wrapper 曾传入 --no-sandbox |
native gateway 拒绝该参数并以非 root 用户启动;无法满足 sandbox 时直接失败,不回退旧 wrapper |
internal/controlplane/api/creator.go 的采集读取已有运行账号,没有按任务自动创建 runtime;internal/controlplane/api/creator_events.go 独立运行监听 |
需要新增任务使用权;现有 source lease 不是浏览器使用权,直接在采集末尾调用 stop 会误停共享会话 |
internal/controlplane/api/creator.go 的临时下载、internal/controlplane/api/creator_material.go 的 audio.wav.tmp 依赖正常退出删除;素材先写固定路径再提交数据库 token |
控制面本机也需要崩溃清理;旧执行可能覆盖新产物,必须先隔离执行目录与产物发布,不能只增加目录扫描 |
internal/creator/source_lease.go / content.go 的来源 lease 为固定十分钟;部分失败收尾仍使用已取消的 context |
长任务可能重复领取,取消后仍显示 running;资源使用权必须覆盖真实执行期并有独立收尾上下文 |
internal/environment/store.go 的 gateway endpoint 可更新,环境按 gateway 名称重新解析地址 |
不能把名称当稳定机器身份;原 owner 和待清理目标必须保留,禁止把仍有绑定资源的 gateway 改指另一机器 |
internal/environment/store.go 的旧 Image/浏览器版本管理模型与前端入口 |
浏览器安装、默认运行时和路径映射必须由 gateway 宿主机负责;control-plane 不能留下版本登记、路径下发或升级入口 |
scripts/dev-backend.mjs / compose*.yaml 现在只保留 PostgreSQL(Compose)和 host-native gateway 注册 |
联调脚本和部署说明已同步;原生模式不启动 Docker browser gateway |
requirements-gateway.lock 当前锁定 websocket-client |
优先保留已有 CDP/WebSocket 与平台适配器,不因取消 Docker 就新增 Playwright/Patchright |
3. 方案选择
3.1 显示与浏览器
| 方案 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| 每个运行实例独立 Xvfb + 浏览器 | 所有权明确;远程桌面不串窗口;单任务易于关闭和清理 | 比共享 Xvfb 多一个轻量进程,需要分配 display | 推荐首版,满足任务结束释放资源;实际开销纳入对比 |
| 每个 gateway 共用一个 Xvfb | 减少 X server 数量 | 窗口、远程桌面及故障相互影响;不能随单任务清理 | 无实测瓶颈前不采用 |
| 改成全 headless 或复用浏览器池 | 可能进一步减少开销 | 与本次 Xvfb 要求、人工登录和监听生命周期不一致 | 本次不做 |
Xvfb 只负责显示;gateway 负责启动、就绪确认、终止、回收与恢复。浏览器进程存在不等于能执行采集:至少须完成 CDP 就绪、目标页面可用、代理与实际账号身份确认。
3.2 进程归属与异常退出
推荐首版运行在 Linux + systemd:每个 runtime 使用独立的 systemd transient service 管理其 Xvfb、浏览器、可选 x11vnc 及子进程,使用该 runtime 的 cgroup 作为终止与观察边界。Python 通过标准库执行固定的管理命令,不实现通用进程管理平台,也不为每个进程引入第三方库。
- **推荐每个 runtime unit 独立存活,gateway 重启后按持久记录和 unit 身份重新核对。**接近当前容器可独立存活的行为,不默认把 gateway 升级变成所有账号强制断开。
- 短任务使用权必须续租,且有明确最长执行期限;gateway 未及时恢复时,runtime 自身的有界寿命也能终止短任务进程。长期监听不按短任务期限回收,但 gateway/代理中断必须可见,恢复后重新确认事件边界,不能宣称无遗漏。
- gateway、控制面重启及监听重连不得清除自动响应的事件去重记录、基线或已占用 UID 冷却;冷却到期仅允许新事件,旧事件永不重放,迟到事件默认只记录、不自动补发。按
plan01.md的 AC-A9、AC-B1 验证,不能用人工逐次确认的发送测试代替自动策略验收。 - 正常停止先请求浏览器退出,给出有限等待时间,再停止整个 runtime unit;无法停止就报清理失败,不能删除仍在使用的 Profile。
- 磁盘中的运行清单记录 gateway、environment、runtime generation、任务/用途、unit 名及临时目录归属;先登记意图,再创建资源,逐步记录结果。
- gateway 重启先核对自己的 unit、运行记录及代理监听;仅重新识别完整身份匹配的受管实例,不接管任意现存 CDP。恢复失败时明确停止该 runtime 并标记待处理,不能另开同 Profile 浏览器或静默直连。
- 不能只按 PID、进程名或目录年龄清理。PID 会复用,另一台机器可能有同名环境,新一代 runtime 也可能复用端口。
- 更简单的“gateway 服务停止即杀掉全部子进程”会改变现有会话体验,因此不默认采用;若用户明确接受每次 gateway 重启中断全部会话,再缩减为该方案。
- 单纯
subprocess+finally无法处理 gateway 被强制终止或宿主机重启;仅靠进程组也必须证明目标浏览器不会留下脱离进程组的子进程。首版使用 OS 原生进程归属,避免依赖此假设。
这是部署前置条件,不承诺本轮支持 Windows/macOS、无 systemd Linux,也不增加多个进程管理后端。实施前需确认目标机器符合这一条件。
3.3 多机及 gateway 运行时
- 控制面继续选中账号绑定的 gateway;gateway 只管理本机进程、目录、端口和浏览器安装。控制面自行管理它创建的素材临时文件,不能要求 gateway 清理另一机器的路径。
- gateway 需持久稳定的节点身份;显示名称、Endpoint 与节点身份分开。仍有绑定环境、运行实例或待清理资源时,不允许把 Endpoint 改指另一机器。
- runtime 身份至少包含
(gateway, environment, generation);创建、查询、停止、清理及事件都核对同一身份。 - Profile 留在账号绑定机器。gateway 不可达时显示不可达/结果不明,绝不换到另一机器以空 Profile 重建。
- 首版不做跨机复制 Profile、不做共享文件系统、不做自动故障转移。人工重新绑定机器必须说明要重新登录或另行批准搬迁。
- 浏览器二进制按版本在每台 gateway 预安装一次,启动前核实版本可用;任务不得拉镜像、下载或复制整套浏览器。
- 用“浏览器版本”取代“镜像版本”,控制面保存版本标识,gateway 映射到预安装的绝对路径。路径不是用户可自由下发的执行命令。
- **必须取得现有指纹 Chromium 的原生 Linux 发行物、来源、许可证及参数支持证据。**不能偷偷换成普通 Chromium。若只有 Docker 镜像可用,应先解决发行物来源问题,不把启动时解包镜像作为新方案。
4. 生命周期与清理契约
4.1 两种资源寿命,不新增两套环境系统
| 对象 | 何时创建 | 何时回收 | 必须保留的内容 |
|---|---|---|---|
| 账号环境定义、稳定指纹、绑定 gateway | 创建账号/环境 | 用户明确删除相关业务对象 | 不因一次任务结束删除 |
| 账号 Profile | 首次人工登录/启动需要时 | 用户明确删除账号数据;普通停止不删除 | Cookie、Local Storage 等登录与身份必需状态 |
| 任务独占浏览器及 Xvfb | 采集真正开始时 | 本次采集成功、失败、取消、超时后 | 不保留活进程 |
| 匿名采集临时 Profile | 仅明确无需登录的任务需要时 | 随该任务清理 | 无需保留 |
| cache、临时下载、CDP 文件、运行目录、临时截图 | 对应 runtime/任务需要时 | runtime 停止并完成结果提交后 | 正式结果另存,不依赖这些目录 |
| 人工登录会话、持续监听浏览器 | 明确登录/开启监听 | 显式停止、登录期限结束或服务故障 | 长期监听不能被另一个短任务关闭 |
| 作品、评论、线索、审计及用户确认保留的素材 | 业务提交成功时 | 遵循业务删除操作 | 不属于运行时垃圾 |
| 浏览器安装包/共享二进制 | 运维安装版本 | 确认无环境引用后显式卸载 | 不随任务删除,也不每任务复制 |
**一个稳定账号 Profile 同时最多由一个浏览器实例使用。**采集账号与自有账号监听职责分开:优先使用已有指定采集账号,不复制活跃 Profile,不为同一账号另开浏览器。冲突明确排队或拒绝,不悄悄关闭监听。若必须使用已有长期会话,需明确标识为借用:只关闭本任务创建的页面、下载与引用,绝不销毁借入 runtime;不能将该路径伪称为任务独占浏览器已回收。
任务所属的临时资源必须全部归还,不要求把用户早已打开的登录/监听会话一并关闭。排队等待期间不能提前占用 Xvfb、浏览器或临时大文件。
4.2 正常与异常终态
- 获取账号/Profile 使用权与运行代次;落盘创建意图。
- 创建 runtime 临时目录,分配显示/端口,启动 Xvfb/浏览器,核对代理及身份。
- 执行业务,先将结果提交到持久存储。大文件写入本次执行独有、不可覆盖的产物路径,再由数据库核对执行 token 并发布引用;旧 token 不能覆盖新文件。数据库提交结果不明时先查引用,不能猜成未发布后删除。
- 无论业务结果如何,都使用独立且有时限的清理上下文释放任务资源,不能沿用已取消的请求上下文。
- 回收顺序:停止任务及订阅/页面 → 退出浏览器及子进程 → 关闭 Xvfb/可选桌面 → 释放本次代理引用 → 删除已确认属于本代次的临时目录 → 释放锁并记账。
- 清理失败时保存待清理清单、最后错误及重试信息;禁止只写日志然后报“已回收”。同环境新启动必须先解决旧代次冲突。
- 重复取消、重复清理、迟到的旧代次请求必须幂等;旧请求不得结束新 runtime。
业务结果与清理结果分别记录:例如“采集成功,资源待清理”。不能因为删除临时目录失败就重复采集,更不能自动重复执行写操作;也不能因为业务失败就跳过清理。
4.3 磁盘增长不能只靠删除临时目录
- 尽可能把浏览器磁盘缓存、下载与临时文件指向 runtime 私有目录。
--disk-cache-dir不代表浏览器的全部写入都已迁移。 - 持久 Profile 只保留登录/身份相关状态;浏览器版本已验证可重建的缓存,必须等 Profile 解锁后按固定白名单清理。不凭名称猜测并删除未知目录,不删除 Cookie/Local Storage。
- 浏览器日志、截图与诊断材料必须有明确大小/数量/保留期限;不能把垃圾从 runtime 目录转移到日志目录。
- 每台 gateway 启动前检查可用磁盘和运行槽位;不足时明确拒绝/等待,不通过删除登录资料腾空间。
- 未知目录不自动
rm -rf。目录归属、路径边界和符号链接校验属于防止误删的必要条件,不是新增安全产品。 - 验收分别统计临时目录、Profile、诊断日志和正式素材;正式采集结果的合理增长不能算泄漏,登录 Profile 的持续无界增长也不能被忽略。
5. 不能漏掉的能力替代
| 风险 | 处理要求 | 放行条件 |
|---|---|---|
| 容器内固定 display/5900/9222 在宿主机冲突 | 由 runtime 独占分配,优先使用操作系统/工具提供的空闲分配能力;并发就绪确认,不用“先探测再假定可用” | 同机器两个以上 runtime 不串端口/窗口 |
| 容器 ID / network ID 被业务当运行身份 | 统一改为 runtime generation 与代理绑定版本;不复用 PID 冒充稳定 ID | 迟到请求、重启、并发启动不误删新实例 |
| 代理以前依赖 Docker 网段/容器地址 | 复用 Python 代理转发;浏览器显式连接本机对应代理端点;代次核对继续保留 | A09/B03/D05 按 HTTP/HTTPS/SOCKS4/SOCKS5 及协议支持的认证配置分项记录;实际浏览器出口、认证失败、网络失败均有证据,失败不直连(AC-E3) |
| 原生版本切换重置账号资料 | 停止后执行普通升级,保留原 Profile、稳定指纹与绑定账号,不通过重建环境升级 | A02 核对实际版本已切换、原 Profile/指纹/登录身份保持(AC-E1);不含首次 Docker 切换的数据处置 |
| 把 Docker 网络隔离等价替换成浏览器 sandbox | 明确移除旧隔离描述;sandbox 不能提供原容器的网络/文件系统边界 | 不作隔离等价承诺;如仍要求系统级禁直连,需要另行确认,而不是暗加防火墙 |
--no-sandbox 直接继承 |
非 root 运行,确认内核/浏览器支持正常 sandbox;不能启动就报前置条件不满足,不静默禁用 | 使用实际指纹浏览器证明 sandbox 配置有效 |
| wrapper 有 RFB 服务,但源码未证明网页远程桌面已接通 | 保留已实现的二维码/CDP 页面动作;如要求完整网页远程桌面,单独确认后使用成熟链路,不冒称已有 | 登录、二维码保持;桌面仅在批准的范围内验收 |
| gateway 消失后浏览器仍在运行 | OS 管理完整运行组;短任务有最长寿命;恢复时识别受管实例并清理失去所有者的资源 | 无永久失管进程;存活监听恢复或明确报告中断 |
| 所有采集结束都通用关闭浏览器 | 任务独占与借入长期会话明确区分;共享代理引用计数不误关 | 常驻监听、其他任务、另一机器不受清理影响 |
| 出口探测在控制面执行、gateway 代理 ready 仅证明监听 | 从实际执行 gateway 的浏览器路径验证代理和身份;控制面探测不能代表远端机器可用 | A/B 机器各自出网检查有证据,不能只复用中央检测结果 |
| 素材临时文件和旧执行产物相互覆盖 | 每个执行独有目录/不可变产物引用,发布受 token 约束;清理也校验归属 | 旧执行恢复、清理、超时不破坏新产物 |
| 多机快照串行等待拖慢续租 | 保留既有心跳,按节点隔离失败与时限;只有验证证明需要时增加有界并发,不新建调度系统 | A 不可达时 B 续租、采集和监听不受拖累 |
| UI 显示完成但后台仍占磁盘 | P2 即交付最小接口适配、启停/采集入口及业务与清理双状态,失败有明确错误;P4 完善完整管理界面 | P2 的单节点闭环可从界面操作并查看停止、待清理和失败,不依赖 P4 才能验收;清理失败不会显示为完全成功 |
6. 改造评审结论与放行项
**已采用:每台 gateway 管理本机独立 Xvfb/runtime,保留账号 Profile,采集结束回收任务资源,复用现有多机路由及业务层。**单节点代码、自动检查和 native gateway smoke 已完成;真实账号、代理、LAN、多节点和性能验收按验证文档分开记录。
代码实施前须确认:
- 目标机器为满足 systemd、Xvfb、sandbox 条件的 Linux,原生指纹浏览器可合法部署且能力相同;已完成非 root native smoke。
- 已接受删除 Docker 浏览器代码路径、镜像字段/接口及相关部署配置;开发数据可受控重建,用户数据删除仍需逐次授权。
- Profile 不随采集删除、机器不自动切换、任务不得抢占人工登录/长期监听;gateway 重启按独立 runtime 核对恢复。
- 本次不把完整网页远程桌面当作已有能力;若要求补齐,另列明确验收范围。
- 验证文档中的资源预算已固定;性能改善和真实平台能力未以无旧基线的推测代替实测。
以上是已执行的实施放行记录;真实平台、代理、LAN 和破坏性资源用例仍需授权操作者按验证文档补齐,未执行项不视为通过。
7. 专项评审处理记录
本轮进行了两项独立只读代码评审:gateway/多机生命周期,以及采集/素材资源清理。它们审查的是实现与改造风险,不是新实现的真实平台证明;自动检查和 native smoke 已记录,真实手工验收仍按验证文档执行。
| 发现 | 处理 |
|---|---|
| gateway 和 Docker runtime 寿命不同,直接绑定父子进程会改变重启体验 | 采用独立 runtime unit,核对恢复;短任务有有界寿命 |
| Xvfb wrapper 与实际强制入口不同,远程桌面链路无完整证据 | 不冒称现有能力;原生启动链单独验证,完整桌面另行确认 |
| source lease 不能代表浏览器所有权,短任务会误停长期会话 | 明确任务自建/借入与使用权,P3 验证并发和监听 |
| 控制面也有临时文件,旧素材执行可能覆盖新产物 | 先做执行目录与不可变产物发布,再做残留清理 |
| gateway 名称/Endpoint 可变,旧清理可能指向新机器 | 固定节点身份与资源 owner,阻止有资源时更换机器 |
| 中央出口检查不能证明远端浏览器出网正确 | 从 A/B 实际执行路径分别验证,不复用中央结果冒充 |
P0 的发行物、目标机器条件、契约和资源参数已记录;性能基线以及未执行的真实平台/代理/故障测试仍全部保留为未验证。
8. 成熟方案参考与证据边界
- Xvfb/xvfb-run 手册:虚拟显示、自动 display 分配与 X server 生命周期;不将它当浏览器业务管理器。
- systemd.kill、systemd-run:使用系统已有的进程归属和终止机制。
- Playwright persistent context 文档:持久 user-data-dir 独占与上下文寿命的成熟约定;引用约定不代表引入该依赖。
- Chromium Linux sandbox:sandbox 是浏览器防护机制,不等于 Docker 环境隔离。
本轮取得检索结果,但直接抓取上述页面被当前工具的代理地址校验拦截;不将未取得的全文或发行版行为当作已验证证据。实施 P0 须用目标机器的手册、浏览器文档与小规模验证补齐。这里只引用文档,不复用上游代码;若后续复制脚本/发行物,必须记录具体来源、版本和许可证。