Hi all,
I’m setting a small JupyterHub ecosystem for an internal lab. I’m using a dockerize configuration and currently the system is configured to use LDAP authentication. Besides I have included Bash Kernel as the lab I’m setting up is devoted to learn bash. I have followed some tutorials as initial steps, but then have adapted the configuration to my needs.
The setup seems to be working fine, but not always. The problem is that when I test it and log with a user, I’m able to open a Bash notebook and a Terminal. I type a command on both of them (ls
) and execute it and it provides an answer. However, I log out and log in with another user and I’m not able to do the previous. The execution environment (I guess the kernel) doesn’t respone, that is, neither the commands in the notebook or in the terminal response. Actually the terminal doesn’t even start up. If I log back with the same user, things are not working neither.
As I have explained I don’t know why there is an instability in the configuration. Can it be the Bash Kernl or is any other thing?
Below I’m disclosing all my configuration.
I’m quite lost, have tried many things. Any help is more than welcome.
- JupyterHub Docker file
.
ARG BASE_IMAGE=jupyterhub/jupyterhub
FROM ${BASE_IMAGE}
RUN pip install --no-cache --upgrade jupyter
RUN pip install --no-cache dockerspawner
RUN pip install --no-cache jupyterhub-ldapauthenticator
RUN pip install --no-cache jupyterhub-idle-culler
COPY jupyterhub_config.py .
EXPOSE 8000
- singleuser Docker file
.
FROM jupyter/minimal-notebook
# A Jupyter kernel for bash
RUN pip install bash_kernel
RUN python -m bash_kernel.install
# TODO: Search why jupyterhub has to be installed
# https://jupyterhub-dockerspawner.readthedocs.io/en/latest/docker-image.html
# ARG JUPYTERHUB_VERSION=1.3.0
ARG JUPYTERHUB_VERSION=3.0
RUN pip3 install --no-cache \
jupyterhub==$JUPYTERHUB_VERSION
- Docker compose
.
version: '3'
services:
# Configuration for Hub+Proxy
jupyterhub:
build:
context: jupyterhub # Build the container from this folder.
args:
BASE_IMAGE: jupyterhub/jupyterhub:${JUPYTERHUB_VERSION}
image: jlanza/jupyterhub
container_name: jupyterhub-jlanza # The service will use this container name.
volumes: # Give access to Docker socket.
- /var/run/docker.sock:/var/run/docker.sock
# - jupyterhub_data:/srv/jupyterhub
ports:
- 8000:8000
environment: # Env variables passed to the Hub process.
DOCKER_SINGLEUSER_IMAGE: jlanza/singleuser
# DOCKER_NETWORK_NAME: jupyter_network
# HUB_IP: jupyterhub-jlanza
DOCKER_NETWORK_NAME: project_no-internet
HUB_IP: jupyterhub-jlanza_no-internet
LDAP_lookup_dn_search_user: ${LDAP_lookup_dn_search_user}
LDAP_lookup_dn_search_password: ${LDAP_lookup_dn_search_password}
LDAP_server_address: ${LDAP_server_address}
LDAP_server_port: ${LDAP_server_port}
networks:
default:
no-internet:
aliases:
- jupyterhub-jlanza_no-internet
labels: # Traefik configuration.
# Tell Traefik to consider (or not) the container (exposedByDefault overriden)
- "traefik.enable=true"
# localhost???
- "traefik.http.routers.jupypterhub.rule=Host(`192.168.56.110`) || Host(`127.0.0.1`) || Host(`localhost`)"
- "traefik.http.routers.jupypterhub.tls=true"
# Configuration for reverse proxy
reverse-proxy:
image: traefik
container_name: traefik-jlanza
ports:
- "80:80"
- "443:443"
- "8080:8080"
networks:
default:
no-internet:
aliases:
- reverse-proxy-jlanza_no-internet
- traefik-jlanza_no-internet
volumes:
- ./reverse-proxy/traefik.toml:/etc/traefik/traefik.toml
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./reverse-proxy/ssl:/etc/ssl
# Configuration for single-user servers
singleuser:
build:
context: singleuser # Build the container from this folder.
args:
JUPYTERHUB_VERSION: ${JUPYTERHUB_VERSION}
image: jlanza/singleuser
container_name: singleuser-jlanza
# When Docker Compose starts the service, it terminates immediately. Indeed this image is meant to be loaded by the Hub, not by Compose.
command: "echo"
environment:
JUPYTER_ENABLE_LAB: 'yes'
networks: # I think it's not mandatory to put it there.
- no-internet
# It is recommended to put all of the files used by JupyterHub into standard UNIX filesystem location
# /srv/jupyterhub for all security and runtime files
# /etc/jupyterhub for all configuration files
# /var/log for log files
volumes:
jupyterhub_data:
name: jupyterhub_data
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: '/root/project/shared'
networks:
default:
name: jupyter_network
no-internet:
internal: true
- Jupyterhub_config.py
.
# Configuration file for Jupyter Hub
import os
import sys
c = get_config()
# Spawn with Docker
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
# Keep the spawn limit low
# c.JupyterHub.concurrent_spawn_limit = 20
# Spawn containers from this image
# c.DockerSpawner.image = 'jlanza/singleuser'
c.DockerSpawner.image = os.environ['DOCKER_SINGLEUSER_IMAGE']
c.DockerSpawner.prefix = 'jupyterhub-user'
# c.DockerSpawner.container_name="jupyterhub-user-{username}"
c.DockerSpawner.extra_create_kwargs = {'user': 'root'}
c.DockerSpawner.environment = {
'GRANT_SUDO': '1',
'UID': '0', # workaround https://github.com/jupyter/docker-stacks/pull/420
}
# c.JupyterHub.base_url='/jupyterhub'
# 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.
c.DockerSpawner.extra_create_kwargs.update({ 'command': "start-singleuser.sh --SingleUserNotebookApp.default_url=/lab" })
# Redirect to JupyterLab, instead of the plain Jupyter notebook
# It seems it is required in case the start-singleuser command before doesn't include the default_url configuration
# c.Spawner.default_url = '/lab'
# Connect containers to this Docker network
# network_name = 'jupyter_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 }
# 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.
notebook_dir = os.environ.get('DOCKER_NOTEBOOK_DIR') or '/home/jovyan'
# notebook_dir = '/home/jovyan/work'
c.DockerSpawner.notebook_dir = notebook_dir
# Mount the real user's Docker volume on the host to the notebook user's
# notebook directory in the container
# Stored in /var/lib/docker/volumes/jupyterhub-shared or jupyterhub-user-{username}
c.DockerSpawner.volumes = {
'jupyterhub-user-{username}': notebook_dir,
'jupyterhub-shared': {"bind": '/home/jovyan/shared', "mode": "rw"}
}
# volume_driver is no longer a keyword argument to create_container()
# c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'local' })
# Remove containers once they are stopped
c.DockerSpawner.remove_containers = False
# For debugging arguments passed to spawned containers
c.DockerSpawner.debug = True
# The docker instances need access to the Hub, so the default loopback port doesn't work:
# from jupyter_client.localinterfaces import public_ips
# c.JupyterHub.hub_ip = public_ips()[0]
# c.JupyterHub.hub_ip = 'jupyterhub'
c.JupyterHub.hub_ip = os.environ['HUB_IP']
# IP Configurations
c.JupyterHub.ip = '0.0.0.0'
c.JupyterHub.port = 8000
# Other stuff
c.Spawner.cpu_limit = 1
c.Spawner.mem_limit = '2G'
c.JupyterHub.services = [
{
"name": "jupyterhub-idle-culler-service",
"command": [
sys.executable,
"-m", "jupyterhub_idle_culler",
"--timeout=3600",
],
# "admin": True,
},
{
"name": "admin-service",
"api_token": "my-token",
}
]
c.JupyterHub.load_roles = [
{
"name": "jupyterhub-idle-culler-role",
"scopes": [
"list:users",
"read:users:activity",
"read:servers",
"delete:servers",
# "admin:users", # if using --cull-users
],
# assignment of role's permissions to:
"services": ["jupyterhub-idle-culler-service"],
},
{
"name": "admin-service-role",
"scopes": [
# specify the permissions the token should have
"admin:users",
"admin-ui"
# "admin:servers",
# "proxy"
],
"services": [
# assign the service the above permissions
"admin-service",
],
}
]
c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator'
# c.LDAPAuthenticator.log_level = 'DEBUG'
c.LDAPAuthenticator.server_address = os.environ['LDAP_server_address']
c.LDAPAuthenticator.use_ssl = True
c.LDAPAuthenticator.server_port = int(os.environ['LDAP_server_port'])
# Restrict access
c.Authenticator.blocked_users = {'root'}
c.Authenticator.admin_users = {'admin'}
c.Authenticator.allowed_users = {'jlanza'}
# LDAP not bind
c.LDAPAuthenticator.lookup_dn = False
c.LDAPAuthenticator.bind_dn_template = [
'CN={username},CN=users,DC=company,DC=es',
]
c.LDAPAuthenticator.valid_username_regex = '^[a-zA-Z][.a-zA-Z0-9_-]*$'
c.LDAPAuthenticator.escape_userdn = False