Skip to content
This repository has been archived by the owner on Jun 27, 2020. It is now read-only.

Jupyterhub Configuration

Philipp Wakonigg edited this page Aug 10, 2017 · 1 revision

This page should describe the Jupyterhub configuration file jupyterhub_config.py.

Base Config

First of all I'd like to start with the 'basic' Jupyterhub configuration. There one can find the network configuration (ip,port,hub_ip), the security configuration (ssl_key, ssl_cert, admin_access, cookie_secret_file, admin_users), the Authenticator Class and the Spawner Class.

import os
c.JupyterHub.ip = os.environ.get('JUPYTERHUB_IP')
c.JupyterHub.port = int(os.environ.get('JUPYTERHUB_PORT'))
c.JupyterHub.hub_ip = os.environ.get('JUPYTERHUB_HUB_IP')
c.JupyterHub.spawner_class = 'cassinyspawner.SwarmSpawner'
c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator'
c.JupyterHub.ssl_key = os.environ['SSL_KEY']
c.JupyterHub.ssl_cert = os.environ['SSL_CERT']
c.JupyterHub.log_level = os.environ.get('JUPYTERHUB_LOG_LEVEL')
c.JupyterHub.admin_access = True
data_dir = os.environ.get('JUPYTERHUB_DATA_VOLUME')
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')

c.Authenticator.admin_users = admin = set()
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]
        if len(parts) > 1 and parts[1] == 'admin':
            admin.add(name)

One can read the whole documentation about the Jupyterhub config here.

Authenticator Config

As defined in the c.JupyterHub.authenticator_classproperty above, this project uses the LDAP Authenticator. If you don't have a LDAP Server in your setup you can choose from various other here or even create a new one.

c.LDAPAuthenticator.server_address = os.environ.get('LDAPAUTHENTICATOR_SERVER_ADDRESS')
c.LDAPAuthenticator.server_port = int(os.environ.get('LDAPAUTHENTICATOR_SERVER_PORT'))
c.LDAPAuthenticator.lookup_dn = os.environ.get('LDAPAUTHENTICATOR_USE_SSL') == 'True'
c.LDAPAuthenticator.user_search_base = os.environ.get('LDAPAUTHENTICATOR_USER_SEARCH_BASE')
c.LDAPAuthenticator.user_attribute = os.environ.get('LDAPAUTHENTICATOR_USER_ATTRIBUTE')
c.LDAPAuthenticator.use_ssl = os.environ.get('LDAPAUTHENTICATOR_USE_SSL') == 'True'
c.LDAPAuthenticator.allowed_groups = allowedgroups = []
pwd = os.path.dirname(__file__)
with open(os.path.join(pwd, 'allowedLDAPGroups')) as f:
    for line in f:
        if not line:
            continue
        allowedgroups.append(line)

c.LDAPAuthenticator.bind_dn_template = bindDnTemplate = []
pwd = os.path.dirname(__file__)
with open(os.path.join(pwd, 'bindDnTemplate')) as f:
    for line in f:
        if not line:
            continue
        bindDnTemplate.append(line)

The juypterhub_config.py of my project uses the above shown LDAPAuthenticator configuration. If you are interested in which purpose each property has please visit the LDAP Authenticator Github page.

Spawner Config

This repo uses the SwarmSpawner to create Notebook Servers as Docker Services in the Docker Swarm network.

c.SwarmSpawner.start_timeout = 60 * 60
c.SwarmSpawner.jupyterhub_service_name = os.environ.get('SWARMSPAWNER_HUB_SERVICE_NAME')
c.SwarmSpawner.service_prefix = os.environ.get('SWARMSPAWNER_SERVICE_PREFIX')
c.SwarmSpawner.networks = [os.environ.get('SWARMSPAWNER_NETWORK')]
c.SwarmSpawner.notebook_dir = os.environ.get('SWARMSPAWNER_NOTEBOOK_DIR')
c.SwarmSpawner.teachers = [os.environ.get('SWARMSPAWNER_TEACHERS')]
c.SwarmSpawner.teacher_image = os.environ.get('SWARMSPAWNER_TNOTEBOOK_IMAGE')
c.SwarmSpawner.student_image = os.environ.get('SWARMSPAWNER_SNOTEBOOK_IMAGE')
mounts = [{'type' : 'volume',
'target' : os.environ.get('SWARMSPAWNER_NOTEBOOK_DIR'),
'source' : 'jupyterhub-user-{username}',
'no_copy' : True,
'driver_config' : {
  'name' : 'local',
  'options' : {
     'type' : 'nfs4',
	 'o' : 'addr='+os.environ.get('NFSSERVER_IP')+',rw',
	 'device' : ':'+os.environ.get('NFSSERVER_USERDATA_DEVICE')
   }
}},{
'type' : 'volume',
'target' : '/srv/nbgrader/exchange',
'source' : 'jupyter-exchange-volume',
'no_copy' : True,
'driver_config' : {
  'name' : 'local',
  'options' : {
     'type' : 'nfs4',
	 'o' : 'addr='+os.environ.get('NFSSERVER_IP')+',rw',
	 'device' : ':'+os.environ.get('NFSSERVER_ASSIGNMENTDATA_DEVICE')
   }
}}]

c.SwarmSpawner.container_spec = {
			'args' : ['start-singleuser.sh'],
            'Image' : os.environ.get('SWARMSPAWNER_NOTEBOOK_IMAGE'),
			'mounts' : mounts
          }

c.SwarmSpawner.resource_spec = {}

The specific spawner configuration shown above is used in this repo to create Juypter Notebook Servers as Docker Services.

One may notice the config properties c.SwarmSpawner.teachers, c.SwarmSpawner.teacher_image and c.SwarmSpawner.student_image are not available in the original repo. I forked the repo to add these properties, to checkout if the username, which is trying to spawn a new Server, is a teacher or a student. My DockerSwarmSpawner then uses different Docker Images for the Notebook Server. This feature is needed if one wants to use nbgrader in the setup. A detailed explanation is NOT YET available on the wiki page DockerSwarmSpawner - nbgrader. You also may look to the page Docker Volumes to understand the mount property of the config file.

Notes

You may ask your self, why the config uses that many ENV Variables. Well, after building the Jupyterhub Dockerimage, the jupyterhub_config.py file can not be modified that easy. One may think, why not use a Docker Bind Volume, where the config file just be mounted before the service starts. I personally think, that this would be a quick and dirty way to perform changes of the Jupyterhub Config, but with ENV Variables the same result can be achieved. Either way you have to restart the service to make your config changes working.

You also may noticed that c.LDAPAuthenticator.allowed_groups,c.LDAPAuthenticator.bind_dn_template and c.Authenticator.admin_users don't use ENV Variables. The reason for this is, that these properties need an array type to work correctly and after a quick research one will notice, that providing an array as a ENV Variable isn't that easy. ENV Variables are always interpreted as Strings. Furthermore, you can't define multiline ENV Variables, which makes the .env file confusing to read/modify.