Judge0在线代码执行系统快速上手:如何用Docker部署90+语言沙箱搭建在线判题
【免费下载链接】judge0Robust, fast, scalable, and sandboxed open-source online code execution system for humans and AI.项目地址: https://gitcode.com/GitHub_Trending/ju/judge0
当不可信的用户代码需要在你的生产环境里运行时,编程学习平台、面试评测系统和 AI 代码校验都会遇到同一个问题。Judge0 是一个开源的在线代码执行系统,它把用户代码放进 isolate 沙箱中编译与运行,并通过 HTTP JSON API 返回输出、时间、内存等结构化执行结果。
Judge0品牌图,开源在线代码执行系统视觉形象
项目速写:一个运行了十年的代码执行引擎
Judge0 的定位是:给你一个开箱即用的在线代码执行系统,让你把"运行代码"这件事外包给沙箱,专心做产品逻辑。它由 Herman Zvonimir Došilović 创建,采用 Rails API 层 + Resque/Redis 异步队列 + PostgreSQL 存储 + ioi/isolate 沙箱的模块化架构,官方文档中有一篇研究论文专门描述了这套设计。
三个硬事实:
| 维度 | 事实 |
|---|---|
| 诞生时间 | 2016 年 8 月,持续维护至今 |
| 语言支持 | 官方宣称 90+ 种语言;仓库语言定义文件中,当前活跃 47 条、已归档 42 条 |
| 维护状态 | 当前版本 1.13.1(见 Dockerfile),GPLv3 许可,API 与队列分层,社区有完整生产案例 |
典型使用场景都围绕"批量执行不可信代码":竞赛判题、教学作业自动批改、AI Agent 执行模型生成的代码。
⚡ 五分钟跑通第一次执行:从提交到拿到结果
最短路径只有四步:克隆仓库、填两个密码、拉起容器、发一条请求。
git clone https://gitcode.com/GitHub_Trending/ju/judge0- 编辑根目录的 judge0.conf,至少补上
REDIS_PASSWORD和POSTGRES_PASSWORD(两者无默认值) - 执行
docker compose up -d,等待 server 与 worker 就绪 - 向
http://localhost:2358/submissions发 POST 请求
最小请求示例(language_id 71 对应仓库语言表中的 Python 3.8.1):
curl -H "Content-Type: application/json" \ -d '{"language_id": 71, "source_code": "print(\"hello, \" + input())", "stdin": "Judge0"}' \ "http://localhost:2358/submissions?wait=true"wait=true会阻塞到执行完成并直接返回 stdout、status 等字段;不加则只返回 token,用GET /submissions/:token轮询。官方文档明确不建议在高并发下使用同步模式。
执行链路拆解:一次提交在沙箱里的完整旅程
结论先说:一次提交经历"入队 → 隔离工作区 → 编译 → 运行 → 校验"五个阶段,全程由 app/jobs/isolate_job.rb 中的 IsolateJob 驱动,沙箱本体是 isolate 命令行。
链路细节:
- API 层(app/controllers/submissions_controller.rb)校验参数、检查队列深度,把 Submission 写入 PostgreSQL,再投递到 Resque 队列
- worker 取出任务,用
isolate -b <box_id> --init创建独立沙箱工作区,把源码、stdin 写入对应文件 - 编译与运行各自生成一个 bash 脚本,在沙箱内执行;脚本中的 shell 危险字符(`$&;<>|`` 等)会被剥离
- 结束后 IsolateJob 读取 isolate 产生的 metadata(耗时、内存、退出码/信号),对照
expected_output判定状态,最后清理沙箱并可回调 callback_url
沙箱隔离至少由三层机制叠加:
- 进程与文件系统隔离:每个提交使用独立 box 与工作目录,
HOME=/tmp、PATH 收窄到系统目录,-d /etc:noexec挂载时禁止执行,运行后执行--cleanup销毁 - 资源限制:CPU 时间
-t(默认 5s,含超时后额外宽限-x)、墙钟时间-w(默认 10s)、内存-m(默认 128MB)、栈-k、进程/线程数-p(默认 60)、可写文件大小-f(默认 1MB),并支持 cgroups 做总量控制 - 网络控制:默认禁止网络,仅当提交请求显式
enable_network=true且服务端配置允许时才追加--share-net
自托管部署选项:Docker Compose 是最短生产路径
仓库内置的 docker-compose.yml 定义了四个服务:server(对外 API,端口 2358)、worker(沙箱执行,运行 scripts/workers)、db(PostgreSQL 16.2)、redis(7.2.4,开启密码)。由于 isolate 依赖 cgroups 与 noexec 挂载,server 和 worker 容器需要privileged: true,部署时务必放在隔离网络里。
因为 server 与 worker 都是无状态进程(状态在 Redis 与 PostgreSQL 中),扩容只需增加 worker 副本,这也是社区将其部署到 Kubernetes 的前提:按副本数水平扩展执行能力即可。开发环境可用 docker-compose.dev.yml 与 scripts/dev/ 下的辅助脚本。
主配置文件是根目录的 judge0.conf,关键项如下:
| 配置项 | 默认值 | 作用 |
|---|---|---|
MAX_QUEUE_SIZE | 100 | 队列上限,超限时新提交返回 503 |
COUNT | 2 × CPU 核数 | 并行 worker 数,决定执行吞吐 |
CPU_TIME_LIMIT/MEMORY_LIMIT | 5s / 128MB | 程序级默认资源限制 |
ENABLE_NETWORK | false | 沙箱默认禁网总开关 |
AUTHN_TOKEN | 空(不鉴权) | 设置后所有请求需携带鉴权头 |
SUBMISSION_CACHE_DURATION | 1s | 结果查询的缓存窗口 |
API 接入实战:核心端点、状态码与错误处理
核心端点定义在 config/routes.rb 中,接入时只需要下表几个:
| 端点 | 方法 | 用途 |
|---|---|---|
/submissions | POST | 创建提交,可选wait、base64_encoded、fields查询参数 |
/submissions/:token | GET / DELETE | 查询结果 / 删除记录(删除需开启开关) |
/submissions/batch | POST / GET | 批量提交与批量查询,默认单批 ≤ 20 |
/languages、/statuses | GET | 语言表与状态表 |
/statistics、/system_info | GET | 用量统计与运行参数 |
/workers | GET | 健康检查 |
提交参数支持逐任务覆盖限制:cpu_time_limit、memory_limit、wall_time_limit、number_of_runs(多次运行取均值)、compiler_options、command_line_arguments、additional_files(Base64 压缩包)等。
状态码枚举在 app/enumerations/status.rb,共 14 个:
| ID | 状态 | ID | 状态 |
|---|---|---|---|
| 1 | In Queue | 8 | Runtime Error (SIGXFSZ) |
| 2 | Processing | 9 | Runtime Error (SIGFPE) |
| 3 | Accepted | 10 | Runtime Error (SIGABRT) |
| 4 | Wrong Answer | 11 | Runtime Error (NZEC) |
| 5 | Time Limit Exceeded | 12 | Runtime Error (Other) |
| 6 | Compilation Error | 13 | Internal Error |
| 7 | Runtime Error (SIGSEGV) | 14 | Exec Format Error |
常见报错:422 表示参数校验失败(如language_id不存在);503 "queue is full" 表示队列已满;返回cannot be converted to UTF-8错误时改用base64_encoded=true请求。
生产环境加固清单
上线前逐项核对:
- 设置
REDIS_PASSWORD与POSTGRES_PASSWORD,不留空默认 - 配置
AUTHN_HEADER/AUTHN_TOKEN开启 API 鉴权,管理端点再叠加AUTHZ_TOKEN - 用
ALLOW_ORIGIN/ALLOW_IP收紧来源,替代默认的全放行 - 按吞吐调整
COUNT与MAX_QUEUE_SIZE,并为MAX_CPU_TIME_LIMIT、MAX_MEMORY_LIMIT等自定义上限设合理天花板 - 保持
ENABLE_NETWORK=false,仅在业务需要时通过ALLOW_ENABLE_NETWORK放开用户侧开关 - 将特权容器放入隔离网络/VPC,避免沙箱逃逸影响宿主机
- 监控
/workers健康、队列深度、状态分布(TLE/CE 占比),在网关层做限流兜底 - 发布时切换
MAINTENANCE_MODE阻止新提交
🧯 避坑指南:五个常见现象与修复方式
输出乱码或 400 报 UTF-8 转换错误。原因:程序输出了非 UTF-8 字节。对策:请求与查询统一带base64_encoded=true,客户端解码后再展示。
高并发下出现 503 queue is full。原因:MAX_QUEUE_SIZE偏小或 worker 处理慢,新请求被直接拒绝。对策:增加 worker 副本、调大队列上限,并对 503 做指数退避重试。
同步wait=true在高峰期超时。原因:同步模式会让请求线程挂起等待整个执行周期,官方文档明确说它扩展性差。对策:改为异步提交 +callback_url回调,或低频轮询 token。
编译通过但运行得到 Runtime Error (NZEC)。原因:编译产物缺失或运行命令找不到可执行文件,退出码为"无正常退出"。对策:检查该语言在 db/languages/active.rb 中的compile_cmd/run_cmd定义,查看结果的exit_signal与message字段定位。
设置了时间上限,程序仍长时间挂起。原因:只约束了 CPU 时间,而程序在等待 I/O 或 sleep 时不消耗 CPU。对策:把WALL_TIME_LIMIT设为更高的兜底值(默认 10s,官方建议显著高于 CPU 上限)。
完整端点说明见仓库内 docs/api/docs.md,执行与状态判定逻辑可从 app/ 目录逐层阅读。Judge0 适合在线判题、AI 代码校验、教学评测这类批量执行不可信代码的场景;如果你的需求是长时间交互式会话或高内存常驻进程,则更适合专用容器服务而非本系统的沙箱模型。
【免费下载链接】judge0Robust, fast, scalable, and sandboxed open-source online code execution system for humans and AI.项目地址: https://gitcode.com/GitHub_Trending/ju/judge0
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考