diff --git a/deploy/Dockerfile b/deploy/Dockerfile index ce70ae41..4b8bfced 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -1,5 +1,10 @@ -FROM python:3.6 +FROM python:3.6-alpine3.6 + ADD requirements.txt /tmp -RUN pip install -r /tmp/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple -WORKDIR /app -CMD python manage.py runserver 0.0.0.0:8085 +RUN apk add --no-cache --virtual .build-deps build-base jpeg-dev zlib-dev postgresql-dev && \ + pip install -r /tmp/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple && \ + apk del .build-deps --purge + +Volume ["/app"] + +CMD sh /app/deploy/run.sh diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 00000000..82098072 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,110 @@ +user nginx; + +# Set number of worker processes automatically based on number of CPU cores. +worker_processes auto; + +# Enables the use of JIT for regular expressions to speed-up their processing. +pcre_jit on; + +# Configures default error logger. +error_log /dev/stderr warn; + +# use supervisor to monitor +daemon off; + +# set pid path +pid /tmp/nginx.pid; + +# Includes files with directives to load dynamic modules. +include /etc/nginx/modules/*.conf; + + +events { + # The maximum number of simultaneous connections that can be opened by + # a worker process. + worker_connections 1024; +} + +http { + # Includes mapping of file name extensions to MIME types of responses + # and defines the default type. + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Name servers used to resolve names of upstream servers into addresses. + # It's also needed when using tcpsocket and udpsocket in Lua modules. + #resolver 208.67.222.222 208.67.220.220; + + # Don't tell nginx version to clients. + server_tokens off; + + # Specifies the maximum accepted body size of a client request, as + # indicated by the request header Content-Length. If the stated content + # length is greater than this size, then the client receives the HTTP + # error code 413. Set to 0 to disable. + client_max_body_size 50m; + + # Timeout for keep-alive connections. Server will close connections after + # this time. + keepalive_timeout 65; + + # Sendfile copies data between one FD and other from within the kernel, + # which is more efficient than read() + write(). + sendfile on; + + # Don't buffer data-sends (disable Nagle algorithm). + # Good for sending frequent small bursts of data in real time. + tcp_nodelay on; + + # Causes nginx to attempt to send its HTTP response head in one packet, + # instead of using partial frames. + #tcp_nopush on; + + + # Path of the file with Diffie-Hellman parameters for EDH ciphers. + #ssl_dhparam /etc/ssl/nginx/dh2048.pem; + + # Specifies that our cipher suits should be preferred over client ciphers. + ssl_prefer_server_ciphers on; + + # Enables a shared SSL cache with size that can hold around 8000 sessions. + ssl_session_cache shared:SSL:2m; + + + # Enable gzipping of responses. + gzip on; + gzip_types application/javascript text/css; + + # Set the Vary HTTP header as defined in the RFC 2616. + gzip_vary on; + + # Enable checking the existence of precompressed files. + #gzip_static on; + + + # Specifies the main log format. + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + # Sets the path, format, and configuration for a buffered log write. + # access_log /var/log/nginx/access.log main; + access_log off + + server { + listen 80 default_server; + server_name _; + keetalive_timeout 5; + + location /static/avatar { + expires max; + alias /app/static/avatar; + } + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + + proxy_pass http://127.0.0.1:8080; + } + } +} diff --git a/deploy/requirements.txt b/deploy/requirements.txt index f6bb6fe7..70aa2e56 100644 --- a/deploy/requirements.txt +++ b/deploy/requirements.txt @@ -12,3 +12,4 @@ qrcode flake8-coding requests django-redis +psycopg2 diff --git a/deploy/run.sh b/deploy/run.sh new file mode 100644 index 00000000..fbc4b3ca --- /dev/null +++ b/deploy/run.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +BASE=/app + +if [ ! -f "$BASE/custom_settings.py" ]; then + echo SECRET_KEY=\"$(cat /dev/urandom | head -1 | md5sum | head -c 32)\" >> /app/oj/custom_settings.py +fi + +if [ ! -d "$BASE/log" ]; then + mkdir -p $BASE/log +fi + +cd $BASE +find . -name "*.pyc" -delete + +python manage.py migrate +if [ $? -ne 0 ]; then + echo "Can't start server" + exit 1 +fi +python manage.py initadmin +python manage.py runserver 0.0.0.0:8080 diff --git a/deploy/supervisor.conf b/deploy/supervisor.conf new file mode 100644 index 00000000..55f53139 --- /dev/null +++ b/deploy/supervisor.conf @@ -0,0 +1,45 @@ +[supervisord] +logfile=/app/log/supervisord.log +logfile_maxbytes=10MB +logfile_backups=10 +loglevel=info +pidfile=/tmp/supervisord.pid +nodaemon=true +childlogdir=/app/log/ + +[supervisorctl] +serverurl=unix:///tmp/supervisor.sock + +[program:gunicorn] +command=gunicorn oj.wsgi --user nobody -b 127.0.0.1:8080 --reload -w `grep -c ^processor /proc/cpuinfo` +directory=/app/ +stdout_logfile=/app/log/gunicorn.log +stderr_logfile=/app/log/gunicorn.log +autostart=true +autorestart=true +startsecs=5 +stopwaitsecs = 5 +killasgroup=true + +[program:task_queue] +command=celery -A oj worker -l warning +directory=/app/ +user=nobody +stdout_logfile=/app/log/celery.log +stderr_logfile=/app/log/celery.log +autostart=true +autorestart=true +startsecs=5 +stopwaitsecs = 5 +killasgroup=true + +[program:nginx] +command=nginx -c /app/deploy/nginx.conf +directory=/app/ +stdout_logfile=/app/log/nginx.log +stderr_logfile=/app/log/nginx.log +autostart=true +autorestart=true +startsecs=5 +stopwaitsecs = 5 +killasgroup=true \ No newline at end of file diff --git a/oj/local_settings.py b/oj/dev_settings.py similarity index 86% rename from oj/local_settings.py rename to oj/dev_settings.py index bbe2398f..cb409383 100644 --- a/oj/local_settings.py +++ b/oj/dev_settings.py @@ -14,14 +14,12 @@ DATABASES = { } } - -# For celery -REDIS_QUEUE = { +REDIS_CONF = { "host": "127.0.0.1", - "port": 6379, - "db": 4 + "port": "6379" } + DEBUG = True ALLOWED_HOSTS = ["*"] @@ -31,5 +29,3 @@ TEST_CASE_DIR = "/tmp" STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ] - -LOG_PATH = "log/" diff --git a/oj/production_settings.py b/oj/production_settings.py new file mode 100644 index 00000000..7d892769 --- /dev/null +++ b/oj/production_settings.py @@ -0,0 +1,28 @@ +import os + + +def get_env(name, default=""): + return os.environ.get(name, default) + + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'HOST': get_env("POSTGRESQL_HOST", "postgresql"), + 'PORT': get_env("POSTGRESQL_PORT", "5433"), + 'NAME': get_env("POSTGRESQL_DBNAME"), + 'USER': get_env("POSTGRESQL_USER"), + 'PASSWORD': get_env("POSTGRESQL_PASSWORD") + } +} + +REDIS_CONF = { + "host": get_env("REDIS_HOST", "redis"), + "port": get_env("REDIS_PORT", "6379") +} + +DEBUG = False + +ALLOWED_HOSTS = ['*'] + +TEST_CASE_DIR = "/test_case" diff --git a/oj/server_settings.py b/oj/server_settings.py deleted file mode 100644 index 7434ee70..00000000 --- a/oj/server_settings.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding=utf-8 -import os - -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': "oj", - 'CONN_MAX_AGE': 0.1, - 'HOST': os.environ["MYSQL_PORT_3306_TCP_ADDR"], - 'PORT': 3306, - 'USER': os.environ["MYSQL_ENV_MYSQL_USER"], - 'PASSWORD': os.environ["MYSQL_ENV_MYSQL_ROOT_PASSWORD"] - } -} - -REDIS_CACHE = { - "host": os.environ["REDIS_PORT_6379_TCP_ADDR"], - "port": 6379, - "db": 1 -} - -REDIS_QUEUE = { - "host": os.environ["REDIS_PORT_6379_TCP_ADDR"], - "port": 6379, - "db": 2 -} - -DEBUG = False - -ALLOWED_HOSTS = ['*'] - - -TEST_CASE_DIR = "/test_case" - -LOG_PATH = "log/" diff --git a/oj/settings.py b/oj/settings.py index 950c426d..a5fe7db4 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -1,8 +1,7 @@ -# coding=utf-8 """ Django settings for oj project. -Generated by 'django-admin startproject' using Django 1.8. +Generated by 'django-admin startproject' using Django 1.11. For more information on this file, see https://docs.djangoproject.com/en/1.8/topics/settings/ @@ -10,27 +9,19 @@ https://docs.djangoproject.com/en/1.8/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.8/ref/settings/ """ -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os from .custom_settings import * -# 判断运行环境 -ENV = os.environ.get("oj_env", "local") - -if ENV == "local": - from .local_settings import * -elif ENV == "server": - from .server_settings import * +if os.environ.get("NODE_ENV") == "production": + from .production_settings import * +else: + from .dev_settings import * BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ - -# Application definition - +# Applications INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.sessions', @@ -117,6 +108,7 @@ USE_TZ = True STATIC_URL = '/static/' AUTH_USER_MODEL = 'account.User' +LOG_PATH = "log/" LOGGING = { 'version': 1, @@ -170,9 +162,7 @@ REST_FRAMEWORK = { ) } - -REDIS_URL = "redis://127.0.0.1:6379" - +REDIS_URL = "redis://%s:%s" % (REDIS_CONF["host"], REDIS_CONF["port"]) def redis_config(db): def make_key(key, key_prefix, version): @@ -191,23 +181,14 @@ CACHES = { } -CELERY_RESULT_BACKEND = CELERY_BROKER_URL = f"{REDIS_URL}/2" -CELERY_TASK_SOFT_TIME_LIMIT = CELERY_TASK_TIME_LIMIT = 180 SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "default" -# For celery -REDIS_QUEUE = { - "host": "127.0.0.1", - "port": 6379, - "db": 4 -} - - -# for celery -BROKER_URL = 'redis://%s:%s/%s' % (REDIS_QUEUE["host"], str(REDIS_QUEUE["port"]), str(REDIS_QUEUE["db"])) +CELERY_RESULT_BACKEND = f"{REDIS_URL}/2" +BROKER_URL = f"{REDIS_URL}/3" +CELERY_TASK_SOFT_TIME_LIMIT = CELERY_TASK_TIME_LIMIT = 180 CELERY_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" diff --git a/submission/views/oj.py b/submission/views/oj.py index 2430a206..613bd385 100644 --- a/submission/views/oj.py +++ b/submission/views/oj.py @@ -1,6 +1,6 @@ from account.decorators import login_required, check_contest_permission -# from judge.tasks import judge_task -from judge.dispatcher import JudgeDispatcher +from judge.tasks import judge_task +# from judge.dispatcher import JudgeDispatcher from problem.models import Problem, ProblemRuleType from contest.models import Contest, ContestStatus, ContestRuleType from utils.api import APIView, validate_serializer @@ -39,8 +39,8 @@ def _submit(response, user, problem_id, language, code, contest_id): problem_id=problem.id, contest_id=contest_id) # use this for debug - JudgeDispatcher(submission.id, problem.id).judge() - # judge_task.delay(submission.id, problem.id) + # JudgeDispatcher(submission.id, problem.id).judge() + judge_task.delay(submission.id, problem.id) return response.success({"submission_id": submission.id})