Docker ecosystem with bash kernel not executing commands

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

Do you have problems with the default Python kernel?

Yes I do. I have tried to used the jupyterhub/singleuser docker image and I also get the same behavior.

I don’t know if it can be related. I have been making some more test and it seems that it is more stable when I’m using incognito mode. Although it fails less, it still fails quite a lot.

Edit: Another funny thing is that if I use the user image with JupyterLab, it seems that everything is working fine. So the problem seems to be the liaison between Hub and Labs :frowning:

Are you using this standalone image in the same way, i.e. adding it to your docker-compose and proxying it through Traefik?

Do you see the same problems if you access JupyterHub directly on port 8000, bypassing Traefik?

Are you using this standalone image in the same way, i.e. adding it to your docker-compose and proxying it through Traefik?

No. I’m using it directly accessing through 8888.

Do you see the same problems if you access JupyterHub directly on port 8000, bypassing Traefik?

At the beginning using the dummy authenticator, i did it and if I remember correctly I had the same issues.

I don’t know if I will have a chance to test it during the weekend, but I’ll try to do my best. If not on Monday morning.

Do you think it could be traefik the one causing all this issues?

Thanks a lot for giving me some hints to test other options.

Hi @manics

I have made the test directly accessing port 8000 without traefik and it seems it is working.
What could be the problem with traefik? I need to use it to hide the service and so on :frowning:
Any help is more than welcome.

See you