私有化在线判题系统实战:基于Docker的QDUOJ高效部署指南

在算法竞赛训练和编程教学中,公共在线判题系统(OJ)往往面临题目类型受限、网络延迟高、数据隐私无法保障等痛点。本文将带你从零开始,在Ubuntu服务器上通过Docker快速部署QDUOJ系统,打造专属的私有化评测环境。不同于简单的步骤罗列,我们将深入探讨容器化部署的最佳实践,并分享实际运营中的经验技巧。

1. 环境准备与核心组件解析

1.1 服务器基础配置

选择Ubuntu 20.04 LTS作为基础系统,这是目前最稳定的长期支持版本。在开始前,建议执行以下基础环境检查:

# 检查系统版本
lsb_release -a
# 更新软件源
sudo apt update && sudo apt upgrade -y

关键组件依赖关系

组件作用推荐版本
Docker容器运行时环境20.10+
docker-compose多容器编排工具1.29+
Python3脚本支持环境3.6+
Git代码版本管理2.25+

提示:生产环境建议使用独立磁盘分区存放Docker数据,避免系统盘空间耗尽导致服务异常。

1.2 Docker引擎优化配置

国内用户推荐使用阿里云镜像加速安装:

# 安装必要工具
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
# 添加Docker官方GPG密钥
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# 设置稳定版仓库
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# 安装Docker引擎
sudo apt install -y docker-ce docker-ce-cli containerd.io

配置Docker守护进程参数:

# 创建配置文件目录
sudo mkdir -p /etc/docker
# 配置镜像加速和日志限制
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://your-aliyun-mirror.mirror.aliyuncs.com"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
EOF
# 重启服务生效
sudo systemctl daemon-reload
sudo systemctl restart docker

2. QDUOJ容器化部署实战

2.1 项目结构与部署流程

获取官方部署模板:

git clone -b 2.0 https://github.com/QingdaoU/OnlineJudgeDeploy.git
cd OnlineJudgeDeploy

目录结构关键说明:

OnlineJudgeDeploy/
├── docker-compose.yml    # 多容器编排定义
├── data/                 # 持久化数据存储
│   ├── mysql/            # 数据库文件
│   └── redis/            # 缓存数据
└── config/               # 应用配置文件

端口规划建议

服务默认端口安全建议
Web前端80/443建议改为非标准端口如8080
MySQL3306限制仅本地访问
Redis6379设置密码认证

修改docker-compose.yml中的端口映射:

services:
  oj-web:
    ports:
      - "8080:80"  # 将80改为8080

2.2 一键启动与健康检查

启动所有服务:

docker-compose up -d

监控启动日志:

# 查看实时日志
docker-compose logs -f 
# 检查容器状态
watch -n 2 docker-compose ps

健康状态验证:

# 检查各容器运行状态
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

注意:首次启动可能需要5-30分钟完成初始化,具体取决于网络速度和服务器性能。

3. 系统安全加固与基础配置

3.1 关键安全措施

  1. 修改默认凭证

    • 登录后台 http://服务器IP:8080/admin
    • 立即修改root账户密码(默认rootroot)
    • 创建具有适当权限的次级管理员账户
  2. 网络访问控制

    # 仅允许特定IP访问管理后台
    sudo ufw allow from 你的IP to any port 8080
    sudo ufw enable
    
  3. 数据库安全加固

    # 进入MySQL容器
    docker exec -it oj-mysql mysql -uroot -p
    

    执行SQL命令修改root密码并创建专用账户:

    ALTER USER 'root'@'%' IDENTIFIED BY '新复杂密码';
    CREATE USER 'oj_user'@'%' IDENTIFIED BY '专用密码';
    GRANT ALL PRIVILEGES ON onlinejudge.* TO 'oj_user'@'%';
    FLUSH PRIVILEGES;
    

3.2 性能调优建议

调整Redis配置:

docker exec -it oj-redis redis-cli
CONFIG SET maxmemory 1gb
CONFIG SET maxmemory-policy allkeys-lru

MySQL性能参数优化(在docker-compose.yml中添加):

oj-mysql:
  environment:
    - MYSQL_INNODB_BUFFER_POOL_SIZE=1G
    - MYSQL_INNODB_LOG_FILE_SIZE=256M

4. 题目管理与系统运维

4.1 题目导入规范

标准题目包结构示例:

problem_1001/
├── testcase/
│   ├── 1.in
│   ├── 1.out
│   ├── 2.in
│   └── 2.out
└── problem.md

题目元数据配置要点

  • 时间限制单位:毫秒(1000ms=1s)
  • 内存限制单位:MB(通常设置64-1024MB)
  • 评测模式选择:
    • ACM模式:实时返回结果
    • OI模式:比赛结束后统一评测

4.2 日常维护命令

备份数据库:

docker exec oj-mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" onlinejudge' > oj_backup_$(date +%Y%m%d).sql

日志轮转设置:

# 设置日志文件大小限制
docker-compose exec oj-web bash -c "ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log"

系统升级流程:

# 停止旧服务
docker-compose down
# 拉取最新代码
git pull origin 2.0
# 重新构建镜像
docker-compose build --no-cache
# 启动新服务
docker-compose up -d

5. 高级功能扩展

5.1 多节点分布式部署

修改docker-compose.yml实现负载均衡:

oj-judge:
  deploy:
    mode: replicated
    replicas: 3
  resources:
    limits:
      cpus: '0.5'
      memory: 512M

5.2 集成持续评测系统

通过Webhook实现Git仓库自动评测:

# 示例:GitLab Webhook处理器
@app.route('/webhook', methods=['POST'])
def handle_webhook():
    data = request.json
    if data['object_kind'] == 'push':
        clone_repo(data['repository']['git_ssh_url'])
        run_test_cases()
        return jsonify({'status': 'processing'})

5.3 自定义评测器开发

创建特殊判题程序(SPJ):

#include <bits/stdc++.h>
using namespace std;

int main(int argc, char* argv[]) {
    ifstream user_out(argv[1]);  // 用户输出
    ifstream std_out(argv[2]);   // 标准输出
    
    // 自定义比对逻辑
    double a, b;
    user_out >> a;
    std_out >> b;
    
    if(fabs(a - b) < 1e-6) {
        return 0;  // AC
    } else {
        return 1;  // WA
    }
}

部署到服务器指定目录:

docker cp spj oj-web:/usr/local/bin/
docker exec oj-web chmod +x /usr/local/bin/spj

在实际运营中,我们发现合理设置题目难度梯度对用户体验至关重要。初期建议准备20-30道涵盖不同难度级别的题目,从简单的"Hello World"输出题到中等难度的算法题逐步过渡。定期查看系统日志分析用户提交模式,可以优化题目设置和服务器资源配置。

更多推荐