From 73163c76b99212bfa6a6e53aeb85b0d52fc42894 Mon Sep 17 00:00:00 2001 From: Justin Tyberg Date: Thu, 14 Apr 2016 15:00:15 -0400 Subject: [PATCH 1/4] Initial version. (c) Copyright IBM Corp. 2016 --- .dockerignore | 5 + .env | 27 ++++ .gitignore | 65 +------- Dockerfile.jupyterhub | 19 +++ README.md | 151 +++++++++++++++++- docker-compose.yml | 52 ++++++ examples/custom-notebook-server/Dockerfile | 21 +++ examples/custom-notebook-server/README.md | 28 ++++ .../docker-entrypoint.sh | 20 +++ examples/letsencrypt/README.md | 47 ++++++ examples/letsencrypt/docker-compose.yml | 38 +++++ examples/letsencrypt/letsencrypt.sh | 84 ++++++++++ hub.sh | 29 ++++ internal/jupyterhub-docker.png | Bin 0 -> 91693 bytes jupyterhub_config.py | 81 ++++++++++ 15 files changed, 604 insertions(+), 63 deletions(-) create mode 100644 .dockerignore create mode 100644 .env create mode 100644 Dockerfile.jupyterhub create mode 100644 docker-compose.yml create mode 100644 examples/custom-notebook-server/Dockerfile create mode 100644 examples/custom-notebook-server/README.md create mode 100755 examples/custom-notebook-server/docker-entrypoint.sh create mode 100644 examples/letsencrypt/README.md create mode 100644 examples/letsencrypt/docker-compose.yml create mode 100755 examples/letsencrypt/letsencrypt.sh create mode 100755 hub.sh create mode 100644 internal/jupyterhub-docker.png create mode 100644 jupyterhub_config.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..781ab7ae --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.gitignore +examples/ +internal/ +README.md diff --git a/.env b/.env new file mode 100644 index 00000000..ba0aa1e8 --- /dev/null +++ b/.env @@ -0,0 +1,27 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# Use this file to set default values for environment variables specified in +# docker-compose configuration file. docker-compose will substitute these +# values for environment variables in the configuration file IF the variables +# are not set in the shell environment. + +# To override these values, set the shell environment variables. + +# Name of Docker machine +DOCKER_MACHINE_NAME=jupyterhub + +# Name of Docker network +DOCKER_NETWORK_NAME=jupyterhub-network + +# Single-user Jupyter Notebook server container image +DOCKER_CONTAINER_IMAGE=jupyter/scipy-notebook:2d878db5cbff + +# Docker run command to use when spawning single-user containers +DOCKER_SPAWN_CMD=start-singleuser.sh + +# Name of JupyterHub container data volume +DATA_VOLUME_HOST=jupyterhub-data + +# Data volume container mount point +DATA_VOLUME_CONTAINER=/data diff --git a/.gitignore b/.gitignore index 1dbc687d..8e924e5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,62 +1,3 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -#Ipython Notebook -.ipynb_checkpoints +secrets/ +userlist +.DS_Store diff --git a/Dockerfile.jupyterhub b/Dockerfile.jupyterhub new file mode 100644 index 00000000..0b5d8cde --- /dev/null +++ b/Dockerfile.jupyterhub @@ -0,0 +1,19 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +FROM jupyter/jupyterhub:master + +# Install dockerspawner and its dependencies +RUN /opt/conda/bin/pip install \ + -e git+https://github.com/jupyter/jupyterhub@62a5e9dbce86cbb8992def81600ff9881d515935#egg=jupyterhub \ + -e git+https://github.com/jupyter/oauthenticator@011f6ea25c6bafca087d94a6c73d24dcbb0bf80e#egg=oauthenticator \ + -e git+https://github.com/jupyter/dockerspawner@3c5e36bc96a252a04bb7700fdb009bd572996f3a#egg=dockerspawner + +# Copy TLS certificate and key +ENV SSL_CERT /srv/jupyterhub/secrets/jupyterhub.cer +ENV SSL_KEY /srv/jupyterhub/secrets/jupyterhub.key +COPY ./secrets/*.cer $SSL_CERT +COPY ./secrets/*.key $SSL_KEY +RUN chmod 700 /srv/jupyterhub/secrets && \ + chmod 600 /srv/jupyterhub/secrets/* + +COPY ./userlist /srv/jupyterhub/userlist diff --git a/README.md b/README.md index 68c4e48a..bcf5b760 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,151 @@ # jupyterhub-deploy-docker -Reference deployment of JupyterHub with docker + +This repository provides a reference deployment of [JupyterHub](https://github.com/jupyter/jupyterhub), a multi-user [Jupyter Notebook](http://jupyter.org/) environment, on a **single host** using [Docker](https://docs.docker.com). + +This deployment: + +* Runs the [JupyterHub components](https://jupyterhub.readthedocs.org/en/latest/getting-started.html#overview) in a Docker container on the host +* Uses [DockerSpawner](https://github.com/jupyter/dockerspawner) to spawn single-user Jupyter Notebook servers in separate Docker containers on the same host +* Persists JupyterHub data in a Docker volume on the host +* Persists user notebook directories in Docker volumes on the host +* Uses [OAuthenticator](https://github.com/jupyter/oauthenticator) and [GitHub OAuth](https://developer.github.com/v3/oauth/) to authenticate users + +![JupyterHub single host Docker deployment](internal/jupyterhub-docker.png) + +## Use Cases + +Possible use cases for this deployment may include, but are not limited to: + +* A JupyterHub demo environment that you can spin up relatively quickly. +* A multi-user Jupyter Notebook environment for small classes, teams, or departments. + +## Disclaimer + +This deployment is **NOT** intended for a production environment. + +## Prerequisites + +* This deployment uses Docker for all the things. It assumes that you wiil be using [Docker Machine](https://docs.docker.com/machine/overview/) and [Docker Compose](https://docs.docker.com/compose/overview/) on a local workstation or laptop to create, build, and run Docker images on a remote host. It requires [Docker Toolbox](https://www.docker.com/products/docker-toolbox) 1.11.0 or higher. See the [installation instructions](https://docs.docker.com/engine/installation/) for your environment. +* This example configures JupyterHub for HTTPS connections (the default). As such, you must provide TLS certificate chain and key files to the JupyterHub server. If you do not have your own certificate chain and key, you can either create self-signed versions, or obtain real ones from [Let's Encrypt](https://letsencrypt.org) (see the [letsencrypt example](examples/letsencrypt/README.md) for instructions). + +## Create a Docker Machine + +Use [Docker Machine](https://docs.docker.com/machine/) to provision a new host, or to connect to an existing one. + +This example assumes that a remote host already exists at IP address `10.0.0.10`, and that you can perform password-less login to this host from your local workstation or laptop using a private SSH key. + +To create a Docker machine that will install and configure the Docker daemon on an existing host (note that Docker Machine will upgrade the Docker daemon on the host if it is already installed): + +``` +# Use the generic driver to create a Docker machine that +# controls the Docker daemon on an existing host +export DRIVER_OPTS="--driver generic \ + --generic-ip-address 10.0.0.10 \ + --generic-ssh-key /path/to/private/ssh/key" + +# Create a machine named jupyterhub +docker-machine create $DRIVER_OPTS jupyterhub + +# Activate the machine +eval "$(docker-machine env jupyterhub)" +``` + +Docker Machine can also provision new hosts using one of it's [supported drivers](https://docs.docker.com/machine/drivers/). When provisioning a host, Docker Machine will automatically generate TLS certificate and key files and use them to authenticate with the remote Docker daemon. + +## Create a Docker Network + +Create a Docker network for inter-container communication. The benefits of using a Docker network are: + +* container isolation - only the containers on the network can access one another +* name resolution - Docker daemon runs an embedded DNS server to provide automatic service discovery for containers connected to user-defined networks. This allows us to access containers on the same network by name. + +Here we create a Docker network named `jupyterhub-network`. Later, we will configure the JupyterHub and single-user Jupyter Notebook containers to run attached to this network. + +``` +docker network create jupyterhub-network +``` + +## Setup GitHub Authentication + +This deployment uses GitHub OAuth to authenticate users. It requires that you create a [GitHub application](https://github.com/settings/applications/new). You will need to specify an OAuth callback URL in the following form: + +``` +https:///hub/oauth_callback +``` + +You must pass the secrets that GitHub provides for your application to JupyterHub at runtime. You can do this by setting the `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, and `OAUTH_CALLBACK_URL` environment variables when you run the JupyterHub container, or you can add them to the `.env` file in the root directory of this repository. For example, + +``` +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +OAUTH_CALLBACK_URL=https:///hub/oauth_callback +``` + +## Build the JupyterHub Docker image + +Configure JupyterHub and build it into a Docker image. + +1. Copy your TLS certificate and key to a directory named `secrets` within this repository directory. These will be added to the Docker image at build time. + + ``` + mkdir -p secrets + cp jupyterhub.cer jupyterhub.key secrets/ + ``` + +1. Create a `userlist` file with a list of authorized users. At a minimum, this file should contain a single admin user. The username should be a GitHub username. For example: + + ``` + jtyberg admin + ``` + + The admin user will have the ability to add more users in the JupyterHub admin console. + +1. Build the JupyterHub Docker image. For convenience, this repo provides a `hub.sh` script that wraps [docker-compose](https://docs.docker.com/compose/reference/), so you can run it with the docker-compose [command line arguments](https://docs.docker.com/compose/reference/overview/). To build the JupyterHub image on the active Docker machine host, run: + + ``` + ./hub.sh build + ``` + +## Create a JupyterHub Data Volume + +Create a Docker volume to persist JupyterHub data. This volume will reside on the host machine. Using a volume allows user lists, cookies, etc., to persist across JupyterHub container restarts. + +``` +docker volume create --name jupyterhub-data +``` + +## Pull the Jupyter Notebook Image + +Pull the Jupyter Notebook Docker image that you would like JupyterHub to spawn for each user. + +Note: Even though Docker will pull the image to the host the first time a user container is spawned, JupyterHub may timeout if the image is large, so it's better to do it beforehand. + +This deployment uses the [jupyter/scipy-notebook](https://hub.docker.com/r/jupyter/scipy-notebook/) Docker image, which is built from the `scipy-notebook` [Docker stacks](https://github.com/jupyter/docker-stacks). + +Note that the Docker stacks `*-notebook` images tagged `2d878db5cbff` include the `start-singleuser.sh` script required to start a single-user instance of the Notebook server that is compatible with JupyterHub. + +``` +docker pull jupyter/scipy-notebook:2d878db5cbff +``` + +## Run the JupyterHub container + +Run the JupyterHub container on the host. + +To run the JupyterHub container in detached mode: + +``` +./hub.sh up -d +``` + +Once the container is running, you should be able to navigate to the JupyterHub console at + +``` +https://myhost.mydomain +``` + +To bring down the JupyterHub container: + +``` +./hub.sh down +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..c721a03c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# JupyterHub docker-compose configuration file +version: "2" + +services: + hub: + build: + context: . + dockerfile: Dockerfile.jupyterhub + image: jupyterhub + container_name: jupyterhub + volumes: + # Bind Docker binary from host machine so we can invoke Docker commands + # from inside container + - "/usr/local/bin/docker:/usr/local/bin/docker:ro" + # Bind Docker TLS certs from host machine so we can authenticate with the + # daemon on the host (DOCKER_HOST should be set to host's IP) + - "/etc/docker:/etc/docker:ro" + # Bind Docker volume on host for JupyterHub database and cookie secrets + - "data:${DATA_VOLUME_CONTAINER}" + ports: + - "443:443" + environment: + # Pass DOCKER_HOST to container to allow it to connect to daemon on host + DOCKER_HOST: ${DOCKER_HOST} + # Locations of TLS certificate and key needed to auth with daemon on host + DOCKER_TLS_CERT: "/etc/docker/server.pem" + DOCKER_TLS_KEY: "/etc/docker/server-key.pem" + # All containers will join this network + DOCKER_NETWORK_NAME: ${DOCKER_NETWORK_NAME} + # JupyterHub will spawn this image for users + DOCKER_CONTAINER_IMAGE: ${DOCKER_CONTAINER_IMAGE} + # Using this run command (optional) + DOCKER_SPAWN_CMD: ${DOCKER_SPAWN_CMD} + # Required to authenticate users using GitHub OAuth + GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + OAUTH_CALLBACK_URL: ${OAUTH_CALLBACK_URL} + command: > + jupyterhub -f /srv/jupyterhub/jupyterhub_config.py + +volumes: + data: + external: + name: ${DATA_VOLUME_HOST} + +networks: + default: + external: + name: ${DOCKER_NETWORK_NAME} diff --git a/examples/custom-notebook-server/Dockerfile b/examples/custom-notebook-server/Dockerfile new file mode 100644 index 00000000..6b120056 --- /dev/null +++ b/examples/custom-notebook-server/Dockerfile @@ -0,0 +1,21 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# Pin to version of notebook image that includes start-singleuser.sh script +FROM jupyter/scipy-notebook:2d878db5cbff + +# Install packages in default Python 3 environment +RUN pip install \ + beautifulsoup4==4.4.* + +# Install packages in Python 2 environment +RUN $CONDA_DIR/envs/python2/bin/pip install \ + beautifulsoup4==4.4.* + +# Use custom startup script +USER root +COPY docker-entrypoint.sh /srv/docker-entrypoint.sh +ENTRYPOINT ["tini", "--", "/srv/docker-entrypoint.sh"] +CMD ["start-singleuser.sh"] + +USER jovyan diff --git a/examples/custom-notebook-server/README.md b/examples/custom-notebook-server/README.md new file mode 100644 index 00000000..84a8fe33 --- /dev/null +++ b/examples/custom-notebook-server/README.md @@ -0,0 +1,28 @@ +# Custom Jupyter Notebook Server Image + +This is an example of using a custom Jupyter Notebook server Docker image with JupyterHub. The `Dockerfile` builds from the `jupyter/scipy-notebook` image, but customizes the image in the following ways: + +* installs an additional Python package to make it available to notebooks +* uses a custom entrypoint script that copies sample notebooks to the user's notebook directory before executing the run command that was provided to the container + +## Build the Image + +Build and tag the image using the `Dockerfile` in this directory. + +``` +docker build -t my-custom-notebook . +``` + +## Run JupyterHub Container + +To have JupyterHub spawn the `my-custom-notebook` image for single-user Notebook servers, set the `DOCKER_CONTAINER_IMAGE` environment variable to the image name when you run the JupyterHub container. For example, run the following **from the root directory** of this repository: + +``` +export DOCKER_CONTAINER_IMAGE=my-custom-notebook + +# bring down the JupyterHub container, if running +./hub.sh down + +# bring it back up +./hub.sh up -d +``` diff --git a/examples/custom-notebook-server/docker-entrypoint.sh b/examples/custom-notebook-server/docker-entrypoint.sh new file mode 100755 index 00000000..8cbbd466 --- /dev/null +++ b/examples/custom-notebook-server/docker-entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +set -e + +# If the run command is the default, do some initialization first +if [ "$(which "$1")" = "/usr/local/bin/start-singleuser.sh" ]; then + # Clone sample notebooks to user's notebook directory. Assume $NB_USER's work + # directory if notebook directory not explicitly set. `git clone` will fail + # if target directory already exists and is not empty, which likely means + # that we've already done it, so just ignore. + : ${NOTEBOOK_DIR:=/home/$NB_USER/work} + git clone https://gist.github.com/parente/facb555dfbae28e817e0 \ + --depth 1 \ + "$NOTEBOOK_DIR/notebook_count" || true +fi + +# Run the command provided +exec "$@" diff --git a/examples/letsencrypt/README.md b/examples/letsencrypt/README.md new file mode 100644 index 00000000..be71a7e3 --- /dev/null +++ b/examples/letsencrypt/README.md @@ -0,0 +1,47 @@ +# Let's Encrypt + +This example includes a Docker Compose configuration file that you can use to deploy [JupyterHub](https://github.com/jupyter/jupyterhub) with TLS certificate and key files generated by [Let's Encrypt](https://letsencrypt.org). + +The `docker-compose.yml` configuration file in this example extends the JupyterHub service defined in the `docker-compose.yml` file in the root directory of this repository. + +When you run the JupyterHub Docker container using the configuration file in this directory, Docker mounts an additional volume containing the Let's Encrypt TLS certificate and key files, and overrides the `SSL_CERT` and `SSL_KEY` environment variables to point to these files. + +## Create a secrets volume + +This example stores the Let's Encrypt TLS certificate and key files in a Docker volume, and mounts the volume to the JupyterHub container at runtime. + +Create a volume to store the certificate and key files. + +``` +# Activate Docker machine where JupyterHub will run +eval "$(docker-machine env jupyterhub)" + +docker volume create --name jupyterhub-secrets +``` + +## Generate Let's Encrypt certificate and key + +Run the `letsencrypt.sh` script to create a TLS full-chain certificate and key. + +The script downloads and runs the `letsencrypt` Docker image to create a full-chain certificate and private key, and stores the files in a Docker volume. You must provide a valid, routable, fully-qualified domain name (you must own it), and you must activate the Docker machine host that the domain points to before you run this script. You must also provide a valid email address and the name of the volume you created above. + +_Notes:_ The script hard codes several `letsencrypt` options, one of which automatically agrees to the Let's Encrypt Terms of Service. + +``` +# Activate Docker machine where JupyterHub will run +eval "$(docker-machine env jupyterhub)" + +./letsencrypt.sh \ + --domain myhost.mydomain \ + --email me@mydomain \ + --volume jupyterhub-secrets +``` + +## Run JupyterHub container + +To run the JupyterHub container using the configuration in this directory, run the `hub.sh` script **from the root directory** of this repository and specify the `docker-compose.yml` file in this directory. Set the `SECRETS_VOLUME` environment variable to the name of the Docker volume containing the TLS certificate and key files. + +``` +SECRETS_VOLUME=jupyterhub-secrets \ + ./hub.sh -f examples/letsencrypt/docker-compose.yml up -d +``` diff --git a/examples/letsencrypt/docker-compose.yml b/examples/letsencrypt/docker-compose.yml new file mode 100644 index 00000000..25ae7a43 --- /dev/null +++ b/examples/letsencrypt/docker-compose.yml @@ -0,0 +1,38 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# JupyterHub docker-compose configuration that uses Let's Encrypt TLS +# certificate and key. + +# Extends the JupyterHub configuration in the root directory of this repository. +# Mounts an additional secrets volume that stores the Let's Encrypt TLS +# certificate and key files, and overrides the `SSL_CERT` and `SSL_KEY` +# environment variables to point to these files. + +version: "2" + +services: + hub: + extends: # hub service in repository root directory + file: ../../docker-compose.yml + service: hub + volumes: + - "secrets:/etc/letsencrypt" + environment: + SSL_KEY: "/etc/letsencrypt/privkey.pem" + SSL_CERT: "/etc/letsencrypt/cert.pem" + +# Explicitly declare volume and network dependencies +# (they cannot be extended) +volumes: + data: + external: + name: ${DATA_VOLUME_HOST} + secrets: + external: + name: ${SECRETS_VOLUME} + +networks: + default: + external: + name: ${DOCKER_NETWORK_NAME} diff --git a/examples/letsencrypt/letsencrypt.sh b/examples/letsencrypt/letsencrypt.sh new file mode 100755 index 00000000..fd593333 --- /dev/null +++ b/examples/letsencrypt/letsencrypt.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# Wrapper script that runs https://letsencrypt.org Docker container to generate +# a certificate for a single domain and store it in a Docker volume. + +set -e + +USAGE=" +Usage: `basename $0` --domain FQDN --email EMAIL --volume SECRETS_VOLUME + [--staging] +" + +while [[ $# > 0 ]] +do +key="$1" +case $key in + --domain) + FQDN="$2" + shift # past argument + ;; + --email) + EMAIL="$2" + shift # past argument + ;; + --volume) + SECRETS_VOLUME="$2" + shift # past argument + ;; + --staging) + CERT_SERVER=--staging + ;; + *) # unknown option + ;; +esac +shift # past argument or value +done + +if [ -z "${FQDN:+x}" ]; then + echo "ERROR: Must provide --domain option or set FQDN environment varable" + echo "$USAGE" && exit 1 +fi + +if [ -z "${EMAIL:+x}" ]; then + echo "ERROR: Must provide --email option set EMAIL environment varable" + echo "$USAGE" && exit 1 +fi + +if [ -z "${SECRETS_VOLUME:+x}" ]; then + echo "ERROR: Must provide --volume option or set SECRETS_VOLUME environment varable" + echo "$USAGE" && exit 1 +fi + +# letsencrypt certificate server type (default is production). +# Set `CERT_SERVER=--staging` for staging. +: ${CERT_SERVER=''} + +# Generate the cert and save it to the Docker volume +docker run --rm -it \ + -p 80:80 \ + -v $SECRETS_VOLUME:/etc/letsencrypt \ + quay.io/letsencrypt/letsencrypt:latest \ + certonly \ + --non-interactive \ + --keep-until-expiring \ + --standalone \ + --standalone-supported-challenges http-01 \ + --agree-tos \ + --force-renewal \ + --domain "$FQDN" \ + --email "$EMAIL" \ + $CERT_SERVER + +# Set permissions so nobody can read the cert and key. +# Also symlink the certs into the root of the /etc/letsencrypt +# directory so that the FQDN doesn't have to be known later. +docker run --rm -it \ + -v $SECRETS_VOLUME:/etc/letsencrypt \ + --entrypoint=/bin/bash \ + quay.io/letsencrypt/letsencrypt:latest \ + -c "find /etc/letsencrypt/* -maxdepth 1 -type l -delete && \ + ln -s /etc/letsencrypt/live/$FQDN/* /etc/letsencrypt/ && \ + find /etc/letsencrypt -type d -exec chmod 755 {} +" diff --git a/hub.sh b/hub.sh new file mode 100755 index 00000000..7a983ca0 --- /dev/null +++ b/hub.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# Wrapper script around docker-compose + +set -e + +for i in "$@" ; do + if [[ "$i" == "up" ]]; then + # Check for required environment variables on startup + if [ -z ${GITHUB_CLIENT_ID:+x} ]; then + echo "ERROR: Must set GITHUB_CLIENT_ID environment variable"; exit 1; + fi + if [ -z ${GITHUB_CLIENT_SECRET:+x} ]; then + echo "ERROR: Must set GITHUB_CLIENT_SECRET environment variable"; exit 1; + fi + if [ -z ${OAUTH_CALLBACK_URL:+x} ]; then + echo "ERROR: Must set OAUTH_CALLBACK_URL environment variable"; exit 1; + fi + + # Set DOCKER_HOST to daemon of target machine + DOCKER_MACHINE_URL=$(docker-machine url $(docker-machine active)) + # Strip the protocol from the url + DOCKER_HOST="${DOCKER_MACHINE_URL#*//*}" + fi +done + +exec docker-compose "$@" diff --git a/internal/jupyterhub-docker.png b/internal/jupyterhub-docker.png new file mode 100644 index 0000000000000000000000000000000000000000..182b67ce564ffc6f1b3d436a7a2685a20565f091 GIT binary patch literal 91693 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 literal 0 HcmV?d00001 diff --git a/jupyterhub_config.py b/jupyterhub_config.py new file mode 100644 index 00000000..43fd7334 --- /dev/null +++ b/jupyterhub_config.py @@ -0,0 +1,81 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# Configuration file for JupyterHub +import os + +c = get_config() + +# We rely on environment variables to configure JupyterHub so that we +# avoid having to rebuild the JupyterHub container every time we change a +# configuration parameter. + +# Spawn single-user servers as Docker containers +c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner' +# Spawn containers from this image +c.DockerSpawner.container_image = os.environ['DOCKER_CONTAINER_IMAGE'] +# JupyterHub requires a single-user instance of the Notebook server, so we +# default to using the `start-singleuser.sh` script included in the +# jupyter/docker-stacks *-notebook images as the Docker run command when +# spawning containers. Optionally, you can override the Docker run command +# using the DOCKER_SPAWN_CMD environment variable. +spawn_cmd = os.environ.get('DOCKER_SPAWN_CMD', "start-singleuser.sh") +c.DockerSpawner.extra_create_kwargs.update({ 'command': spawn_cmd }) +# Connect containers to this Docker network +network_name = os.environ['DOCKER_NETWORK_NAME'] +c.DockerSpawner.use_internal_ip = True +c.DockerSpawner.network_name = network_name +# Pass the network name as argument to spawned containers +c.DockerSpawner.extra_host_config = { 'network_mode': network_name } +c.DockerSpawner.extra_start_kwargs = { 'network_mode': network_name } +# Explicitly set notebook directory because we'll be mounting a host volume to +# it. Most jupyter/docker-stacks *-notebook images run the Notebook server as +# user `jovyan`, and set the notebook directory to `/home/jovyan/work`. +# We follow the same convention. +c.DockerSpawner.notebook_dir = '/home/jovyan/work' +# Mount the real user's Docker volume on the host to the notebook user's +# notebook directory in the container +c.DockerSpawner.volumes = { '{username}': '/home/jovyan/work' } +c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'local' }) +# Remove containers once they are stopped +c.DockerSpawner.remove_containers = True +# Specify paths to TLS certificate and key used to authenticate to Docker +# daemon at DOCKER_HOST +c.DockerSpawner.tls_cert = os.environ['DOCKER_TLS_CERT'] +c.DockerSpawner.tls_key = os.environ['DOCKER_TLS_KEY'] +# For debugging arguments passed to spawned containers +c.DockerSpawner.debug = True + +# User containers will access hub by container name on the Docker network +c.JupyterHub.hub_ip = 'jupyterhub' +c.JupyterHub.hub_port = 8080 + +# TLS config +c.JupyterHub.port = 443 +c.JupyterHub.ssl_key = os.environ['SSL_KEY'] +c.JupyterHub.ssl_cert = os.environ['SSL_CERT'] + +# Authenticate users with GitHub OAuth +c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator' +c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL'] + +# Persist hub data on volume mounted inside container +data_dir = os.environ.get('DATA_VOLUME_CONTAINER', '/data') +c.JupyterHub.db_url = os.path.join('sqlite:///', data_dir, 'jupyterhub.sqlite') +c.JupyterHub.cookie_secret_file = os.path.join(data_dir, + 'jupyterhub_cookie_secret') + +# Whitlelist users and admins +c.Authenticator.whitelist = whitelist = set() +c.Authenticator.admin_users = admin = set() +c.JupyterHub.admin_access = True +pwd = os.path.dirname(__file__) +with open(os.path.join(pwd, 'userlist')) as f: + for line in f: + if not line: + continue + parts = line.split() + name = parts[0] + whitelist.add(name) + if len(parts) > 1 and parts[1] == 'admin': + admin.add(name) From 18d68ce5873d13029835ac1a0789e362215c6c3c Mon Sep 17 00:00:00 2001 From: Justin Tyberg Date: Thu, 14 Apr 2016 15:54:19 -0400 Subject: [PATCH 2/4] Add standard Jupyter LICENSE. (c) Copyright IBM Corp. 2016 --- LICENSE | 27 ------------------------ LICENSE.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 27 deletions(-) delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0c053e4e..00000000 --- a/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2016, JupyterHub -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of jupyterhub-deploy-docker nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..bd6397d4 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,60 @@ +# Licensing terms + +This project is licensed under the terms of the Modified BSD License +(also known as New or Revised or 3-Clause BSD), as follows: + +- Copyright (c) 2001-2015, IPython Development Team +- Copyright (c) 2015-, Jupyter Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the Jupyter Development Team nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## About the Jupyter Development Team + +The Jupyter Development Team is the set of all contributors to the Jupyter project. +This includes all of the Jupyter subprojects. + +The core team that coordinates development on GitHub can be found here: +https://github.com/jupyter/. + +## Our Copyright Policy + +Jupyter uses a shared copyright model. Each contributor maintains copyright +over their contributions to Jupyter. But, it is important to note that these +contributions are typically only changes to the repositories. Thus, the Jupyter +source code, in its entirety is not the copyright of any single person or +institution. Instead, it is the collective copyright of the entire Jupyter +Development Team. If individual contributors want to maintain a record of what +changes/contributions they have specific copyright on, they should indicate +their copyright in the commit message of the change, when they commit the +change to one of the Jupyter repositories. + +With this in mind, the following banner should be used in any source code file +to indicate the copyright and license terms: + + # Copyright (c) Jupyter Development Team. + # Distributed under the terms of the Modified BSD License. From 917c5fbf16283ba8742ea327a46a40a8710b4ace Mon Sep 17 00:00:00 2001 From: Justin Tyberg Date: Thu, 14 Apr 2016 22:27:51 -0400 Subject: [PATCH 3/4] Beef up README. (c) Copyright IBM Corp. 2016 --- README.md | 114 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bcf5b760..ef447f3a 100644 --- a/README.md +++ b/README.md @@ -26,31 +26,78 @@ This deployment is **NOT** intended for a production environment. ## Prerequisites * This deployment uses Docker for all the things. It assumes that you wiil be using [Docker Machine](https://docs.docker.com/machine/overview/) and [Docker Compose](https://docs.docker.com/compose/overview/) on a local workstation or laptop to create, build, and run Docker images on a remote host. It requires [Docker Toolbox](https://www.docker.com/products/docker-toolbox) 1.11.0 or higher. See the [installation instructions](https://docs.docker.com/engine/installation/) for your environment. -* This example configures JupyterHub for HTTPS connections (the default). As such, you must provide TLS certificate chain and key files to the JupyterHub server. If you do not have your own certificate chain and key, you can either create self-signed versions, or obtain real ones from [Let's Encrypt](https://letsencrypt.org) (see the [letsencrypt example](examples/letsencrypt/README.md) for instructions). +* This example configures JupyterHub for HTTPS connections (the default). As such, you must provide TLS certificate chain and key files to the JupyterHub server. If you do not have your own certificate chain and key, you can either [create self-signed versions](https://jupyter-notebook.readthedocs.org/en/latest/public_server.html#using-ssl-for-encrypted-communication), or obtain real ones from [Let's Encrypt](https://letsencrypt.org) (see the [letsencrypt example](examples/letsencrypt/README.md) for instructions). ## Create a Docker Machine -Use [Docker Machine](https://docs.docker.com/machine/) to provision a new host, or to connect to an existing one. +Use [Docker Machine](https://docs.docker.com/machine/) to provision a new host, or to connect to an existing host. In either case, the result will be a "machine" configured on your local workstation or laptop that represents the remote host. After you activate the machine on your local workstation, when you run `docker` commands locally, Docker Machine will execute them on the remote host for you. -This example assumes that a remote host already exists at IP address `10.0.0.10`, and that you can perform password-less login to this host from your local workstation or laptop using a private SSH key. +### Provision a new host -To create a Docker machine that will install and configure the Docker daemon on an existing host (note that Docker Machine will upgrade the Docker daemon on the host if it is already installed): +Docker Machine can provision new hosts on various platforms using one of its [supported drivers](https://docs.docker.com/machine/drivers/). When provisioning a host, Docker Machine will automatically install the latest version of [Docker Engine](https://www.docker.com/products/docker-engine) on the host. It will also generate local TLS certificate and key files to connect to the host and authenticate with the `docker` daemon on the host. + +In the following example, we provision a new virtual server on [IBM SoftLayer](https://www.softlayer.com/promo/freeCloud/freecloud). (You can provision similarly on RackSpace, AWS, and other hosting providers). We set `DRIVER_OPTS` to the SoftLayer-specific options. + +``` +# Set Docker Machine SoftLayer driver options +export DRIVER_OPTS="--driver softlayer \ + --softlayer-api-key \ + --softlayer-user \ + --softlayer-domain mydomain \ + --softlayer-cpu 16 \ + --softlayer-memory 65536 \ + --softlayer-disk-size 100 \ + --softlayer-region wdc01" + +# Create a machine named jupyterhub +docker-machine create $DRIVER_OPTS jupyterhub + +``` + +### Connect to an existing host + +This example configures a "machine" on your local workstation that connects to an existing remote host at IP address `10.0.0.10`. To do this, you must use Docker Machine's `generic` driver, and your local workstation must have a private SSH key that allows you to perform password-less login to the host. + +Substitute your own IP address and path to SSH key in the `DRIVER_OPTS` below. Note that when you run the `docker-machine create` command, Docker Machine will install and configure the latest Docker Engine on the remote host (or upgrade the Docker Engine on the host if it is already installed). ``` # Use the generic driver to create a Docker machine that # controls the Docker daemon on an existing host export DRIVER_OPTS="--driver generic \ --generic-ip-address 10.0.0.10 \ - --generic-ssh-key /path/to/private/ssh/key" + --generic-ssh-key /Users/jtyberg/.ssh/myhost_rsa.pem" # Create a machine named jupyterhub docker-machine create $DRIVER_OPTS jupyterhub +``` + +## Activate Docker Machine + +You must set specific `DOCKER_*` environment variables to tell `docker` that it should run commands against a particular machine. The remainder of this document assumes that the machine called `jupyterhub` is active, and therefore, all `docker` commands will run on the `jupyterhub` remote host. -# Activate the machine +To set the `jupyterhub` machine as active: + +``` eval "$(docker-machine env jupyterhub)" ``` -Docker Machine can also provision new hosts using one of it's [supported drivers](https://docs.docker.com/machine/drivers/). When provisioning a host, Docker Machine will automatically generate TLS certificate and key files and use them to authenticate with the remote Docker daemon. +All this does is set the right environment variables: + +``` +env|grep DOCKER + +DOCKER_HOST=tcp://10.0.0.10:2376 +DOCKER_MACHINE_NAME=jupyterhub +DOCKER_TLS_VERIFY=1 +DOCKER_CERT_PATH=/Users/jtyberg/.docker/machine/machines/jupyterhub +``` + + +To see which machine is active: + +``` +docker-machine active +``` ## Create a Docker Network @@ -81,6 +128,8 @@ GITHUB_CLIENT_SECRET= OAUTH_CALLBACK_URL=https:///hub/oauth_callback ``` +**Note:** The `.env` file is a special file that Docker Compose uses to lookup environment variables. If you choose to place the GitHub secrets in this file, you should ensure that this file remains private (e.g., do not commit the secrets to source control). + ## Build the JupyterHub Docker image Configure JupyterHub and build it into a Docker image. @@ -98,7 +147,7 @@ Configure JupyterHub and build it into a Docker image. jtyberg admin ``` - The admin user will have the ability to add more users in the JupyterHub admin console. + The admin user will have the ability to add more users in the JupyterHub admin console. 1. Build the JupyterHub Docker image. For convenience, this repo provides a `hub.sh` script that wraps [docker-compose](https://docs.docker.com/compose/reference/), so you can run it with the docker-compose [command line arguments](https://docs.docker.com/compose/reference/overview/). To build the JupyterHub image on the active Docker machine host, run: @@ -128,6 +177,8 @@ Note that the Docker stacks `*-notebook` images tagged `2d878db5cbff` include th docker pull jupyter/scipy-notebook:2d878db5cbff ``` +Note: If you choose to use a different container image, be sure to set the `DOCKER_CONTAINER_IMAGE` environment variable either in the shell you use to launch JupyterHub or in the `.env` file. + ## Run the JupyterHub container Run the JupyterHub container on the host. @@ -138,7 +189,7 @@ To run the JupyterHub container in detached mode: ./hub.sh up -d ``` -Once the container is running, you should be able to navigate to the JupyterHub console at +Once the container is running, you should be able to access the JupyterHub console at ``` https://myhost.mydomain @@ -149,3 +200,48 @@ To bring down the JupyterHub container: ``` ./hub.sh down ``` + +## FAQ + +### How can I view the logs for JupyterHub or users' Notebook servers? + +Use `docker logs `. For example, to view the logs of the `jupyterhub` container + +``` +docker logs jupyterhub +``` + +### How can I backup a user's notebook directory? + +There are multiple ways to [backup and restore](https://docs.docker.com/engine/userguide/containers/dockervolumes/#backup-restore-or-migrate-data-volumes) data in Docker containers. + +Suppose you have the following running containers: + +``` +docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Names}}" + +CONTAINER ID IMAGE NAMES +bc02dd6bb91b jupyter/minimal-notebook jupyter-jtyberg +7b48a0b33389 jupyterhub jupyterhub +``` + +In this deployment, the user's notebook directories (`/home/jovyan/work`) are backed by Docker volumes. + +``` +docker inspect -f '{{ .Mounts }}' jupyter-jtyberg + +[{jtyberg /var/lib/docker/volumes/jtyberg/_data /home/jovyan/work local rw true rprivate}] +``` + +We can backup the user's notebook directory by running a separate container that mounts the user's volume and creates a tarball of the directory. + +``` +docker run --rm \ + -u root \ + -v /tmp:/backups \ + -v jtyberg:/notebooks \ + jupyter/minimal-notebook \ + tar cvf /backups/jtyberg-backup.tar /notebooks +``` + +The above command creates a tarball in the `/tmp` directory on the host. \ No newline at end of file From d8adee19af166a88ab0d1d80469e0769f7fa2bfb Mon Sep 17 00:00:00 2001 From: Justin Tyberg Date: Thu, 14 Apr 2016 22:28:39 -0400 Subject: [PATCH 4/4] Do not strip protocol from DOCKER_HOST. In Docker 1.10, including protocol in DOCKER_HOST caused failure to connect to daemon on host machine. Must have fixed in 1.11. (c) Copyright IBM Corp. 2016 --- hub.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hub.sh b/hub.sh index 7a983ca0..0ffcba43 100755 --- a/hub.sh +++ b/hub.sh @@ -20,9 +20,7 @@ for i in "$@" ; do fi # Set DOCKER_HOST to daemon of target machine - DOCKER_MACHINE_URL=$(docker-machine url $(docker-machine active)) - # Strip the protocol from the url - DOCKER_HOST="${DOCKER_MACHINE_URL#*//*}" + DOCKER_HOST=$(docker-machine url $(docker-machine active)) fi done