Customizing JupyterHub on Kubernetes

:heart: @bitnik for your example!

Another perhaps more complicated option is to mount configmaps with the template files, the benefit is that you don’t need to have an init container do stuff but instead mounts things directly assuming you have these files available on helm upgrade etc and some additional configuration work.

To use this approach, do something like below. Note that this is a WIKI post allowing you to edit it if you find something to correct or add, please feel free to do so!

  1. use a custom helm chart that requires the jupyterhub helm chart

    Here is a requirements.yaml file as an example.

    # requirements.yaml
    dependencies:
      # CHART_VERSION: https://jupyterhub.github.io/helm-chart/
      - name: jupyterhub
        version: 0.9-dcde99a
        repository: https://jupyterhub.github.io/helm-chart/
    
  2. add some template files in a local folder alongside helm chart configuration files

    spawn.html - In the helm chart that has a requirements.yaml declaring jupyterhub as a dependency, this file could for example be added in files/etc/jupyterhub/templates/. Note that it references an image in /hub/static/external/my-custom-image.svg, this needs also to be mounted for use and that is done if you place it within files/static/external/ assuming the use of the configmaps presented in this example.

    {% extends "page.html" %}
    {% if announcement_spawn %}
    {% set announcement = announcement_spawn %}
    {% endif %}
    
    {% block main %}
    
    <div class="container">
      {% block heading %}
      <div class="row text-center">
    
        <img src="/hub/static/external/my-custom-image.svg" height="80px" />
    
        <h1>Server Options</h1>
      </div>
      {% endblock %}
      <div class="row col-sm-offset-2 col-sm-8">
        {% if for_user and user.name != for_user.name -%}
        <p>Spawning server for {{ for_user.name }}</p>
        {% endif -%}
        {% if error_message -%}
        <p class="spawn-error-msg text-danger">
          Error: {{error_message}}
        </p>
        {% endif %}
        <form enctype="multipart/form-data" id="spawn_form" action="{{url}}" method="post" role="form">
          {{spawner_options_form | safe}}
          <br>
          <input type="submit" value="Start" class="btn btn-jupyter form-control">
        </form>
      </div>
    </div>
    
    {% endblock %}
    
  3. make a configmap helm template add these files to itself

    # configmap.yaml that I install into the jupyterhub namespace
    # through a custom Helm chart that has a requirements.yaml file
    # that in turn installs jupyterhub.
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: hub-templates
    data:
      {{- (.Files.Glob "files/etc/jupyterhub/templates/*").AsConfig | nindent 2 }}
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: hub-external
    binaryData:
      {{- $root := . }}
      {{- range $path, $bytes := .Files.Glob "files/static/external/*" }}
      {{ base $path }}: '{{ $root.Files.Get $path | b64enc }}'
      {{- end }}
    
  4. and then mount that configmap to the hub pod

    jupyterhub:
      hub:
        extraVolumes:
          - name: hub-templates
            configMap:
              name: hub-templates
          - name: hub-external
            configMap:
              name: hub-external
        extraVolumeMounts:
          - name: hub-templates
            mountPath: /etc/jupyterhub/templates
          - name: hub-external
            mountPath: /usr/local/share/jupyterhub/static/external
        extraConfig:
          templates: |
            c.JupyterHub.template_paths.insert(0, "/etc/jupyterhub/templates")
    
6 Likes