Skip to content

Commit bb80104

Browse files
committed
jhub: extra configs and profiles
1 parent 4271329 commit bb80104

File tree

2 files changed

+122
-1
lines changed

2 files changed

+122
-1
lines changed
26.9 KB
Loading

modules/tutorials/pages/jupyterhub.adoc

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,131 @@ If the default file is not overwritten, but is mounted to a new file in the same
352352
353353
=== Endpoints
354354
355-
=== Driver Service
355+
The Helm chart for JupyterHub allows us to augment the standard configuration with one or more scripts.
356+
As mentioned in an earlier section, we want to define the endpoints dynamically - by making use of the ConfigMap written out by the Keycloak Deployment - and we can do this by adding a script under `extraConfig`:
357+
358+
[source,yaml]
359+
----
360+
extraConfig:
361+
...
362+
03-set-endpoints: |
363+
import os
364+
from oauthenticator.generic import GenericOAuthenticator
365+
keycloak_url = os.getenv("KEYCLOAK_NODEPORT_URL") # <2>
366+
...
367+
keycloak_node_ip = os.getenv("KEYCLOAK_NODE_IP")
368+
...
369+
c.GenericOAuthenticator.oauth_callback_url: f"http://{keycloak_node_ip}:31095/hub/oauth_callback" # <3>
370+
c.GenericOAuthenticator.authorize_url = f"https://{keycloak_url}/realms/demo/protocol/openid-connect/auth"
371+
c.GenericOAuthenticator.token_url = f"https://{keycloak_url}/realms/demo/protocol/openid-connect/token"
372+
c.GenericOAuthenticator.userdata_url = f"https://{keycloak_url}/realms/demo/protocol/openid-connect/userinfo"
373+
----
374+
375+
=== Driver Service (Spark)
376+
377+
NOTE: when using Spark, please the `Provisos` section below.
378+
379+
In the same way, we can use another script to define a driver service for each user.
380+
This is essential when using Spark from within a JupyterHUb notebook so that executor pods can be spawned from the user's kernel in a user-specific way.
381+
This script instructs JupyterHub to use `KubeSpawner` to create a service referenced by the UID of the parent Pod.
382+
383+
[source,yaml]
384+
----
385+
extraConfig:
386+
...
387+
02-create-spark-driver-service-hook: |
388+
# Thanks to https://github.com/jupyterhub/kubespawner/pull/644
389+
from jupyterhub.utils import exponential_backoff
390+
from kubespawner import KubeSpawner
391+
from kubespawner.objects import make_owner_reference
392+
from kubernetes_asyncio.client.models import V1ServicePort
393+
from functools import partial
394+
395+
async def after_pod_created_hook(spawner: KubeSpawner, pod: dict):
396+
owner_reference = make_owner_reference(
397+
pod["metadata"]["name"], pod["metadata"]["uid"]
398+
)
399+
service_manifest = spawner.get_service_manifest(owner_reference)
400+
401+
service_manifest.spec.type = "ClusterIP"
402+
service_manifest.spec.clusterIP = "None" # Headless Services is all we need
403+
service_manifest.spec.ports += [
404+
V1ServicePort(name='spark-ui', port=4040, target_port=4040),
405+
V1ServicePort(name='spark-driver', port=2222, target_port=2222),
406+
V1ServicePort(name='spark-block-manager', port=7777, target_port=7777)
407+
]
408+
409+
await exponential_backoff(
410+
partial(
411+
spawner._ensure_not_exists,
412+
"service",
413+
service_manifest.metadata.name,
414+
),
415+
f"Failed to delete service {service_manifest.metadata.name}",
416+
)
417+
await exponential_backoff(
418+
partial(spawner._make_create_resource_request, "service", service_manifest),
419+
f"Failed to create service {service_manifest.metadata.name}",
420+
)
421+
422+
c.KubeSpawner.after_pod_created_hook = after_pod_created_hook
423+
----
356424
357425
=== Profiles
358426
427+
The `singleuser.profileList` section of the Helm chart values allows us to define notebook profiles by setting the CPU, Memory and Image combinations that can be selected. For instance, the profiles below allows to select 2/4/... CPUs, 4/8/... GB RAM and between two images.
428+
429+
singleuser:
430+
...
431+
profileList:
432+
- display_name: "Default"
433+
description: "Default profile"
434+
default: true
435+
profile_options:
436+
cpu:
437+
display_name: CPU
438+
choices:
439+
"2":
440+
display_name: "2"
441+
kubespawner_override:
442+
cpu_guarantee: 2
443+
cpu_limit: 2
444+
"4":
445+
display_name: "4"
446+
kubespawner_override:
447+
cpu_guarantee: 4
448+
cpu_limit: 4
449+
...
450+
memory:
451+
display_name: Memory
452+
choices:
453+
"4 GB":
454+
display_name: "4 GB"
455+
kubespawner_override:
456+
mem_guarantee: "4G"
457+
mem_limit: "4G"
458+
"8 GB":
459+
display_name: "8 GB"
460+
kubespawner_override:
461+
mem_guarantee: "8G"
462+
mem_limit: "8G"
463+
...
464+
image:
465+
display_name: Image
466+
choices:
467+
"quay.io/jupyter/pyspark-notebook:python-3.11.9":
468+
display_name: "quay.io/jupyter/pyspark-notebook:python-3.11.9"
469+
kubespawner_override:
470+
image: "quay.io/jupyter/pyspark-notebook:python-3.11.9"
471+
"quay.io/jupyter/pyspark-notebook:spark-3.5.2":
472+
display_name: "quay.io/jupyter/pyspark-notebook:spark-3.5.2"
473+
kubespawner_override:
474+
image: "quay.io/jupyter/pyspark-notebook:spark-3.5.2"
475+
476+
These options are then displayed as drop-down lists for the user once logged in:
477+
478+
image::jupyterhub/server-options.png[Server options]
479+
359480
== Images
360481
361482
== Example Notebook

0 commit comments

Comments
 (0)