@virusdefender
2016-01-09T03:27:23.000000Z
字数 6529
阅读 12125
https://github.com/QingdaoU/OnlineJudge
qduoj 可以提供多种语言代码的运行和评测,举办各种类型的比赛,分析比赛结果等,可以用于日常教学练习和考试,目前青大 ACM 队内部使用。
如图,系统主要分为 Web server
、MySQL数据库
、判题服务器
和 Redis
四部分,它们可以任意的合并或者拆分到一台服务器上。
系统使用两个 MySQL 数据库,一个存储用户信息、题目信息、比赛信息等,另一个只存储用户提交的代码和运行结果。因为用户提交的代码和运行结果通常比较长,数据量和体积比较大,所以将其拆分出来。
两个 Redis 实例也是分别承担不同的任务,一个是 web 服务器的缓存,另一个是消息队列和任务队列,承担 web 服务器和判题服务器的通信任务。
判题服务器使用 docker
和 lrun
实现,每次收到一个新的判题任务就会创建一个新的 docker 容器,启动相关的判题程序,结束后自动删除。然后会向 submission
数据库写入本次的判题结果,同时向消息队列中写入,这样 web 服务器在不轮询数据库的情况下就能获取最新的判题结果。
以下安装步骤均为 Ubuntu 系统下的演示,其他的系统可能命令稍有不同。
系统使用 docker 创建运行环境。源代码dockerfiles/oj_web_server
和dockerfiles/judger
中有两个 Dockerfile,是 Django web 服务器的环境和判题的运行环境。同时还依赖于 MySQL 和 redis 的 docker 镜像。
首先安装 Docker,进入上面oj_web_server
目录,命令是 docker build -t oj_web_server .
,其中-t
参数oj_web_server
就是创建的镜像的名字,注意不要漏了最后的那个.
,代表是当前目录。接下来创建判题的运行环境,进入 judger
目录,运行docker build -t judger .
即可。MySQL 和 redis 直接使用官方镜像,命令分别是docker pull mysql/mysql-server
和docker pull redis
。
国内的网络下载 docker 和 build docker 镜像不太方便,可以使用 daocloud 的镜像 https://dashboard.daocloud.io/mirror。安装后运行dao pull ubuntu:14.04
和dao pull python:2.7
之后再 build,应该就会快很多了。如果不介意的话,可以用下我的邀请链接
我正在使用 DaoCloud 提供的一站式容器云平台 ,你也快来加入吧! 自动化持续集成,超高速 Docker
镜像构建,还支持一键部署的容器运行集群哦!点此注册:
https://account.daocloud.io/signup?invite_code=95q5q8dw0n1xv22zc69y
,还有机会获得全球首本《Docker源码分析》和树莓派!
这个时候运行docker images
就可以看到刚才创建的镜像了。
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
oj_web_server latest 0d5fd23cb4aa 5 minutes ago 739.1 MB
judger latest 3d15638ddab8 About an hour ago 951.9 MB
redis latest 2f2578ff984f 4 days ago 109.2 MB
python 2.7 7a7d87336a33 4 days ago 675.4 MB
mysql/mysql-server latest ba04a84ec299 5 days ago 285.8 MB
ubuntu 14.04 91e54dfb1179 3 weeks ago 188.4 MB
在oj
目录中有几个配置文件,是通过环境变量oj_env
来控制加载哪一个的,如果oj_env=local
或者没有这个环境变量就使用local_settings.py
,否则使用server_settings.py
。通用设置在settings.py
中。
两个设置项目的不同点主要包括
BASEBASE
数据库地址,default
是除了用户提交之外的所有的数据,submission 是用户提交数据库。REDIS_CACHE
redis 网页缓存服务器STATICFILES_DIRS
静态文件目录,只有在 DEBUG=True
的时候 runserver 才会去处理。css、js 等都在 static
目录中,用户上传的图片在根目录下的 upload
目录中。TEMPLATE_DIRS
网页模板目录。因为静态文件需要加 md5来处理缓存问题,所以需要修改模板文件,处理后的模板放在 release
里面,源代码在 src
里面。还有两个设置项需要注意,就是 TEST_CASE_DIR
、LOG_PATH
和 IMAGE_UPLOAD_DIR
,暂时没有区分配置文件,都在源代码根目录下。但是在 docker 中运行的时候,我们需要在命令中对这三个文件加进行映射,让实际的文件写入主机磁盘,否则 docker 容器删除后这些文件也丢失了。我们创建/root/log
、/root/test_case
和/root/upload
三个目录。因为 MySQL 也是运行在 docker 中的,也需要对数据库的数据存储目录进行映射, 还需要创建/root/data
。
启动 MySQL,命令是docker run --name mysql -v /root/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql/mysql-server:latest
。然后 docker ps -a
就能看到这个容器了。以后手动的去操作 MySQL 都可以使用这个命令。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
360073468c74 mysql/mysql-server:latest "/entrypoint.sh mysql" 3 seconds ago Up 3 seconds 3306/tcp mysql
要注意的是
- 如果你的 MySQL 备份路径不是/root/data 的话,注意替换命令中的路径。
- --name
参数是 mysql
,这个只能存在一个,如果docker ps -a
中还有一个同名的,会造成启动失败,需要先删除就容器docker rm CONTAINER_ID
。
- MYSQL_ROOT_PASSWORD=root
说明 root
密码是 root
,这个可以自己设置,运行在容器中的代码可以通过环境变量获取到这个密码。
- MySQL 5.6及以上版本会默认打开performance_schema=OFF
,导致 MySQL 的 docker 容器占用大量内存,512M 内存的基本不能运行,问题分析见这里。如果确实内存不足,可以在本地新建一个 my.cnf,复制以下内容,
[mysqld]
skip-host-cache
skip-name-resolve
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
secure-file-priv=/var/lib/mysql-files
user=mysql
assorted security risks
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
performance_schema=OFF
然后启动 MySQL 的命令再加上-v /root/data/my.cnf:/etc/my.cnf
,使用自定义的配置文件,和官方的相比,就添加了最后一行。
接下来我们就能连接到 docker 中的数据库了,命令是docker run -it --link mysql:mysql --rm mysql/mysql-server:latest sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD"'
然后运行下面两个 sql 语句。
create database oj default character set utf8;
,create database oj_submission default character set utf8;
。这个时候,在 /root/data
目录就能看到同名的目录了。
然后我们创建数据库表,首先需要进入到oj_web_server
中,命令是docker run -i -t -e oj_env=server -v /root/qduoj:/code -v /root/test_case:/code/test_case -v /root/log:/code/log -v /root/upload:/code/upload --link mysql --rm oj_web_server /bin/bash
,然后使用命令python manage.py migrate
和python manage.py migrate --database=submission
,如果没有错误,就可以了。然后运行python manage.py shell
创建超级管理员用户。
root@93cfb0df88e5:/code# python manage.py shell
Python 2.7.10 (default, Sep 9 2015, 20:21:51)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from account.models import User
>>> u = User.objects.create(username="root", admin_type=2)
>>> u.set_password("root")
>>> u.save()
安装完成,exit
退出就行了。
接下来我们要在服务器上进行 js 的打包和模板中静态文件加 md5 戳的操作,可以将多个 js 合并成一个,同时避免服务器文件修改后浏览器没及时更新的问题,以后每次更新代码也要进行相同的操作。这个操作需要安装 node.js,自行安装就行。然后在 manage.py 所在的目录运行python tools/release_static.py
就可以了。
启动 redis
docker run --name redis -v:/root/data/:/data -d redis redis-server --appendonly yes
接下来我们就可以用 gunicorn 真正的去运行 web 程序了,命令是
docker run --name oj_web_server -e oj_env=server -v /root/qduoj:/code -v /root/test_case:/code/test_case -v /root/log:/code/log -v /root/upload:/code/upload -v /root/qduoj/dockerfiles/oj_web_server/supervisord.conf:/etc/supervisord.conf -v /root/qduoj/dockerfiles/oj_web_server/gunicorn.conf:/etc/gunicorn.conf -v /root/qduoj/dockerfiles/oj_web_server/mq.conf:/etc/mq.conf -d -p 127.0.0.1:8080:8080 --link mysql --link=redis oj_web_server
安装 nginx,进行一下反向代理和静态文件的处理就好了。
server {
listen 80;
location /static/upload {
alias /root/upload;
}
location /static {
alias /root/qduoj/static/release;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这个时候尝试访问你的 ip 或者域名应该就可以看到网页界面了。
判题队列使用的 celery 不是运行在 docker 里面的,所有它运行需要的数据库配置等都需要手动的获取。主要包括 submission 数据库地址,redis 地址,主机上测试用例目录和源代码目录。因为数据库和 redis 都是在 docker 中运行的,我们首先要获取这两个地址。
docker inspect redis
docker inspect mysql
在这两个输出中寻找 IPAddress,记住,然后打开/etc/profile
,在文件最后增加
export REDIS_PORT_6379_TCP_ADDR=192.168.xx.xx
export submission_db_host=192.168.xx.xx
自己按照实际情况替换即可。
主机上我们也是使用的 supervisor 监控 celery 的,创建/etc/supervisord.conf,
[supervisord]
logfile=/root/log/supervisord.log ; supervisord log file
logfile_maxbytes=50MB ; maximum size of logfile before rotation
logfile_backups=10 ; number of backed up logfiles
loglevel=info ; info, debug, warn, trace
pidfile=/root/log/supervisord.pid ; pidfile location
nodaemon=false ; run supervisord as a daemon
minfds=1024 ; number of startup file descriptors
minprocs=200 ; number of process descriptors
user=root ; default user
childlogdir=/root/log/ ; where child log files will live
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use unix:// schem for a unix sockets.
[include]
files=celeryd.conf
创建/etc/celeryd.conf
[program:celery]
command=celery worker -A judge.judger_controller --loglevel=INFO
directory=/root/qduoj/
user=root
numprocs=1
stdout_logfile=/root/log/worker.log
stderr_logfile=/root/log/worker.log
autostart=true
autorestart=true
startsecs=1
stopwaitsecs = 6
killasgroup=true
运行supervisord
,这时候就会启动 celery 了。