OJ平台远端判题子系统开发(一):需求拆解与Docker核心知识入门
作为DoReMiFaSo团队中负责判题子系统的开发者,本周的主要工作是明确判题子系统的需求边界、调研代码隔离方案,并学习Docker容器化的核心操作。
一、判题子系统需求拆解
1.1 系统的定位
判题子系统不是OJ后端内部的判题模块,而是一个独立部署的服务,包含HTTP API、异步任务队列、判题Worker和Docker沙箱执行引擎四个组成部分。与OJ后端之间通过HTTP API解耦。
1.2 核心需求
经过与团队讨论,梳理出以下核心需求:
- 强隔离性:不同用户提交的代码必须相互隔离,防止恶意代码影响主系统或其他用户的代码执行。
- 资源可管控:精确限制代码运行时的CPU、内存和执行时间,防止单份代码耗尽服务器资源。
- 异步可调用:OJ后端通过HTTP API提交判题请求后可以立即返回,判题在后台异步完成。判题为典型的重操作,编译加运行通常需要数秒至十几秒,同步等待会大量占用连接资源。
- 并行可处理:支持多个判题请求同时执行,通过控制机制限制并发数量。
- 结果精准化:支持多类标准化判题结果,包括Accepted、Wrong Answer、Compile Error、Runtime Error、Time Limit Exceeded、Memory Limit Exceeded、Output Limit Exceeded、System Error等,并提供资源使用详情。
- 安全可加固:支持seccomp安全策略、cap-drop ALL、网络禁用等多层安全防护。
二、代码隔离方案选型
2.1 调研过程
决定用什么技术来执行用户代码,是本周花费时间最多的调研。考察了三种方案:
| 隔离方案 | 隔离性 | 启动速度 | 资源占用 | 开发成本 |
|---|---|---|---|---|
| 传统虚拟机 | 强 | 慢(分钟级) | 高(GB级) | 低 |
| Linux namespace + cgroup | 中等 | 快(毫秒级) | 极低 | 极高 |
| Docker容器化 | 强 | 快(秒级) | 低(MB级) | 低 |
最初考虑直接使用namespace + cgroup,因为Go可以通过syscall直接操作,与判题逻辑深度集成。但在调研过程中发现手动实现的工作量远超预期:需要管理PID namespace、mount namespace、网络namespace,编写cgroup资源限制逻辑,进程清理和回收也需要自行处理。调研了一些开源OJ项目(如Judge0)的实现方式,发现它们在底层均使用Docker作为沙箱。
学习容器底层原理时参考了Google Cloud的容器技术概述:Containers at Google
2.2 最终选择Docker的原因
- Docker基于namespace + cgroup实现,封装完成度高
- Go通过os/exec调用Docker CLI即可操作容器,集成简单
- 镜像体系天然支持多语言环境,每种语言一个镜像
- Docker CLI方案在本地运行和在容器化部署(Compose)场景下均能正常工作
2.3 Docker驱动方式的决策
在Docker操作方式上,对两种方案进行了评估:
- Docker CLI:通过os/exec调用docker命令
- Go Docker SDK:通过官方SDK调用Docker API
SDK方案提供了类型安全和API文档完善的优势,但在实践中发现其引入的间接依赖过多,且在容器化部署场景下(Judger自身作为容器通过docker socket操作宿主机Docker)SDK客户端连接配置复杂。CLI方案虽然通过字符串拼接命令,但稳定且无额外依赖。最终统一采用CLI驱动方案。
三、Docker核心知识学习
3.1 基本概念与命令
通过实际操作学习了Docker的核心概念:
- 镜像(Image):只读模板,包含程序运行所需的所有依赖。项目使用Alpine基础镜像构建判题镜像。
- 容器(Container):镜像的运行实例,隔离环境。每次判题创建一个新容器,判完即销毁。
- docker exec模式:先通过docker run -d(配合sleep)创建后台容器,后续编译和运行分别通过docker exec进入容器执行。这种分步控制的方式比一次性docker run --rm更灵活。

Docker官方基础文档:https://docs.docker.com/get-started/overview/
3.2 资源限制参数
沙箱的核心需求之一是资源管控。Docker提供的资源限制参数:
--network none # 禁用网络
--cpus 1 # 限制1个CPU核心
--memory 256m # 内存硬限制(OOM Kill触发退出码137)
--pids-limit 64 # 最大进程数
--cap-drop ALL # 移除所有Linux Capability
--security-opt no-new-privileges # 禁止提权
--tmpfs /workspace:... # 临时文件系统
资源限制官方文档:https://docs.docker.com/engine/containers/resource_constraints/
3.3 镜像构建
三种判题镜像均基于Alpine Linux构建,以保持最小体积:
# C++17
FROM alpine:3.19
RUN apk add --no-cache g++ libstdc++
RUN adduser -D runner
USER runner
WORKDIR /workspace
# Python 3.11
FROM python:3.11-alpine
RUN adduser -D runner
USER runner
WORKDIR /workspace
# Go 1.22
FROM golang:1.22-alpine
RUN adduser -D runner
USER runner
WORKDIR /workspace
构建命令:
docker build -t remote-judge-cpp17 -f docker/images/cpp17/Dockerfile .
docker build -t remote-judge-go122 -f docker/images/go1.22/Dockerfile .
docker build -t remote-judge-python311 -f docker/images/python3.11/Dockerfile .
构建后的镜像体积:
| 镜像 | 体积 |
|---|---|
| remote-judge-cpp17 | 308 MB |
| remote-judge-go122 | 348 MB |
| remote-judge-python311 | 83.7 MB |

3.4 文件传输模式
沙箱需要将用户代码和测试用例传递到容器内部。设计了两种文件传输模式:
- Bind模式:通过
-v绑定挂载宿主机目录到容器/workspace,适用于本地开发调试 - Copy模式:通过
docker exec+install命令将文件复制到沙箱容器,适用于容器通过docker socket操作宿主Docker的场景
选择两种模式而非单一方案的原因是:Bind模式性能更好但不适用于Compose场景(Judger自身容器化后无法访问宿主机文件系统),Copy模式通用性更强但实现更复杂。
四、遇到的问题与解决
问题1:容器生命周期管理
问题描述:最初使用 docker run --rm 一次性执行命令,编译和运行合并在同一个容器中。这种方式将编译和运行耦合在一起,无法分步控制。
排查过程:发现如果编译失败需要返回错误信息(stderr),但容器已经销毁,无法获取。同时编译和运行需要不同的资源限制(编译阶段内存需求更大),合并执行无法区分。
解决方案:改为 docker run -d image sleep 3600 先创建后台容器,然后通过 docker exec 分别执行编译命令和运行命令。编译完成后可根据exitCode决定是继续运行还是提前结束,两个阶段的资源参数也可独立配置。
问题2:内存限制配置
问题描述:最初将 --memory 设为256m时,Go编译出现OOM(Out of Memory)错误。
排查过程:通过逐步增加内存限制进行测试,发现Alpine上的Go编译器本身需要一定的内存开销,加上编译过程的临时分配,256m在编译阶段不够用。
解决方案:适当调整内存限制。同时发现 --memory 是硬限制,超出后Docker会直接OOM Kill容器,退出码变为137——这个退出码后续用作Memory Limit Exceeded判定的关键信号。
五、测试验证
本周的验证工作以手动执行为主。构建了三个语言的判题镜像并确认镜像构建成功:
| 测试项 | 预期结果 | 实际结果 |
|---|---|---|
| C++17镜像构建 | 构建成功 | 通过,308MB |
| Go 1.22镜像构建 | 构建成功 | 通过,348MB |
| Python 3.11镜像构建 | 构建成功 | 通过,83.7MB |
六、本周总结
完成的内容
- 拆解判题子系统6项核心需求
- 对比三种代码隔离方案,确定Docker为技术基础
- 学习Docker基本操作、资源限制参数和镜像构建
- 设计Bind/Copy两种文件传输模式
- 统一采用CLI驱动方案,舍弃SDK方案
调研查阅的资料
- Docker官方文档:https://docs.docker.com/
- Alpine Linux文档:https://alpinelinux.org/
- Docker安全最佳实践:https://docs.docker.com/engine/security/
- Docker资源限制文档:https://docs.docker.com/engine/containers/resource_constraints/
- Google Cloud容器技术概述:https://cloud.google.com/learn/what-are-containers

浙公网安备 33010602011771号