From 1f7e68c662f8fbf459c4ed75071772dbeb119f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Behmo?= Date: Mon, 9 Apr 2018 19:16:58 +0200 Subject: [PATCH] Working devstack This allows the user to run their own devstack inside the containers. Yay! Also, we handle file permissions cleanly: in docker-entrypoint.sh we chmod the data and edx-platform files to the same UID of the user on the host machine. No more permission headaches! --- Makefile | 49 ++++++++++++------ README.md | 95 ++++++++++++++++++++++++++++++++--- docker-compose.yml | 20 ++++---- edxapp/Dockerfile | 35 +++++++------ edxapp/docker-entrypoint.sh | 17 +++++++ edxapp/wait-for-greenlight.sh | 2 +- 6 files changed, 168 insertions(+), 50 deletions(-) create mode 100755 edxapp/docker-entrypoint.sh diff --git a/Makefile b/Makefile index b3cd97f..8850342 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,19 @@ .PHONY: all configure build migrate assets up daemon +DOCKER_COMPOSE_RUN = docker-compose run --rm -e USERID="$$(id -u)" +ifneq ($(EDX_PLATFORM_SETTINGS),) + DOCKER_COMPOSE_RUN += -e SETTINGS=$(EDX_PLATFORM_SETTINGS) +endif +ifneq ($(EDX_PLATFORM_PATH),) + DOCKER_COMPOSE_RUN += --volume="$(EDX_PLATFORM_PATH):/openedx/edx-platform" +endif + +DOCKER_COMPOSE_RUN_LMS = $(DOCKER_COMPOSE_RUN) -p 8000:8000 lms +DOCKER_COMPOSE_RUN_CMS = $(DOCKER_COMPOSE_RUN) -p 8001:8001 cms + all: configure build migrate assets daemon -##################### Bootstrapping commands +##################### Bootstrapping configure: ./configure @@ -11,14 +22,14 @@ build: docker-compose build migrate: - docker-compose run --rm lms bash -c "./wait-for-greenlight.sh && ./manage.py lms --settings=production migrate" - docker-compose run --rm cms bash -c "./wait-for-greenlight.sh && ./manage.py cms --settings=production migrate" + $(DOCKER_COMPOSE_RUN_LMS) bash -c "wait-for-greenlight.sh && ./manage.py lms migrate" + $(DOCKER_COMPOSE_RUN_CMS) bash -c "wait-for-greenlight.sh && ./manage.py cms migrate" assets: - docker-compose run --rm lms paver update_assets lms --settings=production - docker-compose run --rm cms paver update_assets cms --settings=production + $(DOCKER_COMPOSE_RUN_LMS) paver update_assets lms + $(DOCKER_COMPOSE_RUN_CMS) paver update_assets cms -##################### Running commands +##################### Running up: docker-compose up @@ -30,16 +41,24 @@ daemon: stop: docker-compose stop -##################### Additional commands - -lms-shell: - docker-compose run --rm lms ./manage.py lms --settings=production shell -cms-shell: - docker-compose run --rm lms ./manage.py cms --settings=production shell +##################### Extra import-demo-course: - docker-compose run --rm cms /bin/bash -c "git clone https://github.com/edx/edx-demo-course ../edx-demo-course && git -C ../edx-demo-course checkout open-release/ginkgo.master && python ./manage.py cms --settings=production import ../data ../edx-demo-course" - # Seed the course permissions: is it necessary? "./manage.py lms --settings=production seed_permissions_roles 'course-v1:edX+DemoX+Demo_Course'" + $(DOCKER_COMPOSE_RUN_CMS) /bin/bash -c "git clone https://github.com/edx/edx-demo-course ../edx-demo-course && git -C ../edx-demo-course checkout open-release/ginkgo.master && python ./manage.py cms import ../data ../edx-demo-course" + # Seed the course permissions: is it necessary? "./manage.py lms seed_permissions_roles 'course-v1:edX+DemoX+Demo_Course'" create-staff-user: - docker-compose run --rm lms /bin/bash -c "./manage.py lms --settings=production manage_user --superuser --staff ${USERNAME} ${EMAIL} && ./manage.py lms --settings=production changepassword ${USERNAME}" + $(DOCKER_COMPOSE_RUN_LMS) /bin/bash -c "./manage.py lms manage_user --superuser --staff ${USERNAME} ${EMAIL} && ./manage.py lms changepassword ${USERNAME}" + + +##################### Development + +lms: + $(DOCKER_COMPOSE_RUN_LMS) bash +cms: + $(DOCKER_COMPOSE_RUN_CMS) bash + +lms-shell: + $(DOCKER_COMPOSE_RUN_LMS) ./manage.py lms shell +cms-shell: + $(DOCKER_COMPOSE_RUN_LMS) ./manage.py cms shell diff --git a/README.md b/README.md index 138ccce..819ec33 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # Open edX quick install (in Docker containers) -This is a **one-click production install of [Open edX](https://openedx.org)**. +This is a **one-click install of [Open edX](https://openedx.org), both for production and local development**. -The deployment of a full-featured Open edX platform is a highly technical and complex project... but we can remove most of the complexity by relying on pre-configured Docker containers and by using only a subset of all possible features. We made this so that non-technical people could still install Open edX by themselves: knowing how to launch a server and ssh into it should be enough. But we also made sure that every step of the deploy process could be customized if you have the technical skills. +The deployment of a full-featured Open edX platform is a highly technical and complex project. Here, we greatly simplify it by: + +1. relying on pre-configured Docker containers for external services, such as MySQL and MongoDb +2. activating only a subset of all Open edX features + +We made this project so that non-technical people could still install Open edX by themselves: knowing how to launch a server and ssh into it should be enough. But we also made sure that every step of the deploy process could be customized if you have the technical skills. ## Quickstart @@ -10,9 +15,9 @@ All you have to do is [download the content of this repository](https://codeload make all -There is no step #2. You will be asked some questions about the configuration of your Open edX platform. The build step will take some time, but then you will have both an LMS and a CMS running behind a web server on port 80. You should be able to access your platform at the address you gave during the configuration phase. +You will be asked some questions about the configuration of your Open edX platform. The build step will take some time, but then you will have both an LMS and a CMS running behind a web server on port 80, ready for production. You should be able to access your platform at the address you gave during the configuration phase. -To be honest, I really don't like 1-click installs :-p They tend to hide much of the important details. So I strongly recommend you read the more detailed instructions below to understand what is going on exactly and to troubleshoot potential issues. +To be honest, I really don't like 1-click installs :-p They tend to hide much of the important details. So I strongly recommend you read the more detailed instructions below to understand what is going on exactly and to troubleshoot potential issues. Also, instructions are given to setup a local development environment. ## Requirements @@ -22,7 +27,7 @@ The only prerequisite for running this is Python and a working docker install. Y - [Docker install](https://docs.docker.com/engine/installation/) - [Docker compose install](https://docs.docker.com/compose/install/) -Note that the web server container will bind to port 80, so if you already have a web server running (Apache or Nginx, for instance), you should stop it. +Note that the production web server container will bind to port 80, so if you already have a web server running (Apache or Nginx, for instance), you should stop it. You should be able to run Open edX on any platform that supports Docker and Python, including Mac OS and Windows. For now, only Ubuntu 16.04 was tested but we have no reason to believe the install would not work on a different OS. @@ -49,6 +54,8 @@ Building the images may require a long time, depending on your bandwidth, as you These commands should be run just once. They will create the database tables and generate static assets, such as images, stylesheets and Javascript dependencies. +If migrations are stopped with a `Killed` message, this certainly means the docker containers don't have enough RAM. See the [troubleshooting](#troubleshooting) section. + ### Running Open edX make up @@ -83,15 +90,19 @@ And then, to stop all services: ### Logging -To view the logs from all containers: +To view the logs from all containers use the [`docker-compose logs`](https://docs.docker.com/compose/reference/logs/) command: docker-compose logs -f To view the logs from just one container, for instance the web server: - docker-compose logs -f + docker-compose logs -f nginx -### Development +The last commands produce the logs since the creation of the containers, which can be a lot. Similar to a `tail -f`, you can run: + + docker-compose logs --tail=0 -f + +### Debugging Open a bash in the lms: @@ -102,6 +113,74 @@ Open a python shell in the lms or the cms: make lms-shell make cms-shell +## For developers + +In addition to running Open edX in production, you can use the docker containers for local development. This means you can hack on Open edX without setting up a Virtual Machine. Essentially, this replaces the devstack provided by edX. + +First, configure your project such that the LMS and the CMS can be accessed locally: + + make configure + ... + Your website domain name for students (LMS) (default: "www.myopenedx.com"): localhost:8000 + Your website domain name for teachers (CMS) (default: "studio.myopenedx.com"): localhost:8001 + ... + +Then, build the images and prepare the database: + + make build + make migrate + +Point to your local install of [edx-platform](https://github.com/edx/edx-platform/) on your host machine: + + export EDX_PLATFORM_PATH=/path/to/your/edx-platform + +Point to your settings file: + + export EDX_PLATFORM_SETTINGS=development + +In this example, you should have a `development.py` file in `edx-platform/lms/envs` and `edx-platform/cms/envs`. Here is a minimal settings file: + + from .devstack import * + + # Load module store settings from config files + update_module_store_settings(MODULESTORE, doc_store_settings=DOC_STORE_CONFIG) + + # Set uploaded media file path + MEDIA_ROOT = "/openedx/data/uploads/" + + # Deactivate forums + FEATURES['ENABLE_DISCUSSION_SERVICE'] = False + + # 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) + + # Create folders if necessary + import os + for folder in [LOG_DIR, MEDIA_ROOT, STATIC_ROOT_BASE]: + if not os.path.exists(folder): + os.makedirs(folder) + +You are ready to go! Run: + + make lms + +Or: + + make cms + +This will open a shell in the LMS container. You can then run just any command you are used to. For example: + + paver lms + +This will collect assets and run a development server which will **automatically reload** after you make changes to your edx-platform repository. + +Note that the containers are built on the Ginkgo release. If you are working on a different version of Open edX, you will have to rebuild the images with a different `EDX_PLATFORM_VERSION` argument. You may also want to change the `EDX_PLATFORM_REPOSITORY` argument to point to your own fork of edx-platform. + ## Troubleshooting ### "Running migrations... Killed!" diff --git a/docker-compose.yml b/docker-compose.yml index 48e2a38..cdf927b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,8 +56,8 @@ services: lms: build: context: ./edxapp - args: - service_variant: lms + environment: + SERVICE_VARIANT: lms restart: unless-stopped volumes: - ./data/lms:/openedx/data @@ -71,8 +71,8 @@ services: cms: build: context: ./edxapp - args: - service_variant: cms + environment: + SERVICE_VARIANT: cms restart: unless-stopped volumes: - ./data/cms:/openedx/data @@ -85,9 +85,9 @@ services: lms_worker: build: context: ./edxapp - args: - service_variant: lms - command: ./manage.py lms --settings=production celery worker --loglevel=info --hostname=edx.lms.core.default.%%h --maxtasksperchild 100 + environment: + SERVICE_VARIANT: lms + command: ./manage.py lms celery worker --loglevel=info --hostname=edx.lms.core.default.%%h --maxtasksperchild 100 restart: unless-stopped environment: C_FORCE_ROOT: "1" # run celery tasks as root #nofear @@ -99,9 +99,9 @@ services: cms_worker: build: context: ./edxapp - args: - service_variant: cms - command: ./manage.py cms --settings=production celery worker --loglevel=info --hostname=edx.cms.core.default.%%h --maxtasksperchild 100 + environment: + SERVICE_VARIANT: cms + command: ./manage.py cms celery worker --loglevel=info --hostname=edx.cms.core.default.%%h --maxtasksperchild 100 restart: unless-stopped environment: C_FORCE_ROOT: "1" # run celery tasks as root #nofear diff --git a/edxapp/Dockerfile b/edxapp/Dockerfile index 1d2c3dd..bb657fc 100644 --- a/edxapp/Dockerfile +++ b/edxapp/Dockerfile @@ -23,15 +23,19 @@ VOLUME /openedx/data WORKDIR /openedx/edx-platform ## Checkout edx-platform code -RUN git clone https://github.com/edx/edx-platform.git --branch open-release/ginkgo.master --depth 1 . +ARG EDX_PLATFORM_REPOSITORY=https://github.com/edx/edx-platform.git +ARG EDX_PLATFORM_VERSION=open-release/ginkgo.master +RUN git clone $EDX_PLATFORM_REPOSITORY --branch $EDX_PLATFORM_VERSION --depth 1 . +VOLUME /openedx/edx-platform -# Install python requirements -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 +# Install python requirements (clone source repos in a separate dir, otherwise +# will be overwritten when we mount edx-platform) +RUN pip install --src ../venv/src -r requirements/edx/pre.txt +RUN pip install --src ../venv/src -r requirements/edx/github.txt +RUN pip install --src ../venv/src -r requirements/edx/local.txt +RUN pip install --src ../venv/src -r requirements/edx/base.txt +RUN pip install --src ../venv/src -r requirements/edx/post.txt +RUN pip install --src ../venv/src -r requirements/edx/paver.txt # Install nodejs requirements RUN npm install @@ -46,17 +50,16 @@ COPY ./config/cms.env.json /openedx/ COPY ./config/lms.auth.json /openedx/ COPY ./config/cms.auth.json /openedx/ -# Copy convenient script -COPY ./wait-for-greenlight.sh . - -############ End of code common to lms & cms +# Copy convenient scripts +COPY ./wait-for-greenlight.sh /usr/local/bin/ +COPY ./docker-entrypoint.sh /usr/local/bin/ # service variant is "lms" or "cms" -ARG service_variant +ENV SERVICE_VARIANT lms +ENV SETTINGS production -# Configure environment -ENV DJANGO_SETTINGS_MODULE ${service_variant}.envs.production -ENV SERVICE_VARIANT ${service_variant} +# Entrypoint will fix permissiosn of all files and run commands as openedx +ENTRYPOINT ["docker-entrypoint.sh"] # Run server EXPOSE 8000 diff --git a/edxapp/docker-entrypoint.sh b/edxapp/docker-entrypoint.sh new file mode 100755 index 0000000..feecc57 --- /dev/null +++ b/edxapp/docker-entrypoint.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e +export DJANGO_SETTINGS_MODULE=$SERVICE_VARIANT.envs.$SETTINGS +USERID=${USERID:=1000} + +## Configure user with a different USERID if requested. +if [ "$USERID" -ne 1000 ] + then + echo "creating new user 'openedx' with UID $USERID" + useradd -m openedx -u $USERID + chown -R openedx /openedx + + # Run CMD as different user + exec chroot --userspec="$USERID" --skip-chdir / "$@" +else + # Run CMD as root (business as usual) + exec "$@" +fi diff --git a/edxapp/wait-for-greenlight.sh b/edxapp/wait-for-greenlight.sh index 9eda518..b7537d5 100755 --- a/edxapp/wait-for-greenlight.sh +++ b/edxapp/wait-for-greenlight.sh @@ -1,7 +1,7 @@ #!/bin/bash echo "Checking system..." -until ./manage.py lms --settings=production check +until ./manage.py $SERVICE_VARIANT check do printf "." sleep 1