### Start JupyterHub Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Starts JupyterHub from the root directory of the Kubespawner repository, using the preconfigured jupyterhub_config.py. ```bash # Run this from the kubespawner repo's root directory where the preconfigured # jupyterhub_config.py file resides! jupyterhub ``` -------------------------------- ### Install Documentation Dependencies and Build HTML Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Install the necessary Python packages for documentation and then build the HTML documentation locally. This is recommended for making larger documentation changes. ```sh pip install -r docs/requirements.txt cd docs make html ``` -------------------------------- ### Setup Python Virtual Environment with venv Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Creates and activates a Python virtual environment using venv within the cloned repository directory. ```bash cd kubespawner python3 -m venv . source bin/activate ``` -------------------------------- ### Install Editable Kubespawner and Dependencies Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Installs Kubespawner in editable mode along with its testing dependencies. ```bash pip install -e ".[test]" ``` -------------------------------- ### Install and Configure Configurable HTTP Proxy Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Installs the nodejs-based Configurable HTTP Proxy (CHP) and adds its binary directory to the PATH. ```bash npm install configurable-http-proxy export PATH=$(pwd)/node_modules/.bin:$PATH ``` -------------------------------- ### Basic KubeSpawner Configuration Source: https://context7.com/jupyterhub/kubespawner/llms.txt Set the spawner class, default pod image, and start timeout. Configure Kubernetes namespace, resource limits and guarantees (CPU/RAM), and GPU requests. Define user and group IDs for container execution and specify image pull policies and secrets. Pin the working directory and control service account token mounting. ```python # jupyterhub_config.py c.JupyterHub.spawner_class = 'kubespawner.KubeSpawner' # Basic pod image and timeout c.KubeSpawner.image = 'quay.io/jupyterhub/singleuser:4.1' c.KubeSpawner.start_timeout = 300 # seconds # Kubernetes namespace (defaults to current namespace inside cluster) c.KubeSpawner.namespace = 'jhub' # Resource limits and guarantees c.KubeSpawner.cpu_limit = 2.0 c.KubeSpawner.cpu_guarantee = 0.5 c.KubeSpawner.mem_limit = '4G' c.KubeSpawner.mem_guarantee = '1G' # Request a GPU c.KubeSpawner.extra_resource_guarantees = {"nvidia.com/gpu": "1"} c.KubeSpawner.extra_resource_limits = {"nvidia.com/gpu": "1"} # Run containers as a specific UID/GID c.KubeSpawner.uid = 1000 c.KubeSpawner.gid = 100 c.KubeSpawner.fs_gid = 100 # group that owns mounted volumes # Image pull policy and private registry credentials c.KubeSpawner.image_pull_policy = 'IfNotPresent' c.KubeSpawner.image_pull_secrets = ['my-registry-secret'] # Pin the working directory c.KubeSpawner.working_dir = '/home/jovyan' # Disable JupyterHub service account token mount for security c.KubeSpawner.automount_service_account_token = False ``` -------------------------------- ### Implement a Custom ResourceReflector Source: https://context7.com/jupyterhub/kubespawner/llms.txt Create a custom reflector by subclassing ResourceReflector to mirror Kubernetes resources. This example shows how to watch for Services in a specific namespace with label selectors and handle reflection failures. ```python import asyncio from kubespawner.reflector import ResourceReflector from kubespawner.clients import load_config, shared_client # Typical internal usage pattern (also valid for custom reflectors) load_config() # loads in-cluster or kubeconfig credentials class ServiceReflector(ResourceReflector): kind = "services" api_group_name = "CoreV1Api" # list_method_name is derived automatically from kind async def main(): reflector = ServiceReflector( namespace="jhub", labels={"app": "jupyterhub"}, # watch only JupyterHub services request_timeout=60, timeout_seconds=10, restart_seconds=30, ) def on_failure(): print("Reflector gave up, shutting down") reflector.on_failure = on_failure # Start: does a full list first, then begins streaming watch await reflector.start() # Wait until the initial list has loaded await reflector.first_load_future # Access the current resource state (thread-safe dict) for key, service in reflector.resources.items(): print(key, service["metadata"]["name"]) # Graceful shutdown await reflector.stop() asyncio.run(main()) ``` -------------------------------- ### Kubespawner Template Field Examples Source: https://github.com/jupyterhub/kubespawner/blob/main/docs/source/templates.md Illustrates how the 'escape' and 'safe' slug schemes transform various input strings, highlighting differences in handling hyphens, capital letters, and long names. The 'safe' scheme aims to produce valid Kubernetes object names and labels. ```text Examples: | name | | ------------------------------------------------------------------- | | `username` | | `has-hyphen` | | `Capital` | | `user@email.com` | | `a-very-long-name-that-is-too-long-for-sixty-four-character-labels` | | escape scheme | | `username` | | `has-2dhyphen` | | `-43apital` (error) | | `user-40email-2ecom` | | `a-2dvery-2dlong-2dname-2dthat-2dis-2dtoo-2dlong-2dfor-2dsixty-2dfour-2dcharacter-2dlabels` (error) | | safe scheme | | `username` | | `has-hyphen` | | `capital---1a1cf792` | | `user-email-com---0925f997` | | `a-very-long-name-that-is-too-long-for---29ac5fd2` | ``` -------------------------------- ### Dynamic Pod Customization Hooks Source: https://context7.com/jupyterhub/kubespawner/llms.txt Customize user pods before creation using `modify_pod_hook` and perform actions after creation with `after_pod_created_hook`. Examples include injecting environment variables, adding init containers, and logging pod information. ```python # jupyterhub_config.py from kubernetes_asyncio.client.models import V1EnvVar async def modify_pod(spawner, pod): """Inject a per-user secret token and an init container.""" # Add a secret-backed environment variable pod.spec.containers[0].env.append( V1EnvVar( name='USER_TOKEN', value_from={ 'secretKeyRef': { 'name': f'user-token-{spawner.user.name}', 'key': 'token', 'optional': True, } }, ) ) # Add an init container that pre-populates the home directory pod.spec.init_containers = pod.spec.init_containers or [] pod.spec.init_containers.append({ 'name': 'setup-home', 'image': 'busybox', 'command': ['sh', '-c', 'cp -r /skeleton/. /home/jovyan/'], 'volumeMounts': [{'mountPath': '/home/jovyan', 'name': 'home'}], }) return pod c.KubeSpawner.modify_pod_hook = modify_pod async def after_pod_created(spawner, pod): """Log the pod IP after it is created.""" spawner.log.info( f"Pod {pod['metadata']['name']} created for {spawner.user.name}" ) c.KubeSpawner.after_pod_created_hook = after_pod_created ``` -------------------------------- ### Persistent Storage Configuration Source: https://context7.com/jupyterhub/kubespawner/llms.txt Configure persistent storage for user pods using Kubernetes PersistentVolumeClaims (PVCs). This example shows auto-provisioning, capacity, storage class, access modes, and mounting shared NFS and config maps. ```python # jupyterhub_config.py # Persistent home directory via PVC (auto-provisioned) c.KubeSpawner.storage_pvc_ensure = True c.KubeSpawner.storage_capacity = '10Gi' c.KubeSpawner.storage_class = 'standard' c.KubeSpawner.storage_access_modes = ['ReadWriteOnce'] c.KubeSpawner.pvc_name_template = 'claim-{username}' # Mount a shared NFS dataset read-only + user's home PVC read-write c.KubeSpawner.volumes = [ { 'name': 'home', 'persistentVolumeClaim': {'claimName': 'claim-{username}'}, }, { 'name': 'shared-data', 'nfs': {'server': 'nfs.example.com', 'path': '/datasets'}, }, { 'name': 'config-volume', 'configMap': {'name': 'notebook-config'}, }, ] c.KubeSpawner.volume_mounts = [ {'name': 'home', 'mountPath': '/home/jovyan', 'subPath': '{username}'}, {'name': 'shared-data', 'mountPath': '/data', 'readOnly': True}, {'name': 'config-volume', 'mountPath': '/etc/notebook'}, ] ``` -------------------------------- ### Configure Kubernetes API Host Source: https://context7.com/jupyterhub/kubespawner/llms.txt Example of overriding the Kubernetes API server URL in jupyterhub_config.py. This is normally auto-detected from in-cluster configuration. ```python # jupyterhub_config.py # Override the API server URL (normally auto-detected from in-cluster config) c.KubeSpawner.k8s_api_host = 'https://k8s-api.example.com:6443' ``` -------------------------------- ### Configure Host to Pod Networking (Linux) Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Adds a route on Linux to allow host communication with pods in minikube. Remember to undo this change later. ```bash sudo ip route add $(kubectl get node minikube -o jsonpath="{.spec.podCIDR}") via $(minikube ip) # later on you can undo this with sudo ip route del $(kubectl get node minikube -o jsonpath="{.spec.podCIDR}") ``` -------------------------------- ### Configure Host to Pod Networking (macOS) Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Adds a route on macOS to allow host communication with pods in minikube. Remember to undo this change later. ```bash sudo route -n add -net $(kubectl get node minikube -o jsonpath="{.spec.podCIDR}") $(minikube ip) # later on you can undo this with sudo route delete -net $(kubectl get node minikube -o jsonpath="{.spec.podCIDR}") ``` -------------------------------- ### List User Pods using Shared Client Source: https://context7.com/jupyterhub/kubespawner/llms.txt Demonstrates how to list user pods in a specific namespace using the shared Kubernetes client. Requires the 'shared_client' function to be available. ```python import asyncio async def list_user_pods(namespace='jhub'): api = shared_client('CoreV1Api') resp = await api.list_namespaced_pod( namespace, label_selector='component=singleuser-server', ) for pod in resp.items: print(pod.metadata.name, pod.status.phase) asyncio.run(list_user_pods()) ``` -------------------------------- ### Configure Advanced Pod Specifications with KubeSpawner Source: https://context7.com/jupyterhub/kubespawner/llms.txt Add Kubernetes lifecycle hooks, init containers that run before the notebook container, and extra sidecar containers that run alongside it. Also allows for extra pod-level and container-level configuration. ```python # jupyterhub_config.py # Lifecycle hook: run a script when the container starts/stops c.KubeSpawner.lifecycle_hooks = { 'postStart': { 'exec': { 'command': ['/bin/sh', '-c', 'echo "Server started" >> /tmp/lifecycle.log'], } }, 'preStop': { 'exec': { 'command': ['/bin/sh', '-c', 'jupyter nbconvert --to script /home/jovyan/*.ipynb || true'], } }, } # Init container: wait for a dependency before starting the notebook c.KubeSpawner.init_containers = [ { 'name': 'wait-for-db', 'image': 'busybox', 'command': ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done'], } ] # Sidecar container: run a cron job alongside the notebook c.KubeSpawner.extra_containers = [ { 'name': 'log-shipper', 'image': 'fluent/fluent-bit:latest', 'volumeMounts': [ {'name': 'logs', 'mountPath': '/var/log/notebooks'}, ], } ] # Extra pod-level and container-level config for fields not covered by traitlets c.KubeSpawner.extra_pod_config = { 'restartPolicy': 'OnFailure', 'dnsPolicy': 'ClusterFirstWithHostNet', 'hostNetwork': True, } c.KubeSpawner.extra_container_config = { 'envFrom': [{'configMapRef': {'name': 'common-env'}}], } ``` -------------------------------- ### make_ingress() Source: https://context7.com/jupyterhub/kubespawner/llms.txt Creates Kubernetes `V1Ingress`, `V1Service`, and `V1Endpoints` objects for exposing a user's notebook server. This is used by `KubeIngressProxy`. ```APIDOC ## make_ingress() ### Description Creates the Kubernetes `V1Ingress`, optional `V1Service`, and optional `V1Endpoints` objects needed by `KubeIngressProxy` to expose a user's notebook server. ### Parameters - **name** (str) - Required - The base name for the Kubernetes objects. - **routespec** (str) - Required - The routing specification for the ingress (e.g., '/user/alice/'). - **target** (str) - Required - The target URL of the notebook server (e.g., 'http://10.0.0.5:8888'). - **data** (dict) - Optional - Additional data to be included, often used for user identification. - **namespace** (str) - Optional - The Kubernetes namespace to deploy into. Defaults to the namespace of the running pod. - **common_labels** (dict) - Optional - Labels to apply to all generated objects. - **ingress_extra_labels** (dict) - Optional - Additional labels specifically for the Ingress object. - **ingress_extra_annotations** (dict) - Optional - Additional annotations specifically for the Ingress object. - **ingress_class_name** (str) - Optional - The name of the ingress class to use. - **ingress_specifications** (list[dict]) - Optional - A list of ingress specifications, each containing 'host' and 'tlsSecret'. ### Returns - `V1Endpoints` - The generated Kubernetes Endpoints object. - `V1Service` - The generated Kubernetes Service object. - `V1Ingress` - The generated Kubernetes Ingress object. ### Request Example ```python from kubespawner.objects import make_ingress endpoint, service, ingress = make_ingress( name='jupyter-alice', routespec='/user/alice/', target='http://10.0.0.5:8888', data={'user': 'alice'}, namespace='jhub', common_labels={'app': 'jupyterhub'}, ingress_extra_labels={'env': 'production'}, ingress_extra_annotations={'nginx.ingress.kubernetes.io/proxy-read-timeout': '3600'}, ingress_class_name='nginx', ingress_specifications=[ {'host': 'hub.example.com', 'tlsSecret': 'tls-secret'}, ], ) ``` ### Response Example ```python print(ingress.spec.rules[0].host) # hub.example.com print(ingress.spec.rules[0].http.paths[0].path) # /user/alice/ print(service) # V1Service (ClusterIP for IP target) print(endpoint) # V1Endpoints ``` ``` -------------------------------- ### Create Ingress, Service, and Endpoint objects Source: https://context7.com/jupyterhub/kubespawner/llms.txt Use `make_ingress` to create Kubernetes Ingress, Service, and Endpoints objects for exposing a user's notebook server. Configure routes, target, namespace, and ingress-specific annotations. ```python from kubespawner.objects import make_ingress endpoint, service, ingress = make_ingress( name='jupyter-alice', routespec='/user/alice/', target='http://10.0.0.5:8888', data={'user': 'alice'}, namespace='jhub', common_labels={'app': 'jupyterhub'}, ingress_extra_labels={'env': 'production'}, ingress_extra_annotations={'nginx.ingress.kubernetes.io/proxy-read-timeout': '3600'}, ingress_class_name='nginx', ingress_specifications=[ {'host': 'hub.example.com', 'tlsSecret': 'tls-secret'}, ], ) print(ingress.spec.rules[0].host) # hub.example.com print(ingress.spec.rules[0].http.paths[0].path) # /user/alice/ print(service) # V1Service (ClusterIP for IP target) print(endpoint) # V1Endpoints ``` -------------------------------- ### Create a PersistentVolumeClaim manifest Source: https://context7.com/jupyterhub/kubespawner/llms.txt Use `make_pvc` to generate a V1PersistentVolumeClaim manifest for user home directory storage. Specify storage class, access modes, capacity, and labels. ```python from kubespawner.objects import make_pvc pvc = make_pvc( name='claim-alice', storage_class='standard-ssd', access_modes=['ReadWriteOnce'], selector={'matchLabels': {'content': 'jupyter'}}, storage='10Gi', labels={'hub.jupyter.org/username': 'alice'}, annotations={'hub.jupyter.org/kubespawner-version': '7.0.0'}, ) print(pvc.kind) # PersistentVolumeClaim print(pvc.spec.resources.requests) # {'storage': '10Gi'} print(pvc.spec.storage_class_name) # standard-ssd ``` -------------------------------- ### Create a Namespace manifest Source: https://context7.com/jupyterhub/kubespawner/llms.txt Use `make_namespace` to generate a V1Namespace manifest, typically used when `enable_user_namespaces=True`. This is called internally before a user pod is created. ```python from kubespawner.objects import make_namespace ns = make_namespace( name='jhub-alice', labels={ 'app.kubernetes.io/managed-by': 'kubespawner', 'hub.jupyter.org/username': 'alice', }, annotations={'hub.jupyter.org/username': 'alice'}, ) print(ns.metadata.name) # jhub-alice print(ns.metadata.labels) # {'app.kubernetes.io/managed-by': 'kubespawner', ...} ``` -------------------------------- ### Manage Kubernetes API Client Configuration Source: https://context7.com/jupyterhub/kubespawner/llms.txt Use `load_config` to initialize the Kubernetes client configuration, supporting in-cluster credentials or kubeconfig files. `shared_client` provides cached, loop-scoped API client instances. ```python from kubespawner.clients import load_config, shared_client # Called once at startup (results are cached via lru_cache) load_config( host='https://k8s.example.com:6443', # override API server URL ssl_ca_cert='/path/to/ca.crt', # custom CA bundle verify_ssl=True, ) # Returns a cached CoreV1Api instance for the current event loop core_v1 = shared_client('CoreV1Api') # Returns a cached NetworkingV1Api instance networking_v1 = shared_client('NetworkingV1Api') ``` -------------------------------- ### load_config() and shared_client() Source: https://context7.com/jupyterhub/kubespawner/llms.txt Utilities for managing Kubernetes API client configuration and instances. `load_config` initializes the client configuration, and `shared_client` provides cached, loop-scoped Kubernetes API client instances. ```APIDOC ## load_config() and shared_client() ### Description `load_config` initialises the global `kubernetes_asyncio` configuration from in-cluster service account credentials or from `~/.kube/config`. `shared_client` returns a cached, loop-scoped instance of any Kubernetes API class, avoiding redundant connections. ### Parameters - **host** (str) - Optional - Override the API server URL. - **ssl_ca_cert** (str) - Optional - Path to a custom CA bundle. - **verify_ssl** (bool) - Optional - Whether to verify SSL certificates. - **api_class_name** (str) - Required for `shared_client` - The name of the Kubernetes API class to instantiate (e.g., 'CoreV1Api'). ### Usage Examples ```python from kubespawner.clients import load_config, shared_client # Called once at startup (results are cached via lru_cache) load_config( host='https://k8s.example.com:6443', # override API server URL ssl_ca_cert='/path/to/ca.crt', # custom CA bundle verify_ssl=True, ) # Returns a cached CoreV1Api instance for the current event loop core_v1 = shared_client('CoreV1Api') # Returns a cached NetworkingV1Api instance networking_v1 = shared_client('NetworkingV1Api') ``` ``` -------------------------------- ### Configure Security Contexts in KubeSpawner Source: https://context7.com/jupyterhub/kubespawner/llms.txt Set fixed or dynamic UIDs, GIDs, and supplemental GIDs. Use container_security_context or pod_security_context for advanced configurations like read-only root filesystems or seccomp profiles. Dynamic UIDs can be generated using callables. ```python # jupyterhub_config.py # Simple: run as a fixed UID/GID c.KubeSpawner.uid = 1000 c.KubeSpawner.gid = 100 c.KubeSpawner.fs_gid = 100 # fsGroup on the pod security context c.KubeSpawner.supplemental_gids = [1001, 1002] c.KubeSpawner.allow_privilege_escalation = False # Advanced: full container security context (overrides uid/gid/privileged) c.KubeSpawner.container_security_context = { 'runAsUser': 1000, 'runAsGroup': 100, 'allowPrivilegeEscalation': False, 'readOnlyRootFilesystem': True, 'capabilities': {'drop': ['ALL']}, 'seccompProfile': {'type': 'RuntimeDefault'}, } # Pod-level security context (affects all containers including init/sidecars) c.KubeSpawner.pod_security_context = { 'runAsNonRoot': True, 'fsGroup': 100, 'fsGroupChangePolicy': 'OnRootMismatch', 'sysctls': [{'name': 'net.ipv4.tcp_keepalive_time', 'value': '60'}], } # UID/GID can also be dynamic callables async def get_uid(spawner): return 1000 + hash(spawner.user.name) % 1000 c.KubeSpawner.uid = get_uid ``` -------------------------------- ### Reset Version to Development Source: https://github.com/jupyterhub/kubespawner/blob/main/RELEASE.md After a successful release, reset the version to a development state (e.g., '2.0.1.dev0') for the next iteration. ```shell tbump --no-tag ${NEXT_VERSION}.dev0 ``` -------------------------------- ### Clone Kubespawner Repository Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Clones the Kubespawner repository from GitHub to your local machine. ```sh git clone https://github.com/jupyterhub/kubespawner.git ``` -------------------------------- ### KubeSpawner Server Profile Selection Source: https://context7.com/jupyterhub/kubespawner/llms.txt Define a list of server profiles for users to select at spawn time. Each profile can override KubeSpawner traitlets and offer nested profile options for finer control over environment choices like images and resources. ```python # jupyterhub_config.py c.KubeSpawner.profile_list = [ { 'display_name': 'Standard (2 CPU / 4 GB)', 'description': 'Standard environment for most workloads.', 'slug': 'standard', 'default': True, 'profile_options': { 'image': { 'display_name': 'Image', 'choices': { 'scipy': { 'display_name': 'Scipy Notebook', 'default': True, 'kubespawner_override': { 'image': 'quay.io/jupyter/scipy-notebook:latest', }, }, 'datascience': { 'display_name': 'Data Science Notebook', 'kubespawner_override': { 'image': 'quay.io/jupyter/datascience-notebook:latest', }, }, }, # Allow users to type a custom image name 'unlisted_choice': { 'enabled': True, 'display_name': 'Custom image', 'validation_regex': r'^.+:.+$', 'validation_message': 'Must be image:tag', 'kubespawner_override': {'image': '{value}'}, }, }, }, 'kubespawner_override': { 'cpu_limit': 2, 'mem_limit': '4G', 'default_url': '/lab', }, }, { 'display_name': 'GPU (1 GPU / 8 GB)', 'slug': 'gpu', 'kubespawner_override': { 'image': 'quay.io/jupyter/pytorch-notebook:latest', 'extra_resource_guarantees': {'nvidia.com/gpu': '1'}, 'extra_resource_limits': {'nvidia.com/gpu': '1'}, 'mem_limit': '8G', 'node_selector': {'accelerator': 'nvidia-tesla-t4'}, }, }, ] ``` -------------------------------- ### make_namespace() Source: https://context7.com/jupyterhub/kubespawner/llms.txt Creates a `V1Namespace` manifest, used when `enable_user_namespaces=True`. This is called internally before a user pod is created. ```APIDOC ## make_namespace() ### Description Creates a `V1Namespace` manifest used when `enable_user_namespaces=True`. Called internally before the user pod is created. ### Parameters - **name** (str) - Required - The name of the namespace. - **labels** (dict) - Optional - Labels to apply to the namespace. - **annotations** (dict) - Optional - Annotations to apply to the namespace. ### Returns - `V1Namespace` - The generated Kubernetes Namespace object. ### Request Example ```python from kubespawner.objects import make_namespace ns = make_namespace( name='jhub-alice', labels={ 'app.kubernetes.io/managed-by': 'kubespawner', 'hub.jupyter.org/username': 'alice', }, annotations={'hub.jupyter.org/username': 'alice'}, ) ``` ### Response Example ```python print(ns.metadata.name) # jhub-alice print(ns.metadata.labels) # {'app.kubernetes.io/managed-by': 'kubespawner', ...} ``` ``` -------------------------------- ### Customize Pod and PVC Naming Templates with KubeSpawner Source: https://context7.com/jupyterhub/kubespawner/llms.txt Control the names given to pods and PVCs using Python format-string templates. Template fields include `{username}`, `{servername}`, `{user_server}`, and more. The `safe` slug scheme is the default. ```python # jupyterhub_config.py # Custom pod name template c.KubeSpawner.pod_name_template = 'nb-{user_server}' # -> user "alice" default server => "nb-alice" # -> user "alice" named "work" => "nb-alice--work" # -> user "Bob@Corp.com" => "nb-bob-corp-com---" # Custom PVC name template (username only = shared across named servers) c.KubeSpawner.pvc_name_template = 'home-{username}' # Lock in the PVC name after first use so renames don't detach data c.KubeSpawner.remember_pvc_name = True # Custom namespace template (only when enable_user_namespaces=True) c.KubeSpawner.user_namespace_template = '{hubnamespace}-{username}' # Pin the slug scheme explicitly c.KubeSpawner.slug_scheme = 'safe' # default; 'escape' for legacy behaviour ``` -------------------------------- ### Enable Internal SSL and Set KubeSpawner Source: https://github.com/jupyterhub/kubespawner/blob/main/docs/source/ssl.md Enable internal SSL for JupyterHub and specify KubeSpawner as the spawner class. This is the primary step to activate mutual TLS for internal communication. ```python c.JupyterHub.internal_ssl = True c.JupyterHub.spawner_class = 'kubespawner.KubeSpawner' ``` -------------------------------- ### Configure Kubernetes Metadata with KubeSpawner Source: https://context7.com/jupyterhub/kubespawner/llms.txt Attach custom labels and annotations to user pods and PVCs. Template variables are expanded per-user. `common_labels` controls the base set applied to all resources. ```python # jupyterhub_config.py # Override common labels (applied to all pods and PVCs) c.KubeSpawner.common_labels = { 'app.kubernetes.io/name': 'jupyterhub', 'app.kubernetes.io/managed-by': 'kubespawner', 'app': 'jupyterhub', 'heritage': 'jupyterhub', 'environment': 'production', } # Per-pod labels referencing template variables c.KubeSpawner.extra_labels = { 'hub.jupyter.org/team': 'data-science', 'user': '{username}', } # Per-pod annotations (arbitrary key-value metadata) c.KubeSpawner.extra_annotations = { 'prometheus.io/scrape': 'true', 'prometheus.io/port': '8888', 'cluster-autoscaler.kubernetes.io/safe-to-evict': 'false', } # Extra labels on PVCs only c.KubeSpawner.storage_extra_labels = {'user': '{username}'} c.KubeSpawner.storage_extra_annotations = {'backup': 'daily'} ``` -------------------------------- ### Run All Automated Tests Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Executes the entire automated test suite for Kubespawner using pytest. ```bash pytest ``` -------------------------------- ### Configure Secret Name Template and Mount Path Source: https://github.com/jupyterhub/kubespawner/blob/main/docs/source/ssl.md Customize the template for Kubernetes secret names and the mount path for SSL certificates within user pods. Defaults are provided. ```python c.KubeSpawner.secret_name_template = "{pod_name}" c.KubeSpawner.secret_mount_path = "/etc/jupyterhub/ssl/" ``` -------------------------------- ### Checkout and Update Main Branch Source: https://github.com/jupyterhub/kubespawner/blob/main/RELEASE.md Ensure your local main branch is up-to-date with the remote repository before proceeding with release steps. ```shell git checkout main git fetch origin main git reset --hard origin/main ``` -------------------------------- ### Bump Version and Tag Release Source: https://github.com/jupyterhub/kubespawner/blob/main/RELEASE.md Use tbump to update the version number, create a commit, and push a git tag for the release. The CI system will handle building and publishing. ```shell pip install tbump tbump --dry-run ${VERSION} # run tbump ${VERSION} ``` -------------------------------- ### Configure Docker Daemon for IP Range Conflict Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Modifies the Docker daemon configuration to resolve IP range conflicts with minikube. Restart Docker after applying changes. ```json { "bip": "172.19.1.1/16" } ``` -------------------------------- ### make_pvc() Source: https://context7.com/jupyterhub/kubespawner/llms.txt Creates a V1PersistentVolumeClaim manifest for a user's home directory storage. This function is useful for defining persistent storage for user pods. ```APIDOC ## make_pvc() ### Description Creates a `V1PersistentVolumeClaim` manifest for a user's home directory storage. ### Parameters - **name** (str) - Required - The name of the PVC. - **storage_class** (str) - Optional - The storage class to use for the PVC. - **access_modes** (list[str]) - Optional - The access modes for the PVC (e.g., ['ReadWriteOnce']). - **selector** (dict) - Optional - A dictionary of label selectors for the PVC. - **storage** (str) - Required - The amount of storage to request (e.g., '10Gi'). - **labels** (dict) - Optional - Labels to apply to the PVC. - **annotations** (dict) - Optional - Annotations to apply to the PVC. ### Returns - `V1PersistentVolumeClaim` - The generated Kubernetes PersistentVolumeClaim object. ### Request Example ```python from kubespawner.objects import make_pvc pvc = make_pvc( name='claim-alice', storage_class='standard-ssd', access_modes=['ReadWriteOnce'], selector={'matchLabels': {'content': 'jupyter'}}, storage='10Gi', labels={'hub.jupyter.org/username': 'alice'}, annotations={'hub.jupyter.org/kubespawner-version': '7.0.0'}, ) ``` ### Response Example ```python print(pvc.kind) # PersistentVolumeClaim print(pvc.spec.resources.requests) # {'storage': '10Gi'} print(pvc.spec.storage_class_name) # standard-ssd ``` ``` -------------------------------- ### Enable Per-User Namespace Isolation with KubeSpawner Source: https://context7.com/jupyterhub/kubespawner/llms.txt When enabled, each user's pods are spawned into a dedicated Kubernetes namespace. The Hub must hold ClusterRole-level RBAC privileges. Labels/annotations can be applied when the namespace is first created. ```python # jupyterhub_config.py c.KubeSpawner.enable_user_namespaces = True # Namespace template: defaults to "{hubnamespace}-{username}" c.KubeSpawner.user_namespace_template = 'jhub-user-{username}' # Labels/annotations applied when the namespace is first created c.KubeSpawner.user_namespace_labels = { 'app.kubernetes.io/managed-by': 'kubespawner', 'hub.jupyter.org/username': '{username}', } c.KubeSpawner.user_namespace_annotations = { 'hub.jupyter.org/team': 'research', } # Clean up namespace on server stop (via post_stop_hook) async def cleanup_namespace(spawner): from kubernetes_asyncio import client v1 = client.CoreV1Api() try: await v1.delete_namespace(spawner.namespace) except Exception as e: spawner.log.warning(f"Could not delete namespace {spawner.namespace}: {e}") c.KubeSpawner.post_stop_hook = cleanup_namespace ``` -------------------------------- ### make_pod Source: https://github.com/jupyterhub/kubespawner/blob/main/docs/source/objects.md Creates a Kubernetes Pod object. This function is used internally by Kubespawner to define the specification for a user's spawned pod. ```APIDOC ## make_pod ### Description Creates a Kubernetes Pod object. ### Signature `make_pod(name, image, cmd=None, args=None, env=None, jupyter_server_uri=None, uid=None, gid=None, fs_gid=None, kubernetes_user=None, kubernetes_groups=None, extra_annotations=None, extra_labels=None, node_selector=None, tolerations=None, resources=None, image_pull_secrets=None, volumes=None, volume_mounts=None, service_account=None, security_context=None, init_containers=None, sidecar_containers=None, node_name=None, scheduler_name=None, runtime_class_name=None, enable_resource_limits=False, priority_class_name=None, enable_dns_log_rotate=False, pod_override_object=None, pod_yaml_template=None, **kwargs)` ### Parameters * **name** (str) - The name of the pod. * **image** (str) - The container image to use for the pod. * **cmd** (list, optional) - The command to run in the container. * **args** (list, optional) - Arguments to the command. * **env** (dict, optional) - Environment variables for the container. * **jupyter_server_uri** (str, optional) - The URI of the Jupyter server. * **uid** (int, optional) - The user ID for the container. * **gid** (int, optional) - The group ID for the container. * **fs_gid** (int, optional) - The file system group ID for the container. * **kubernetes_user** (str, optional) - The Kubernetes username. * **kubernetes_groups** (list, optional) - The Kubernetes groups. * **extra_annotations** (dict, optional) - Extra annotations to add to the pod. * **extra_labels** (dict, optional) - Extra labels to add to the pod. * **node_selector** (dict, optional) - Node selector for pod scheduling. * **tolerations** (list, optional) - Tolerations for pod scheduling. * **resources** (dict, optional) - Resource requests and limits for the pod. * **image_pull_secrets** (list, optional) - Image pull secrets for the pod. * **volumes** (list, optional) - Volumes to mount in the pod. * **volume_mounts** (list, optional) - Volume mounts for the container. * **service_account** (str, optional) - The service account to use for the pod. * **security_context** (dict, optional) - Security context for the pod. * **init_containers** (list, optional) - Init containers for the pod. * **sidecar_containers** (list, optional) - Sidecar containers for the pod. * **node_name** (str, optional) - The name of the node to schedule the pod on. * **scheduler_name** (str, optional) - The name of the scheduler to use for the pod. * **runtime_class_name** (str, optional) - The runtime class name for the pod. * **enable_resource_limits** (bool, optional) - Whether to enable resource limits for the pod. * **priority_class_name** (str, optional) - The priority class name for the pod. * **enable_dns_log_rotate** (bool, optional) - Whether to enable DNS log rotation for the pod. * **pod_override_object** (dict, optional) - An object to override parts of the generated pod spec. * **pod_yaml_template** (str, optional) - A YAML template for the pod. * **kwargs** - Additional keyword arguments. ### Returns * A Kubernetes Pod object. ``` -------------------------------- ### Convert Dict to Kubernetes Model Instance Source: https://context7.com/jupyterhub/kubespawner/llms.txt Converts a Python dictionary with camelCase or snake_case keys into a typed kubernetes_asyncio model instance. Useful for creating Kubernetes objects from configuration. ```python from kubernetes_asyncio.client.models import V1Container from kubespawner.utils import get_k8s_model # Convert a dict to a typed model (camelCase keys are mapped automatically) container = get_k8s_model(V1Container, { 'name': 'sidecar', 'image': 'busybox', 'command': ['sleep', 'infinity'], 'resources': { 'requests': {'memory': '64Mi'}, 'limits': {'memory': '128Mi'}, }, }) print(type(container)) # print(container.image) # busybox ``` -------------------------------- ### Clean Build Artifacts Source: https://github.com/jupyterhub/kubespawner/blob/main/CONTRIBUTING.md Removes the .eggs directory, which can sometimes cause issues with test runs. ```bash rm -rf .eggs ``` -------------------------------- ### Configure Kubespawner Slug Scheme Source: https://github.com/jupyterhub/kubespawner/blob/main/docs/source/templates.md Select the slug generation scheme for template strings. 'safe' is the default in Kubespawner 7 and ensures valid object names and labels. 'escape' maintains compatibility with versions prior to 7. ```python c.KubeSpawner.slug_scheme = "escape" # no changes from kubespawner 6 c.KubeSpawner.slug_scheme = "safe" # default for kubespawner 7 ``` -------------------------------- ### Dynamic Profile List via Callable Source: https://context7.com/jupyterhub/kubespawner/llms.txt Define user-specific Jupyter notebook server profiles dynamically using a Python callable. This allows for conditional profile generation based on user attributes. ```python async def dynamic_profiles(spawner): """Return profiles based on the user's group membership.""" username = spawner.user.name if username in ['admin', 'researcher']: return [{'display_name': 'Large (16 CPU / 64 GB)', 'kubespawner_override': {'cpu_limit': 16, 'mem_limit': '64G'}}] return [{'display_name': 'Small (1 CPU / 2 GB)', 'kubespawner_override': {'cpu_limit': 1, 'mem_limit': '2G'}}] c.KubeSpawner.profile_list = dynamic_profiles ``` -------------------------------- ### Generate Kubernetes-Safe Names Source: https://context7.com/jupyterhub/kubespawner/llms.txt Utilize `safe_slug`, `escape_slug`, and `multi_slug` for generating valid Kubernetes DNS-label names from arbitrary strings. `is_valid_object_name` checks compliance with Kubernetes naming rules. ```python from kubespawner.slugs import safe_slug, escape_slug, multi_slug, is_valid_object_name # Simple valid names pass through unchanged print(safe_slug('alice')) # alice print(safe_slug('my-server')) # my-server # Names with special characters get a hash suffix print(safe_slug('alice@corp.com')) # alice-corp-com---<8-char hash> print(safe_slug('Capital')) # capital--- print(safe_slug('has--double')) # has--- (-- is a reserved delimiter) # Legacy escape scheme print(escape_slug('alice@corp.com')) # alice-40corp-2ecom print(escape_slug('Capital')) # -43apital (not a valid k8s name!) # Combine username + servername into a single slug (used in pod_name_template) print(multi_slug(['alice', 'my server'])) # alice--my-server--- print(multi_slug(['alice@corp.com', 'work'])) # alice-co--work--- # Validate a string against Kubernetes object name rules print(is_valid_object_name('jupyter-alice')) # True print(is_valid_object_name('Alice')) # False (capital letter) print(is_valid_object_name('a' * 64)) # False (> 63 chars) ``` -------------------------------- ### Configure Kubernetes API Retry Timeout Source: https://context7.com/jupyterhub/kubespawner/llms.txt Sets the total retry budget in seconds, including exponential backoff, for Kubernetes API requests. The default is 30 seconds. ```python # jupyterhub_config.py # Total retry budget including exponential backoff (default 30 s) c.KubeSpawner.k8s_api_request_retry_timeout = 60 ``` -------------------------------- ### Recursive Template Expansion with recursive_format Source: https://context7.com/jupyterhub/kubespawner/llms.txt Applies Python str.format_map substitution recursively through nested strings, lists, and dicts. Missing keys are left as-is, preventing KeyErrors. Used for expanding template variables in configuration fields. ```python from kubespawner.utils import recursive_format # Expand a simple string print(recursive_format('jupyter-{username}', username='alice')) # -> 'jupyter-alice' # Expand a nested structure config = { 'name': 'home-{username}', 'persistentVolumeClaim': {'claimName': 'claim-{username}'}, } result = recursive_format(config, username='alice') print(result) # -> {'name': 'home-alice', 'persistentVolumeClaim': {'claimName': 'claim-alice'}} # Missing keys are left in place (no KeyError) print(recursive_format('{username}-{unknown_key}', username='alice')) # -> 'alice-{unknown_key}' # Works with lists too mounts = [ {'mountPath': '/home/{username}', 'name': 'home'}, {'mountPath': '/tmp', 'name': 'tmp'}, ] print(recursive_format(mounts, username='bob')) # -> [{'mountPath': '/home/bob', 'name': 'home'}, {'mountPath': '/tmp', 'name': 'tmp'}] ``` -------------------------------- ### safe_slug(), escape_slug(), multi_slug(), is_valid_object_name() Source: https://context7.com/jupyterhub/kubespawner/llms.txt Utilities for generating Kubernetes-safe names and validating them. `safe_slug` and `escape_slug` convert arbitrary strings into valid Kubernetes DNS-label names, while `multi_slug` combines multiple components. `is_valid_object_name` checks if a string conforms to Kubernetes naming rules. ```APIDOC ## safe_slug() and escape_slug() ### Description `safe_slug` (new in v7, default slug scheme) produces always-valid Kubernetes DNS-label names from arbitrary strings. `escape_slug` is the legacy scheme. `multi_slug` combines multiple name components (e.g., username + servername) into a single valid slug with a collision-resistant hash. ### Parameters - **string** (str) - The input string to convert. - **components** (list[str]) - A list of string components to combine for `multi_slug`. ### Returns - `str` - A Kubernetes-safe name string. ### Usage Examples ```python from kubespawner.slugs import safe_slug, escape_slug, multi_slug, is_valid_object_name # Simple valid names pass through unchanged print(safe_slug('alice')) # alice print(safe_slug('my-server')) # my-server # Names with special characters get a hash suffix print(safe_slug('alice@corp.com')) # alice-corp-com---<8-char hash> print(safe_slug('Capital')) # capital--- print(safe_slug('has--double')) # has--- (-- is a reserved delimiter) # Legacy escape scheme print(escape_slug('alice@corp.com')) # alice-40corp-2ecom print(escape_slug('Capital')) # -43apital (not a valid k8s name!) # Combine username + servername into a single slug (used in pod_name_template) print(multi_slug(['alice', 'my server'])) # alice--my-server--- print(multi_slug(['alice@corp.com', 'work'])) # alice-co--work--- # Validate a string against Kubernetes object name rules print(is_valid_object_name('jupyter-alice')) # True print(is_valid_object_name('Alice')) # False (capital letter) print(is_valid_object_name('a' * 64)) # False (> 63 chars) ``` ```