mirror of
https://github.com/ChristianLight/tutor.git
synced 2025-01-05 15:12:10 +00:00
🌅
This commit is contained in:
commit
bdd1a41f62
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.*.swp
|
||||
data/*
|
36
README.md
Normal file
36
README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# [WIP] Docker-compose Open edX production stack
|
||||
|
||||
This is a work-in-progress.
|
||||
|
||||
The production stack is composed of Nginx, MySQL, MongoDB, Memcache and an LMS container.
|
||||
|
||||
## Install
|
||||
|
||||
Create necessary data folders:
|
||||
|
||||
mkdir -p data/lms/course_static data/lms/data data/lms/logs data/lms/staticfiles data/lms/uploads
|
||||
mkdir -p data/mongodb data/mysql
|
||||
|
||||
## Lauch a production stack
|
||||
|
||||
docker-compose up --build
|
||||
|
||||
Container data is in `./data`.
|
||||
|
||||
## Development tips & tricks
|
||||
|
||||
Open a bash in the lms:
|
||||
|
||||
docker-compose run lms bash
|
||||
|
||||
How to find the IP address of a running docker:
|
||||
|
||||
docker container ls
|
||||
docker inspect a0fc4cc602f8
|
||||
|
||||
## TODO
|
||||
|
||||
- Add a CMS container
|
||||
- Add rabbitmq and celery worker containers
|
||||
- Make sure that secret keys are not shared with the entire world
|
||||
- Fix TODOs
|
53
docker-compose.yml
Normal file
53
docker-compose.yml
Normal file
@ -0,0 +1,53 @@
|
||||
version: "3"
|
||||
services:
|
||||
|
||||
memcached:
|
||||
image: memcached:1.4.38
|
||||
|
||||
mongodb:
|
||||
# Use WiredTiger in all environments, just like at edx.org
|
||||
command: mongod --smallfiles --nojournal --storageEngine wiredTiger
|
||||
image: mongo:3.0.14
|
||||
volumes:
|
||||
- ./data/mongodb:/data/db
|
||||
|
||||
mysql:
|
||||
image: mysql:5.6.36
|
||||
command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ""
|
||||
MYSQL_DATABASE: "openedx"
|
||||
MYSQL_USER: "openedx"
|
||||
MYSQL_PASSWORD: "password"
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
||||
volumes:
|
||||
- ./data/mysql:/var/lib/mysql
|
||||
|
||||
nginx:
|
||||
build: ./nginx
|
||||
volumes:
|
||||
- ./data/lms/course_static:/openedx/course_static:ro
|
||||
- ./data/lms/staticfiles:/openedx/staticfiles:ro
|
||||
- ./data/lms/uploads:/openedx/uploads:ro
|
||||
depends_on:
|
||||
- lms
|
||||
|
||||
lms:
|
||||
build:
|
||||
context: ./lms
|
||||
args:
|
||||
RUN_MIGRATIONS: 1
|
||||
COLLECT_STATIC: 1
|
||||
volumes:
|
||||
- ./data/lms/course_static:/openedx/course_static
|
||||
- ./data/lms/data:/openedx/data
|
||||
- ./data/lms/logs:/openedx/logs
|
||||
- ./data/lms/staticfiles:/openedx/staticfiles
|
||||
- ./data/lms/uploads:/openedx/uploads
|
||||
depends_on:
|
||||
- memcached
|
||||
- mongodb
|
||||
- mysql
|
||||
|
||||
|
||||
# TODO rabbitmq and celery workers
|
68
lms/Dockerfile
Normal file
68
lms/Dockerfile
Normal file
@ -0,0 +1,68 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
# Install system requirements
|
||||
RUN apt update
|
||||
RUN apt upgrade -y
|
||||
# Global requirements
|
||||
RUN apt install -y language-pack-en git python-virtualenv build-essential software-properties-common curl git-core libxml2-dev libxslt1-dev python-pip libmysqlclient-dev python-apt python-dev libxmlsec1-dev libfreetype6-dev swig gcc g++
|
||||
# lms requirements
|
||||
RUN apt install -y gettext gfortran graphviz graphviz-dev libffi-dev libfreetype6-dev libgeos-dev libjpeg8-dev liblapack-dev libpng12-dev libxml2-dev libxmlsec1-dev libxslt1-dev nodejs npm ntp pkg-config
|
||||
|
||||
# Install symlink so that we have access to 'node' binary without virtualenv.
|
||||
# This replaces the "nodeenv" install.
|
||||
RUN apt install -y nodejs-legacy
|
||||
|
||||
# Create necessary folders
|
||||
RUN mkdir /openedx
|
||||
RUN mkdir /openedx/data
|
||||
RUN mkdir /openedx/logs
|
||||
RUN mkdir /openedx/uploads
|
||||
RUN mkdir /openedx/staticfiles
|
||||
VOLUME /openedx/data
|
||||
VOLUME /openedx/logs
|
||||
VOLUME /openedx/staticfiles
|
||||
VOLUME /openedx/uploads
|
||||
WORKDIR /openedx
|
||||
|
||||
# Checkout edx-platform code
|
||||
RUN git clone https://github.com/edx/edx-platform.git
|
||||
WORKDIR /openedx/edx-platform
|
||||
RUN git checkout open-release/ficus.master
|
||||
|
||||
# Install python requirements
|
||||
RUN pip install pip==8.1.2
|
||||
RUN pip install setuptools==24.0.3
|
||||
RUN pip install -r requirements/edx/pre.txt
|
||||
RUN pip install -r requirements/edx/github.txt
|
||||
RUN pip install -r requirements/edx/local.txt
|
||||
RUN pip install -r requirements/edx/base.txt
|
||||
RUN pip install -r requirements/edx/post.txt
|
||||
RUN pip install -r requirements/edx/paver.txt
|
||||
|
||||
# Finish requirements install
|
||||
RUN paver install_prereqs
|
||||
|
||||
# Copy configuration files
|
||||
COPY ./lms.env.json /openedx/
|
||||
COPY ./lms.auth.json /openedx/
|
||||
COPY ./production.py /openedx/edx-platform/lms/envs/
|
||||
|
||||
# Configure environment
|
||||
ENV DJANGO_SETTINGS_MODULE lms.envs.production
|
||||
ENV SERVICE_VARIANT lms
|
||||
|
||||
# Run server
|
||||
EXPOSE 8000
|
||||
|
||||
# Migrate
|
||||
ARG RUN_MIGRATIONS=0
|
||||
ENV RUN_MIGRATIONS ${RUN_MIGRATIONS}
|
||||
# Collect static assets
|
||||
ARG COLLECT_STATIC=0
|
||||
ENV COLLECT_STATIC ${COLLECT_STATIC}
|
||||
|
||||
# TODO Here we wait until mysql and mongodb become available but it's a terrible solution
|
||||
CMD sleep 5 && \
|
||||
if [ "$RUN_MIGRATIONS" = "1" ] ; then ./manage.py lms migrate --settings=production ; fi && \
|
||||
if [ "$COLLECT_STATIC" = "1" ] ; then paver update_assets lms --settings=production ; fi && \
|
||||
gunicorn --name lms --bind=0.0.0.0:8000 --max-requests=1000 lms.wsgi:application
|
73
lms/lms.auth.json
Normal file
73
lms/lms.auth.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"SECRET_KEY": "7i#nri2i@--brp0sri9qf@ewlj1qxghv0%af$sk4ntn9pv$8t#",
|
||||
"AWS_ACCESS_KEY_ID": "",
|
||||
"AWS_SECRET_ACCESS_KEY": "",
|
||||
"XQUEUE_INTERFACE": {
|
||||
"basic_auth": ["edx", "edx"],
|
||||
"django_auth": {
|
||||
"username": "lms",
|
||||
"password": "password"
|
||||
},
|
||||
"url": "http://localhost:18040"
|
||||
},
|
||||
"CONTENTSTORE": {
|
||||
"ENGINE": "xmodule.contentstore.mongo.MongoContentStore",
|
||||
"DOC_STORE_CONFIG": {
|
||||
"db": "edxapp",
|
||||
"host": "mongodb"
|
||||
}
|
||||
},
|
||||
"DOC_STORE_CONFIG": {
|
||||
"db": "edxapp",
|
||||
"host": "mongodb"
|
||||
},
|
||||
"DATABASES": {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"NAME": "openedx",
|
||||
"USER": "openedx",
|
||||
"PASSWORD": "password",
|
||||
"HOST": "mysql",
|
||||
"PORT": "3306",
|
||||
"ATOMIC_REQUESTS": true
|
||||
}
|
||||
},
|
||||
"MODULESTORE": {
|
||||
"default": {
|
||||
"ENGINE": "xmodule.modulestore.mixed.MixedModuleStore",
|
||||
"OPTIONS": {
|
||||
"mappings": {},
|
||||
"stores": [
|
||||
{
|
||||
"NAME": "split",
|
||||
"ENGINE": "xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore",
|
||||
"DOC_STORE_CONFIG": {
|
||||
"host": "mongodb",
|
||||
"db": "xmodule",
|
||||
"collection": "modulestore"
|
||||
},
|
||||
"OPTIONS": {
|
||||
"default_class": "xmodule.hidden_module.HiddenDescriptor",
|
||||
"fs_root": "/openedx/data",
|
||||
"render_template": "edxmako.shortcuts.render_to_string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"NAME": "draft",
|
||||
"ENGINE": "xmodule.modulestore.mongo.DraftMongoModuleStore",
|
||||
"DOC_STORE_CONFIG": {
|
||||
"host": "mongodb",
|
||||
"db": "xmodule",
|
||||
"collection": "modulestore"
|
||||
},
|
||||
"OPTIONS": {
|
||||
"default_class": "xmodule.hidden_module.HiddenDescriptor",
|
||||
"fs_root": "/opt/openedx/data",
|
||||
"render_template": "edxmako.shortcuts.render_to_string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
lms/lms.env.json
Normal file
74
lms/lms.env.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"SITE_NAME": "myopenedx.com",
|
||||
"BOOK_URL": "",
|
||||
"LOG_DIR": "/openedx/logs",
|
||||
"LOGGING_ENV": "sandbox",
|
||||
"OAUTH_OIDC_ISSUER": "http://localhost:8000/oauth2",
|
||||
"PLATFORM_NAME": "My Open edX",
|
||||
"FEATURES": {
|
||||
"PREVIEW_LMS_BASE": "localhost:8000"
|
||||
},
|
||||
"LMS_ROOT_URL": "http://myopenedx.com",
|
||||
"CMS_ROOT_URL": "http://studio.myopenedx.com",
|
||||
"CMS_BASE": "studio.myopenedx.com",
|
||||
"LMS_BASE": "myopenedx.com",
|
||||
"CELERY_BROKER_HOSTNAME": "localhost",
|
||||
"CELERY_BROKER_TRANSPORT": "amqp",
|
||||
"MEDIA_ROOT": "/openedx/uploads/",
|
||||
"CACHES": {
|
||||
"default": {
|
||||
"KEY_PREFIX": "default",
|
||||
"VERSION": "1",
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"KEY_FUNCTION": "util.memcache.safe_key",
|
||||
"KEY_PREFIX": "default",
|
||||
"LOCATION": "memcached:11211"
|
||||
},
|
||||
"general": {
|
||||
"KEY_PREFIX": "general",
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"KEY_FUNCTION": "util.memcache.safe_key",
|
||||
"KEY_PREFIX": "default",
|
||||
"LOCATION": "memcached:11211"
|
||||
},
|
||||
"mongo_metadata_inheritance": {
|
||||
"KEY_PREFIX": "mongo_metadata_inheritance",
|
||||
"TIMEOUT": 300,
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"KEY_FUNCTION": "util.memcache.safe_key",
|
||||
"KEY_PREFIX": "default",
|
||||
"LOCATION": "memcached:11211"
|
||||
},
|
||||
"staticfiles": {
|
||||
"KEY_PREFIX": "staticfiles_general",
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"KEY_FUNCTION": "util.memcache.safe_key",
|
||||
"KEY_PREFIX": "default",
|
||||
"LOCATION": "memcached:11211"
|
||||
},
|
||||
"configuration": {
|
||||
"KEY_PREFIX": "configuration",
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"KEY_FUNCTION": "util.memcache.safe_key",
|
||||
"KEY_PREFIX": "default",
|
||||
"LOCATION": "memcached:11211"
|
||||
},
|
||||
"celery": {
|
||||
"KEY_PREFIX": "celery",
|
||||
"TIMEOUT": "7200",
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"KEY_FUNCTION": "util.memcache.safe_key",
|
||||
"KEY_PREFIX": "default",
|
||||
"LOCATION": "memcached:11211"
|
||||
},
|
||||
"course_structure_cache": {
|
||||
"KEY_PREFIX": "course_structure",
|
||||
"LOCATION": "memcached:11211",
|
||||
"TIMEOUT": "7200",
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"KEY_FUNCTION": "util.memcache.safe_key",
|
||||
"KEY_PREFIX": "default",
|
||||
"LOCATION": "memcached:11211"
|
||||
}
|
||||
}
|
||||
}
|
17
lms/production.py
Normal file
17
lms/production.py
Normal file
@ -0,0 +1,17 @@
|
||||
from .aws import *
|
||||
MEDIA_ROOT = "/openedx/uploads/"
|
||||
FEATURES['ENABLE_DISCUSSION_SERVICE'] = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
'*',
|
||||
ENV_TOKENS.get('LMS_BASE'),
|
||||
FEATURES['PREVIEW_LMS_BASE'],
|
||||
]
|
||||
|
||||
# We need to activate dev_env for logging, otherwise rsyslog is required (but
|
||||
# it is not available in docker).
|
||||
LOGGING = get_logger_config(LOG_DIR,
|
||||
logging_env=ENV_TOKENS['LOGGING_ENV'],
|
||||
debug=False,
|
||||
dev_env=True,
|
||||
service_variant=SERVICE_VARIANT)
|
15
nginx/Dockerfile
Normal file
15
nginx/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM nginx:1.13
|
||||
|
||||
RUN mkdir /openedx
|
||||
RUN mkdir /openedx/course_static
|
||||
RUN mkdir /openedx/static_files
|
||||
RUN mkdir /openedx/uploads
|
||||
|
||||
VOLUME /openedx/course_static
|
||||
VOLUME /openedx/staticfiles
|
||||
VOLUME /openedx/uploads
|
||||
|
||||
# Wait until LMS becomes available
|
||||
# TODO we shouldn't have to wait
|
||||
RUN sleep 10
|
||||
COPY lms.conf /etc/nginx/conf.d/lms.conf
|
89
nginx/lms.conf
Normal file
89
nginx/lms.conf
Normal file
@ -0,0 +1,89 @@
|
||||
upstream lms-backend {
|
||||
server lms:8000 fail_timeout=0;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name myopenedx.com;
|
||||
|
||||
# Prevent invalid display courseware in IE 10+ with high privacy settings
|
||||
add_header P3P 'CP="Open edX does not have a P3P policy."';
|
||||
|
||||
# Nginx does not support nested condition or or conditions so
|
||||
# there is an unfortunate mix of conditonals here.
|
||||
|
||||
client_max_body_size 4M;
|
||||
|
||||
rewrite ^(.*)/favicon.ico$ /static/images/favicon.ico last;
|
||||
|
||||
# Disables server version feedback on pages and in headers
|
||||
server_tokens off;
|
||||
|
||||
location @proxy_to_lms_app {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_redirect off;
|
||||
proxy_pass http://lms-backend;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri @proxy_to_lms_app;
|
||||
}
|
||||
|
||||
# /login?next=<any image> can be used by 3rd party sites in <img> tags to
|
||||
# determine whether a user on their site is logged into edX.
|
||||
# The most common image to use is favicon.ico.
|
||||
location /login {
|
||||
if ( $arg_next ~* "favicon.ico" ) {
|
||||
return 403;
|
||||
}
|
||||
try_files $uri @proxy_to_lms_app;
|
||||
}
|
||||
|
||||
# Need a separate location for the image uploads endpoint to limit upload sizes
|
||||
location ~ ^/api/profile_images/[^/]*/[^/]*/upload$ {
|
||||
try_files $uri @proxy_to_lms_app;
|
||||
client_max_body_size 1049576;
|
||||
}
|
||||
|
||||
location ~ ^/media/(?P<file>.*) {
|
||||
root /openedx/uploads;
|
||||
try_files /$file =404;
|
||||
expires 31536000s;
|
||||
}
|
||||
|
||||
location ~ ^/static/(?P<file>.*) {
|
||||
root /openedx;
|
||||
try_files /staticfiles/$file /course_static/$file =404;
|
||||
|
||||
# return a 403 for static files that shouldn't be
|
||||
# in the staticfiles directory
|
||||
location ~ ^/static/(?:.*)(?:\.xml|\.json|README.TXT) {
|
||||
return 403;
|
||||
}
|
||||
|
||||
# Set django-pipelined files to maximum cache time
|
||||
location ~ "/static/(?P<collected>.*\.[0-9a-f]{12}\..*)" {
|
||||
expires max;
|
||||
# Without this try_files, files that have been run through
|
||||
# django-pipeline return 404s
|
||||
try_files /staticfiles/$collected /course_static/$collected =404;
|
||||
}
|
||||
|
||||
# Set django-pipelined files for studio to maximum cache time
|
||||
location ~ "/static/(?P<collected>[0-9a-f]{7}/.*)" {
|
||||
expires max;
|
||||
|
||||
# Without this try_files, files that have been run through
|
||||
# django-pipeline return 404s
|
||||
try_files /staticfiles/$collected /course_static/$collected =404;
|
||||
}
|
||||
|
||||
# Expire other static files immediately (there should be very few / none of these)
|
||||
expires epoch;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user