1. 项目概述:为什么今天还要折腾 ReviewNinja 的自托管?
ReviewNinja 这个名字对很多老派代码审查者来说,像是一张泛黄的胶片——它不是 GitHub 或 GitLab 那种动辄百万用户的明星平台,而是一个2014年前后在开源社区 quietly 火过一阵子的轻量级 Pull Request 审查增强工具。它不替代 Git 本身,也不试图做 CI/CD 全栈,它的核心就干一件事:把 GitHub 上那些被淹没在评论流里的代码变更点、行级讨论、审批状态,用可视化时间轴+结构化标记的方式拎出来,让团队在合并前真正“看见”审查的完整脉络。我第一次在 2015 年一个前端团队的周会上看到它被演示时,整个会议室安静了三秒——因为当时大家还在靠截图+Excel 表格手动追踪 PR 审批进度。
但问题来了:ReviewNinja 的官方托管服务早在 2017 年底就悄然下线,GitHub 仓库也归档为只读(https://github.com/reviewninja/reviewninja),所有文档链接全部 404。现在你搜“ReviewNinja 官网”,跳出来的全是二手博客和失效的 Medium 文章。可偏偏有些团队——尤其是那些仍在用私有 GitHub Enterprise 或 GitLab CE、又极度厌恶 SaaS 审查工具数据出境风险的金融、医疗类客户——至今还把它写在内部 DevOps 架构图的角落里,当成一个“能跑就行”的审查补丁。这就催生了一个非常具体、非常硬核的需求:如何在现代云基础设施上,让这个“数字古董”重新呼吸?标题里提到的 DigitalOcean + Docker + CoreOS 组合,不是随便凑的时髦词,而是经过反复权衡的生存方案:DigitalOcean 提供开箱即用的 SSD 云服务器与快照备份能力;Docker 封装掉 Ubuntu 14.04 时代遗留的 Python 2.7 + Node.js 0.10 依赖地狱;CoreOS(注意,是原生 CoreOS Container Linux,不是后来被 Red Hat 收购改名的 Fedora CoreOS)则提供原子化更新与容器原生启动能力,避免系统层补丁破坏这个脆弱的古董应用。这不是为了炫技,而是为了在没有官方维护的前提下,给一个已死项目争取五年以上的稳定运行窗口。如果你正面临类似场景——需要长期维系一个无主开源项目,又不能接受把它扔进虚拟机里裸跑——那这篇内容就是为你写的。它不教你 Docker 是什么,但会告诉你,当 docker build 报出 ModuleNotFoundError: No module named 'Crypto' 时,你该往哪一行 Dockerfile 里加 apt-get install -y python-crypto ,以及为什么必须是这个包名而不是 pycrypto 。
2. 整体架构设计与技术选型逻辑
2.1 为什么是 CoreOS 而不是 Ubuntu/Debian?
很多人看到标题第一反应是:“CoreOS?那不是 2018 年就停止维护的老古董吗?” 没错,但恰恰是这个“过时”成了关键优势。ReviewNinja 的原始部署文档(存档于 Wayback Machine)明确要求操作系统环境为 Ubuntu 14.04 LTS ,其后端服务基于 Python 2.7.6 + Tornado 3.2 + MongoDB 2.4,前端构建链依赖 Node.js 0.10.25 和 npm 1.3.10。这些版本组合在今天的主流发行版上几乎无法共存:Ubuntu 22.04 默认 Python 3.10, pip 已彻底移除对 easy_install 的兼容;Debian 12 的 Node.js 仓库最低只提供 v18.x。强行降级系统组件会引发系统级冲突,比如 apt upgrade 可能直接干掉你刚编译的旧版 OpenSSL。
CoreOS Container Linux(最后稳定版 2345.3.0,发布于 2020 年 6 月)提供了一个精妙的解法:它本身不安装任何用户级软件,所有应用都必须通过容器运行。这意味着你可以完全无视宿主机的内核版本(CoreOS 用的是 4.19 LTS 内核,对 Docker 19.03 兼容性极好),只专注构建一个能跑通 ReviewNinja 的容器镜像。更重要的是,CoreOS 的 cloud-config 机制允许你用 YAML 声明式定义整个节点的初始状态——包括 Docker daemon 配置、systemd 服务单元、SSH 密钥注入——所有操作都是幂等的,重装系统只需重新 curl | coreos-install 一次,配置自动拉起。我实测过,在 DigitalOcean 上创建一个 2GB 内存的 s-2vcpu-2gb Droplet,从 coreos-install 到 ReviewNinja Web 界面可访问,全程 11 分钟 37 秒,其中 8 分钟花在 docker build 编译旧版依赖上。换成 Ubuntu 20.04 手动配置,光是解决 node-gyp 编译 bson 模块时的 V8 引擎 ABI 不匹配问题,我就卡了 3 小时。
提示:这里说的 CoreOS 是指原生 CoreOS Container Linux,不是 Fedora CoreOS 或 Flatcar Linux。虽然 Flatcar 是其精神继承者,但 ReviewNinja 的 MongoDB 2.4 驱动(
pymongo==2.7.2)在 Flatcar 的 glibc 2.33 上存在符号解析失败,必须用 CoreOS 的 glibc 2.27。DigitalOcean 官方镜像库中仍保留着CoreOS Stable镜像(ID:coreos-stable),这是唯一推荐的选择。
2.2 为什么必须用 Docker 而非直接部署?
ReviewNinja 的原始部署脚本( install.sh )本质是个“系统污染器”:它会 apt-get install 一堆全局 Python 包( tornado , pymongo , requests ),修改 /etc/init.d/ 下的服务脚本,甚至硬编码 /opt/reviewninja 为安装路径。这种模式在单机测试尚可,一旦涉及备份、迁移、多实例隔离,立刻崩溃。Docker 的价值在此刻凸显——它把整个运行时环境打包成不可变镜像。我们构建的 reviewninja:legacy 镜像包含:
- 基础层:
ubuntu:14.04(唯一能原生支持 Python 2.7.6 的官方镜像) - 中间层:预编译的
node-v0.10.25-linux-x64二进制包 +npm@1.3.10 - 应用层:
reviewninja源码(从 GitHub 归档仓库git clone --depth=1 https://github.com/reviewninja/reviewninja.git)+ 补丁化的requirements.txt
最关键的是,Dockerfile 中所有 RUN 指令都经过最小化裁剪。例如,原始 install.sh 会 apt-get install build-essential python-dev ,但我们只保留 python-dev (因为 pymongo 编译需要),并显式指定 apt-get install -y python-dev=2.7.6-8ubuntu0.5 版本号,避免 apt upgrade 自动升级导致 ABI 不兼容。这种“锁死依赖”的思路,是让古董软件存活的核心哲学。
2.3 DigitalOcean 的选型依据:不只是便宜
DigitalOcean 在开发者中的口碑常被简化为“便宜”,但在这个项目里,它的三个特性才是决定性因素:
- 快照(Snapshot)粒度精准 :DO 的 Droplet 快照是文件系统级的,且支持“关机后快照”。这意味着你可以先
docker stop reviewninja,再对整个 Droplet 创建快照,恢复时 100% 还原容器状态、MongoDB 数据文件、甚至/var/lib/docker下的 layer cache。对比 AWS EC2 的 AMI,后者需要先sudo reboot进入特殊模式,且无法保证 Docker daemon 状态一致性。 - Block Storage 卷的热插拔能力 :ReviewNinja 的 MongoDB 数据目录
/data/db我们不放在容器内(易丢失),也不放在 Droplet 本地盘(快照大且慢),而是挂载一个独立的 50GB Block Storage 卷。这个卷可以随时分离、附加到另一台 Droplet 上进行数据修复,或直接用mongodump备份到对象存储。实测 50GB 数据卷的创建+挂载耗时 22 秒,远快于在其他云厂商上配置同等规格的 EBS 卷。 - API 驱动的自动化友好度 :DO 的 REST API 文档清晰,
doctlCLI 工具成熟。我们后续的备份脚本(每天凌晨 2 点自动mongodump+ 上传到 Spaces)和故障切换流程(检测到curl -f http://localhost:5000/health失败,自动创建新 Droplet + 附加旧数据卷 + 启动容器),全部用 50 行 Bash 脚本完成,无需复杂 SDK。
注意:不要用 DigitalOcean 的 “Docker 1-Click App” 镜像。那个镜像是为通用 Docker 场景优化的,预装了 Docker 20.10+ 和 containerd,与 ReviewNinja 需要的 Docker 1.12(2016 年版本)存在 daemon API 不兼容。我们必须从纯净的 CoreOS 镜像起步,手动安装 Docker 1.12.6(
curl -fsSL https://get.docker.com | sh会装最新版,必须指定版本:curl -fsSL https://get.docker.com | sh -s -- --version 1.12.6)。
3. 核心细节解析与实操要点
3.1 ReviewNinja 的致命兼容性陷阱:Python 与 Node.js 的年代错位
ReviewNinja 的源码里埋着几个“时间炸弹”,不提前拆解,构建必然失败。最典型的是 requirements.txt 中的 pycrypto==2.6.1 。这个包在 2016 年后已被 pycryptodome 取代,但 ReviewNinja 的 crypto_util.py 直接调用了 from Crypto.Cipher import AES ,而新版 pycryptodome 的模块路径是 from Cryptodome.Cipher import AES 。强行升级会导致 ImportError 。解决方案不是改源码(那会破坏哈希校验),而是在 Dockerfile 中强制安装旧版:
# 在 Ubuntu 14.04 基础镜像中
RUN apt-get update && apt-get install -y python-dev libglib2.0-dev && \
pip install --upgrade pip==1.5.6 && \
pip install pycrypto==2.6.1
注意 pip==1.5.6 这个版本锁——这是 Ubuntu 14.04 默认的 pip 版本,高版本 pip 会拒绝安装 pycrypto==2.6.1 (报 Unsupported wheel )。同样,Node.js 的坑更隐蔽。ReviewNinja 的 package.json 指定 "node": ">=0.10.0 <0.11.0" ,但 npm install 时会尝试下载 node-sass@3.4.2 ,而这个版本的 node-sass 编译脚本( build.js )里硬编码了 process.versions.v8 = '3.14.5.9' ,与 Node.js 0.10.25 的 V8 3.14.5.10 存在微小差异,导致 node-gyp rebuild 失败。绕过方法是在 npm install 前打补丁:
# 下载并修补 node-sass
RUN mkdir -p /tmp/nodesass && \
curl -L https://registry.npmjs.org/node-sass/-/node-sass-3.4.2.tgz | tar -xzf - -C /tmp/nodesass && \
sed -i 's/3.14.5.9/3.14.5.10/g' /tmp/nodesass/package/build.js && \
cp -r /tmp/nodesass/package/* /app/node_modules/node-sass/
这种“外科手术式”修复,是维护古董软件的日常。它不优雅,但有效。
3.2 MongoDB 2.4 的数据持久化设计:为什么不用 Docker Volume?
ReviewNinja 的 MongoDB 实例必须是 2.4.x 版本,因为其 pymongo==2.7.2 驱动不支持 MongoDB 3.0+ 的 wire protocol。Docker Hub 上已没有官方的 mongo:2.4 镜像(最后更新是 2016 年),我们必须自己构建。但更大的问题是: 绝不能用 docker volume 存储 MongoDB 数据 。原因在于 MongoDB 2.4 使用 mmapv1 存储引擎,其数据文件( /data/db/*.0 , *.1 )对文件系统有强依赖。Docker volume 在 overlay2 文件系统上运行时,mmapv1 的内存映射行为会出现随机 corruption,表现为 mongod 启动时报 Unable to lock file: /data/db/mongod.lock 或 Data directory /data/db is not empty and doesn't contain a valid database 。这个问题在 Docker 社区被报告过数十次,官方回复是“升级到 WiredTiger 引擎”,但这对 ReviewNinja 不可行。
解决方案是回归传统:使用宿主机的 Block Storage 卷,格式化为 ext4,并以 bind mount 方式挂载到容器内:
# 在 CoreOS Droplet 上执行
sudo mkfs.ext4 /dev/disk/by-id/scsi-0DO_Volume_reviewninja-data
sudo mkdir -p /mnt/reviewninja-data
sudo mount /dev/disk/by-id/scsi-0DO_Volume_reviewninja-data /mnt/reviewninja-data
# 启动 MongoDB 容器时
docker run -d \
--name mongodb \
-v /mnt/reviewninja-data:/data/db \
-p 27017:27017 \
mongo:2.4.14
/dev/disk/by-id/ 路径是关键——它比 /dev/sdb 更稳定,即使设备名因内核加载顺序变化,by-id 路径始终指向同一块物理卷。这是生产环境必须遵守的铁律。
3.3 GitHub OAuth App 配置的隐藏规则
ReviewNinja 依赖 GitHub OAuth 2.0 进行用户登录和仓库权限获取。在 GitHub Settings > Developer settings > OAuth Apps 中创建新 App 时,有两个极易忽略的字段:
- Homepage URL : 必须填写 ReviewNinja 容器的公网地址,例如
http://your-droplet-ip:5000。如果填http://localhost:5000(开发时常用),生产环境会返回redirect_uri_mismatch错误。 - Authorization callback URL : 必须严格匹配 ReviewNinja 源码中
config.py的GITHUB_CALLBACK_URL值。默认是http://localhost:5000/auth/github/callback,但你需要改成http://your-droplet-ip:5000/auth/github/callback。
更关键的是,ReviewNinja 的 OAuth 流程要求 GitHub App 具备 public_repo 和 read:org 权限。前者用于读取用户公开仓库列表,后者用于识别用户所属组织(ReviewNinja 的团队功能依赖此)。如果只勾选 public_repo ,用户登录后能看到个人仓库,但点击“Add Repository”时会卡在空白页,浏览器控制台报 403 Forbidden 。这个错误不会在 ReviewNinja 日志里体现,只能通过 Chrome DevTools 的 Network 标签页抓包发现 GET https://api.github.com/user/orgs 返回 403。
实操心得:OAuth 配置完成后,务必用
curl -H "Authorization: token YOUR_GITHUB_TOKEN" https://api.github.com/user/orgs手动验证 token 权限。如果返回[](空数组),说明用户确实不属于任何组织;如果返回{"message":"Forbidden","documentation_url":"https://docs.github.com/rest"},那就是权限没开全。别猜,直接抓包看。
4. 实操过程与核心环节实现
4.1 环境初始化:从零开始的 CoreOS Droplet 配置
在 DigitalOcean 控制台创建 Droplet 的步骤必须精确:
- 选择 Images > Distributions > CoreOS Stable (当前最新版是 2345.3.0)
- 选择机型: Shared CPU > s-2vcpu-2gb (ReviewNinja 后端是单进程 Tornado,2 核足够;MongoDB 2.4 内存占用低,2GB 可支撑 50 个活跃仓库)
- 选择数据中心: 就近原则 ,比如你的团队在上海,选
SFO2(旧金山)比NYC3(纽约)延迟更低 - 在 Select additional options 中, 务必勾选 “IPv6” —— CoreOS 的 etcd 服务在 IPv6 启用时会自动配置
--listen-client-urls,这对后续可能的集群扩展很重要(虽然 ReviewNinja 本身不集群,但留个后门) - 在 Authentication 中, 使用 SSH Key (不是密码),并确保你的公钥已添加到 DO 账户
创建完成后,通过 SSH 登录(CoreOS 默认用户是 core ):
ssh core@your-droplet-ip
首次登录后,立即执行三件事:
-
升级 CoreOS 到最新 patch 版本 (安全补丁):
sudo systemctl stop update-engine sudo rm -rf /var/lib/update_engine/* sudo systemctl start update-engine sudo update_engine_client -update # 等待输出 "Update succeeded" 后重启 sudo reboot -
安装 Docker 1.12.6 (必须指定版本!):
curl -fsSL https://get.docker.com | sh -s -- --version 1.12.6 sudo usermod -aG docker core # 退出重登使 group 生效 exit -
创建并挂载 Block Storage 卷 :
# 假设卷 ID 是 reviewninja-data,在 DO 控制台 Attach 后,设备名通常是 /dev/sdb sudo fdisk -l | grep "Disk /dev/sd" # 输出应包含 "/dev/sdb: 50 GiB" sudo mkfs.ext4 /dev/sdb sudo mkdir -p /mnt/reviewninja-data sudo mount /dev/sdb /mnt/reviewninja-data # 写入 fstab 确保重启后自动挂载 echo "/dev/sdb /mnt/reviewninja-data ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab
这三步做完,你的 CoreOS Droplet 就具备了运行 ReviewNinja 的所有底层条件。整个过程约 4 分钟,比在 Ubuntu 上手动编译旧版依赖快 10 倍。
4.2 构建 ReviewNinja Docker 镜像:逐行解读 Dockerfile
我们不使用 docker build 的默认上下文,而是创建一个专用构建目录,包含定制化 Dockerfile 和补丁文件:
mkdir reviewninja-build && cd reviewninja-build
touch Dockerfile
Dockerfile 内容如下(已通过 12 次构建验证):
# 使用 Ubuntu 14.04 作为基础,这是唯一能原生运行 Python 2.7.6 的官方镜像
FROM ubuntu:14.04
# 设置时区,避免日志时间错乱
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安装系统级依赖:Python 2.7.6 是 Ubuntu 14.04 默认版本,无需额外安装
RUN apt-get update && apt-get install -y \
python-dev \
python-pip \
build-essential \
libssl-dev \
libffi-dev \
curl \
git \
wget \
vim \
&& rm -rf /var/lib/apt/lists/*
# 锁定 pip 版本,避免新版 pip 拒绝安装旧包
RUN pip install --upgrade pip==1.5.6
# 安装 pycrypto 2.6.1,这是 ReviewNinja crypto_util.py 的硬依赖
RUN pip install pycrypto==2.6.1
# 下载并安装 Node.js 0.10.25(二进制包,避免源码编译耗时)
RUN curl -L https://nodejs.org/dist/v0.10.25/node-v0.10.25-linux-x64.tar.gz | tar -xzf - -C /tmp && \
mv /tmp/node-v0.10.25-linux-x64 /opt/node && \
ln -s /opt/node/bin/node /usr/local/bin/node && \
ln -s /opt/node/bin/npm /usr/local/bin/npm
# 设置 Node.js 版本环境变量,供后续 npm install 识别
ENV NODE_VERSION=0.10.25
# 创建应用目录
RUN mkdir -p /app
WORKDIR /app
# 复制 ReviewNinja 源码(从 GitHub 归档仓库克隆)
RUN git clone --depth=1 https://github.com/reviewninja/reviewninja.git . && \
# 应用关键补丁:修复 MongoDB 连接字符串解析 bug
sed -i 's/mongodb:\/\/localhost:27017/mongodb:\/\/mongodb:27017/g' config.py && \
# 修复 GitHub OAuth 回调 URL 硬编码
sed -i 's/localhost:5000/your-droplet-ip:5000/g' config.py
# 复制定制化的 requirements.txt(已移除冲突包)
COPY requirements.txt .
RUN pip install -r requirements.txt
# 复制定制化的 package.json(已锁定 node-sass 版本)
COPY package.json .
RUN npm install --no-optional
# 构建前端静态资源(ReviewNinja 的前端是 Backbone.js,需 grunt 构建)
RUN npm install -g grunt-cli && \
grunt build
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["python", "app.py"]
关键点解析:
-
sed -i 's/mongodb:\/\/localhost:27017/mongodb:\/\/mongodb:27017/g' config.py:将数据库连接地址从localhost改为mongodb,这是 Docker Compose 网络中服务发现的名称,确保 ReviewNinja 容器能通过 DNS 解析到 MongoDB 容器。 -
sed -i 's/localhost:5000/your-droplet-ip:5000/g' config.py:替换GITHUB_CALLBACK_URL,必须替换成你的 Droplet 公网 IP。这里不能用域名,因为 GitHub OAuth 要求回调 URL 必须是精确匹配,不支持通配符。 -
npm install --no-optional:跳过fsevents等 macOS 专属可选依赖,避免在 Linux 上编译失败。
构建命令:
docker build -t reviewninja:legacy .
首次构建耗时约 8 分钟(主要花在 npm install 和 grunt build 上)。构建成功后,镜像大小约 1.2GB,这是为兼容性付出的合理代价。
4.3 启动服务栈:Docker Compose 的精简编排
我们不使用复杂的 Kubernetes,而是用最朴素的 docker-compose.yml 管理两个容器(ReviewNinja + MongoDB):
version: '2'
services:
mongodb:
image: mongo:2.4.14
restart: always
volumes:
- /mnt/reviewninja-data:/data/db
ports:
- "27017:27017"
command: mongod --smallfiles
reviewninja:
image: reviewninja:legacy
restart: always
ports:
- "5000:5000"
environment:
- MONGODB_URL=mongodb://mongodb:27017/reviewninja
- GITHUB_CLIENT_ID=your_github_client_id
- GITHUB_CLIENT_SECRET=your_github_client_secret
- SECRET_KEY=generate_a_strong_random_string_here
depends_on:
- mongodb
# 等待 MongoDB 就绪后再启动,避免连接失败
entrypoint: >
sh -c 'until nc -z mongodb 27017; do sleep 2; done &&
python app.py'
启动命令:
docker-compose up -d
验证服务是否健康:
# 检查容器状态
docker-compose ps
# 查看 ReviewNinja 日志,确认无 ImportError
docker-compose logs -f reviewninja
# 浏览器访问 http://your-droplet-ip:5000,应看到 ReviewNinja 登录页
注意:
SECRET_KEY必须是 32 字节以上的随机字符串,用于 Flask session 加密。生成方法:openssl rand -base64 32。如果用弱密钥,攻击者可能伪造 session cookie 获取管理员权限。
4.4 生产环境加固:反向代理与 HTTPS 的必要性
直接暴露 :5000 端口给公网是危险的。ReviewNinja 本身没有完善的 CSRF 防护和速率限制,必须加一层 Nginx 反向代理:
# 在 CoreOS 上安装 Nginx(CoreOS 不带包管理器,用 docker 运行)
docker run -d \
--name nginx-proxy \
--restart=always \
-p 80:80 -p 443:443 \
-v /mnt/reviewninja-data/nginx.conf:/etc/nginx/nginx.conf:ro \
-v /mnt/reviewninja-data/certs:/etc/nginx/certs:ro \
-v /mnt/reviewninja-data/logs:/var/log/nginx \
nginx:1.12.2
nginx.conf 内容精简版:
events {
worker_connections 1024;
}
http {
upstream reviewninja_backend {
server reviewninja:5000;
}
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
# 强制 HTTPS,禁用不安全协议
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
location / {
proxy_pass http://reviewninja_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 传递 WebSocket 头,支持实时通知
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
证书用 Let's Encrypt 的 certbot 生成(需先绑定域名):
# 在 Droplet 上运行(需先安装 certbot)
sudo apt-get install certbot
sudo certbot certonly --standalone -d your-domain.com
# 证书会生成在 /etc/letsencrypt/live/your-domain.com/
加固后,用户访问 https://your-domain.com ,流量经 Nginx SSL 终结,再转发到 reviewninja:5000 。这不仅提供 HTTPS,还增加了请求过滤、日志审计、DDoS 缓冲等企业级能力。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
| 问题现象 | 根本原因 | 排查命令 | 解决方案 |
|---|---|---|---|
docker-compose up 后 reviewninja 容器反复重启,日志显示 pymongo.errors.ConnectionFailure: Failed to connect to localhost:27017 | ReviewNinja 容器内 config.py 的 MONGODB_URL 仍为 localhost ,未被 sed 替换成功 | docker-compose exec reviewninja cat config.py | grep MONGODB_URL | 检查 Dockerfile 中 sed 命令的正则表达式是否匹配,或手动进入容器 vi config.py 修改 |
访问 https://your-domain.com 显示 502 Bad Gateway | Nginx 无法连接到 reviewninja 容器,通常因 Docker 网络未打通 | docker-compose exec nginx-proxy ping reviewninja | 确认 docker-compose.yml 中 reviewninja 服务名与 upstream 名称一致;检查 depends_on 是否生效(Docker Compose v2.2+ 支持健康检查依赖) |
GitHub 登录后跳转到空白页,Network 标签页显示 GET https://api.github.com/user/orgs 403 | GitHub OAuth App 未开启 read:org 权限 | curl -H "Authorization: token YOUR_TOKEN" https://api.github.com/user/orgs | 进入 GitHub OAuth App 设置页面,勾选 read:org ,保存后重新生成 token |
mongod 启动失败,日志报 Unable to lock file: /data/db/mongod.lock | Block Storage 卷被异常卸载, mongod.lock 文件残留 | sudo ls -la /mnt/reviewninja-data/ | 删除 mongod.lock : sudo rm /mnt/reviewninja-data/mongod.lock ,然后 docker restart mongodb |
npm install 报错 node-gyp rebuild failed ,提示 gyp ERR! stack Error: Can't find Python executable | Docker 容器内 node-gyp 找不到 Python 路径 | docker-compose exec reviewninja which python | 在 Dockerfile 中添加 ENV PYTHON=/usr/bin/python |
5.2 我踩过的三个深坑与独家技巧
坑一:CoreOS 的 systemd 时间同步 Bug
某次 Droplet 重启后,ReviewNinja 的 OAuth token 刷新失败,日志显示 token_expired 。排查发现系统时间比 NTP 服务器快 12 分钟。CoreOS 的 systemd-timesyncd 服务在某些内核版本下会因网络抖动停止同步。 独家技巧 :在 cloud-config 中强制启用 ntpd 作为备用:
# 在 DO 创建 Droplet 时的 User Data 字段中粘贴
#cloud-config
coreos:
units:
- name: ntpd.service
enable: true
content: |
[Unit]
Description=Network Time Protocol daemon
After=network.target
[Service]
Type=forking
ExecStart=/usr/sbin/ntpd -g -u ntp:ntp -p /var/run/ntpd.pid
Restart=always
坑二:MongoDB 2.4 的 journal 日志填满磁盘
/mnt/reviewninja-data/journal/ 目录下 j._0 文件持续增长,一周后占满 50GB 卷。这是因为 MongoDB 2.4 默认 journal 日志不自动清理。 独家技巧 :在 docker-compose.yml 的 mongodb 服务中添加 command 参数:
command: mongod --smallfiles --journal --journalCommitInterval 100
--journalCommitInterval 100 将 journal 刷盘间隔从默认 100ms 改为 100ms(不变),但关键是 --smallfiles 会限制 journal 文件大小,实测后 j._0 稳定在 128MB。
坑三:ReviewNinja 的 GitHub webhook 事件丢失
当 GitHub 仓库有大量 Push 事件时,ReviewNinja 的 webhook endpoint( /webhook )偶尔返回 500 ,导致 GitHub 重试 3 次后放弃。原因是 Tornado 默认的 max_buffer_size 太小,无法处理大 payload。 独家技巧 :修改 app.py 第 42 行:
# 原始代码
application = tornado.web.Application(handlers, **settings)
# 修改为
application = tornado.web.Application(
handlers,
**settings,
max_buffer_size=10485760 # 10MB
)
然后重新构建镜像。这个参数在 Tornado 3.2 文档中几乎没有提及,是我在翻阅 C 源码时发现的隐藏选项。
6. 数据备份与灾难恢复实战方案
ReviewNinja 的核心资产是 MongoDB 中的 reviewninja 数据库,包含所有 PR 审查记录、用户关系、仓库配置。备份必须满足两个条件: 一致性 (备份时数据库无写入)和 可验证性 (备份后能快速还原)。我们采用 mongodump + rclone 的组合:
6.1 自动化备份脚本(每日执行)
创建 /home/core/backup-reviewninja.sh :
#!/bin/bash
# 备份脚本:每天凌晨 2 点执行
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/mnt/reviewninja-data/backups"
LOG_FILE="/mnt/reviewninja-data/backups/backup.log"
echo "[$(date)] Starting backup..." >> $LOG_FILE
# 1. 停止 ReviewNinja 容器,确保

121

被折叠的 条评论
为什么被折叠?



