mirror of
https://github.com/stellarshenson/stellars-jupyterhub-ds.git
synced 2026-03-07 21:50:28 +00:00
feat: idle server culler for automatic shutdown of inactive servers
- Add jupyterhub-idle-culler package to Dockerfile - Configure as managed JupyterHub service with role-based scopes - Environment variables: IDLE_CULLER_ENABLED, IDLE_CULLER_TIMEOUT, IDLE_CULLER_CULL_EVERY, IDLE_CULLER_MAX_AGE - Default: disabled, 24h timeout, 10min check interval - Bump version to 3.6.0
This commit is contained in:
@@ -138,3 +138,6 @@ This journal tracks substantive work on documents, diagrams, and documentation c
|
||||
|
||||
45. **Task - Selective notification recipients**: Enhanced notification broadcast to allow targeting specific servers<br>
|
||||
**Result**: Added ActiveServersHandler (`GET /api/notifications/active-servers`) to list active servers. Modified BroadcastNotificationHandler to accept optional `recipients` array - filters to selected users if provided, sends to all if omitted (backward compatible). Updated notifications.html with "Send to all active servers" checkbox, server selection list with Select All/Deselect All buttons, dynamic button text showing recipient count. Validation prevents sending with no recipients selected
|
||||
|
||||
46. **Task - Idle server culler**: Implemented automatic shutdown of inactive servers<br>
|
||||
**Result**: Added jupyterhub-idle-culler package to Dockerfile. Added configuration with environment variables: IDLE_CULLER_ENABLED (default 0), IDLE_CULLER_TIMEOUT (default 86400s/24h), IDLE_CULLER_CULL_EVERY (default 600s/10min), IDLE_CULLER_MAX_AGE (default 0/unlimited). Service runs as managed JupyterHub service with role-based scopes. Disabled by default, opt-in via IDLE_CULLER_ENABLED=1. Bumped version to 3.6.0
|
||||
|
||||
19
README.md
19
README.md
@@ -18,6 +18,7 @@ Multi-user JupyterHub 4 deployment platform with data science stack, GPU support
|
||||
- **Native Authentication**: Built-in user management with NativeAuthenticator supporting optional self-registration (`ENABLE_SIGNUP`) and admin approval. Authorization page protects existing users from accidental discard - only pending signup requests can be discarded
|
||||
- **Admin User Creation**: Batch user creation from admin panel with auto-generated mnemonic passwords (e.g., `storm-apple-ocean`). Credentials modal with copy/download options
|
||||
- **Shared Storage**: Optional CIFS/NAS mount support for shared datasets across all users
|
||||
- **Idle Server Culler**: Automatic shutdown of inactive servers after configurable timeout (default: 24 hours). Frees resources when users leave servers running
|
||||
- **Production Ready**: Traefik reverse proxy with TLS termination, automatic container updates via Watchtower
|
||||
|
||||
## User Interface
|
||||
@@ -360,6 +361,24 @@ services:
|
||||
- ENABLE_SIGNUP=0 # disable self-registration, admin creates users
|
||||
```
|
||||
|
||||
#### Idle Server Culler
|
||||
|
||||
Automatically stop user servers after a period of inactivity to free up resources. Disabled by default.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
jupyterhub:
|
||||
environment:
|
||||
- IDLE_CULLER_ENABLED=1 # enable idle culler
|
||||
- IDLE_CULLER_TIMEOUT=86400 # 24 hours (default) - stop after this many seconds of inactivity
|
||||
- IDLE_CULLER_CULL_EVERY=600 # 10 minutes (default) - how often to check for idle servers
|
||||
- IDLE_CULLER_MAX_AGE=0 # 0 (default) - max server age regardless of activity (0=unlimited)
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
- `IDLE_CULLER_TIMEOUT`: Server is stopped after this many seconds without activity. Active servers are never culled
|
||||
- `IDLE_CULLER_MAX_AGE`: Force stop servers older than this (useful to force image updates). Set to 0 to disable
|
||||
|
||||
#### Custom Branding
|
||||
|
||||
Replace the default JupyterHub logo with a custom logo. Mount your logo file and reference it via environment variable:
|
||||
|
||||
@@ -188,7 +188,11 @@ ENABLE_GPU_SUPPORT = int(os.environ.get("ENABLE_GPU_SUPPORT", 2))
|
||||
ENABLE_SERVICE_MLFLOW = int(os.environ.get("ENABLE_SERVICE_MLFLOW", 1))
|
||||
ENABLE_SERVICE_GLANCES = int(os.environ.get("ENABLE_SERVICE_GLANCES", 1))
|
||||
ENABLE_SERVICE_TENSORBOARD = int(os.environ.get("ENABLE_SERVICE_TENSORBOARD", 1))
|
||||
ENABLE_SIGNUP = int(os.environ.get("ENABLE_SIGNUP", 1)) # 0 - disabled (admin creates users), 1 - enabled (self-registration)
|
||||
ENABLE_SIGNUP = int(os.environ.get("ENABLE_SIGNUP", 1)) # 0 - disabled (admin creates users), 1 - enabled (self-registration)
|
||||
IDLE_CULLER_ENABLED = int(os.environ.get("IDLE_CULLER_ENABLED", 0)) # 0 - disabled, 1 - enabled
|
||||
IDLE_CULLER_TIMEOUT = int(os.environ.get("IDLE_CULLER_TIMEOUT", 86400)) # idle timeout in seconds (default: 24 hours)
|
||||
IDLE_CULLER_CULL_EVERY = int(os.environ.get("IDLE_CULLER_CULL_EVERY", 600)) # check interval in seconds (default: 10 min)
|
||||
IDLE_CULLER_MAX_AGE = int(os.environ.get("IDLE_CULLER_MAX_AGE", 0)) # max server age in seconds (0 = unlimited)
|
||||
TF_CPP_MIN_LOG_LEVEL = int(os.environ.get("TF_CPP_MIN_LOG_LEVEL", 3))
|
||||
DOCKER_NOTEBOOK_DIR = "/home/lab/workspace"
|
||||
JUPYTERHUB_BASE_URL = os.environ.get("JUPYTERHUB_BASE_URL")
|
||||
@@ -421,4 +425,41 @@ if c is not None:
|
||||
(r'/notifications', NotificationsPageHandler),
|
||||
]
|
||||
|
||||
# Idle culler service - automatically stops servers after inactivity
|
||||
if IDLE_CULLER_ENABLED == 1:
|
||||
import sys
|
||||
|
||||
# Define role with required scopes for idle culler
|
||||
c.JupyterHub.load_roles = [
|
||||
{
|
||||
"name": "jupyterhub-idle-culler-role",
|
||||
"scopes": [
|
||||
"list:users",
|
||||
"read:users:activity",
|
||||
"read:servers",
|
||||
"delete:servers",
|
||||
],
|
||||
"services": ["jupyterhub-idle-culler"],
|
||||
}
|
||||
]
|
||||
|
||||
# Configure idle culler service
|
||||
culler_cmd = [
|
||||
sys.executable,
|
||||
"-m", "jupyterhub_idle_culler",
|
||||
f"--timeout={IDLE_CULLER_TIMEOUT}",
|
||||
f"--cull-every={IDLE_CULLER_CULL_EVERY}",
|
||||
]
|
||||
if IDLE_CULLER_MAX_AGE > 0:
|
||||
culler_cmd.append(f"--max-age={IDLE_CULLER_MAX_AGE}")
|
||||
|
||||
c.JupyterHub.services = [
|
||||
{
|
||||
"name": "jupyterhub-idle-culler",
|
||||
"command": culler_cmd,
|
||||
}
|
||||
]
|
||||
|
||||
print(f"[Idle Culler] Enabled - timeout={IDLE_CULLER_TIMEOUT}s, check every={IDLE_CULLER_CULL_EVERY}s, max_age={IDLE_CULLER_MAX_AGE}s")
|
||||
|
||||
# EOF
|
||||
|
||||
@@ -3,8 +3,8 @@ PROJECT_NAME="stellars-jupyterhub-ds"
|
||||
PROJECT_DESCRIPTION="Multi-user JupyterHub 4 deployment platform with data science stack, GPU auto-detection, NativeAuthenticator, and isolated per-user environments spawned via DockerSpawner"
|
||||
|
||||
# Version
|
||||
VERSION="3.5.46_cuda-12.9.1_jh-5.4.2"
|
||||
VERSION_COMMENT="Admin user creation with auto-generated passwords, NativeAuth sync, custom templates"
|
||||
VERSION="3.6.0_cuda-12.9.1_jh-5.4.2"
|
||||
VERSION_COMMENT="Idle server culler, selective notifications, volume encoding fix"
|
||||
RELEASE_TAG="RELEASE_3.2.11"
|
||||
RELEASE_DATE="2025-11-09"
|
||||
|
||||
|
||||
@@ -43,11 +43,12 @@ COPY --chmod=644 services/jupyterhub/templates_enhanced/*.html /srv/jupyterhub/
|
||||
COPY --chmod=644 services/jupyterhub/templates_enhanced/static/custom.css /tmp/custom.css
|
||||
COPY --chmod=644 config/jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py
|
||||
|
||||
## install dockerspawner, nativeauthenticator
|
||||
## install dockerspawner, nativeauthenticator, idle-culler
|
||||
RUN pip install -U --no-cache-dir \
|
||||
docker \
|
||||
dockerspawner \
|
||||
jupyterhub-nativeauthenticator
|
||||
jupyterhub-nativeauthenticator \
|
||||
jupyterhub-idle-culler
|
||||
|
||||
## copy custom.css to JupyterHub's static directory
|
||||
RUN <<-EOF
|
||||
|
||||
Reference in New Issue
Block a user