Replaces the minimal project description with a comprehensive README including a component overview table, quick start instructions, common Ansible operations, and links to detailed documentation. Aligns with Red Panda Approval™ standards.
343 lines
12 KiB
Markdown
343 lines
12 KiB
Markdown
# JupyterLab - Interactive Computing Environment
|
|
|
|
## Overview
|
|
JupyterLab is a web-based interactive development environment for notebooks, code, and data. Deployed on **Puck** as a systemd service running in a Python virtual environment, with OAuth2-Proxy sidecar providing Casdoor SSO authentication.
|
|
|
|
**Host:** puck.incus
|
|
**Role:** Application Runtime (Python App Host)
|
|
**Container Port:** 22181 (JupyterLab), 22182 (OAuth2-Proxy)
|
|
**External Access:** https://jupyter.ouranos.helu.ca/ (via HAProxy on Titania)
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌──────────┐ ┌────────────┐ ┌─────────────┐ ┌────────────┐
|
|
│ Client │─────▶│ HAProxy │─────▶│ OAuth2-Proxy│─────▶│ JupyterLab │
|
|
│ │ │ (Titania) │ │ (Puck) │ │ (Puck) │
|
|
└──────────┘ └────────────┘ └─────────────┘ └────────────┘
|
|
│
|
|
▼
|
|
┌───────────┐
|
|
│ Casdoor │
|
|
│ (Titania) │
|
|
└───────────┘
|
|
```
|
|
|
|
### Authentication Flow
|
|
|
|
```
|
|
┌──────────┐ ┌────────────┐ ┌─────────────┐ ┌──────────┐
|
|
│ Browser │─────▶│ HAProxy │─────▶│ OAuth2-Proxy│─────▶│ Casdoor │
|
|
│ │ │ (Titania) │ │ (Puck) │ │(Titania) │
|
|
└──────────┘ └────────────┘ └─────────────┘ └──────────┘
|
|
│ │ │
|
|
│ 1. Access jupyter.ouranos.helu.ca │ │
|
|
│─────────────────────────────────────▶│ │
|
|
│ 2. No session - redirect to Casdoor │ │
|
|
│◀─────────────────────────────────────│ │
|
|
│ 3. User authenticates │ │
|
|
│─────────────────────────────────────────────────────────▶│
|
|
│ 4. Redirect with auth code │ │
|
|
│◀─────────────────────────────────────────────────────────│
|
|
│ 5. Exchange code, set session cookie│ │
|
|
│◀─────────────────────────────────────│ │
|
|
│ 6. Proxy to JupyterLab │ │
|
|
│◀─────────────────────────────────────│ │
|
|
```
|
|
|
|
## Deployment
|
|
|
|
### Playbook
|
|
|
|
```bash
|
|
cd ansible
|
|
ansible-playbook jupyterlab/deploy.yml
|
|
```
|
|
|
|
### Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `jupyterlab/deploy.yml` | Main deployment playbook |
|
|
| `jupyterlab/jupyterlab.service.j2` | Systemd unit for JupyterLab |
|
|
| `jupyterlab/oauth2-proxy-jupyter.service.j2` | Systemd unit for OAuth2-Proxy sidecar |
|
|
| `jupyterlab/oauth2-proxy-jupyter.cfg.j2` | OAuth2-Proxy configuration |
|
|
| `jupyterlab/jupyter_lab_config.py.j2` | JupyterLab server configuration |
|
|
|
|
### Deployment Steps
|
|
|
|
1. **Install Dependencies**: python3-venv, nodejs, npm, graphviz
|
|
2. **Ensure User Exists**: `robert:robert` with home directory
|
|
3. **Create Directories**: Notebooks dir, config dir, log dir
|
|
4. **Create Virtual Environment**: `/home/robert/env/jupyter`
|
|
5. **Install Python Packages**: jupyterlab, jupyter-ai, langchain-ollama, matplotlib, plotly
|
|
6. **Install Jupyter Extensions**: contrib nbextensions
|
|
7. **Template Configuration**: Apply JupyterLab config
|
|
8. **Download OAuth2-Proxy**: Binary from GitHub releases
|
|
9. **Template OAuth2-Proxy Config**: With Casdoor OIDC settings
|
|
10. **Start Services**: Enable and start both systemd units
|
|
|
|
## Configuration
|
|
|
|
### Key Features
|
|
|
|
- **Jupyter AI**: AI assistance via jupyter-ai[all] with LangChain Ollama integration
|
|
- **Visualization**: matplotlib, plotly for data visualization
|
|
- **Diagrams**: Mermaid support via jupyterlab-mermaid
|
|
- **Extensions**: Jupyter contrib nbextensions
|
|
- **SSO**: Casdoor authentication via OAuth2-Proxy sidecar
|
|
- **WebSocket**: Full WebSocket support through reverse proxy
|
|
|
|
### Storage Locations
|
|
|
|
| Path | Purpose | Owner |
|
|
|------|---------|-------|
|
|
| `/home/robert/Notebooks` | Notebook files | robert:robert |
|
|
| `/home/robert/env/jupyter` | Python virtual environment | robert:robert |
|
|
| `/etc/jupyterlab` | Configuration files | root:robert |
|
|
| `/var/log/jupyterlab` | Application logs | robert:robert |
|
|
| `/etc/oauth2-proxy-jupyter` | OAuth2-Proxy config | root:root |
|
|
|
|
### Installed Python Packages
|
|
|
|
| Package | Purpose |
|
|
|---------|---------|
|
|
| `jupyterlab` | Core JupyterLab server |
|
|
| `jupyter-ai[all]` | AI assistant integration |
|
|
| `langchain-ollama` | Ollama LLM integration |
|
|
| `matplotlib` | Data visualization |
|
|
| `plotly` | Interactive charts |
|
|
| `jupyter_contrib_nbextensions` | Community extensions |
|
|
| `jupyterlab-mermaid` | Mermaid diagram support |
|
|
| `ipywidgets` | Interactive widgets |
|
|
|
|
### Logging
|
|
|
|
- **JupyterLab**: systemd journal via `SyslogIdentifier=jupyterlab`
|
|
- **OAuth2-Proxy**: systemd journal via `SyslogIdentifier=oauth2-proxy-jupyter`
|
|
- **Alloy Forwarding**: Syslog port 51491 → Loki
|
|
|
|
## Access After Deployment
|
|
|
|
1. **Web Interface**: https://jupyter.ouranos.helu.ca/
|
|
2. **Authentication**: Redirects to Casdoor SSO login
|
|
3. **After Login**: Full JupyterLab interface with notebook access
|
|
|
|
## Monitoring
|
|
|
|
### Alloy Configuration
|
|
**File:** `ansible/alloy/puck/config.alloy.j2`
|
|
|
|
- **Log Collection**: Syslog port 51491 → Loki
|
|
- **Job Label**: `jupyterlab`
|
|
- **System Metrics**: Process exporter tracks JupyterLab process
|
|
|
|
### Health Check
|
|
- **URL**: `http://puck.incus:22182/ping` (OAuth2-Proxy)
|
|
- **JupyterLab API**: `http://127.0.0.1:22181/api/status` (localhost only)
|
|
|
|
## Required Vault Secrets
|
|
|
|
Add to `ansible/inventory/group_vars/all/vault.yml`:
|
|
|
|
### 1. OAuth Client ID
|
|
```yaml
|
|
vault_jupyter_oauth_client_id: "jupyter-oauth-client"
|
|
```
|
|
**Requirements:**
|
|
- **Purpose**: Client ID for Casdoor OAuth2 application
|
|
- **Source**: Must match `clientId` in Casdoor application configuration
|
|
|
|
### 2. OAuth Client Secret
|
|
```yaml
|
|
vault_jupyter_oauth_client_secret: "YourRandomOAuthSecret123!"
|
|
```
|
|
**Requirements:**
|
|
- **Length**: 32+ characters recommended
|
|
- **Purpose**: Client secret for Casdoor OAuth2 authentication
|
|
- **Generation**:
|
|
```bash
|
|
openssl rand -base64 32
|
|
```
|
|
|
|
### 3. Cookie Secret
|
|
```yaml
|
|
vault_jupyter_oauth2_cookie_secret: "32CharacterRandomStringHere1234"
|
|
```
|
|
**Requirements:**
|
|
- **Length**: Exactly 32 characters (or 16/24 for AES)
|
|
- **Purpose**: Encrypts OAuth2-Proxy session cookies
|
|
- **Generation**:
|
|
```bash
|
|
openssl rand -base64 32 | head -c 32
|
|
```
|
|
|
|
## Host Variables
|
|
|
|
**File:** `ansible/inventory/host_vars/puck.incus.yml`
|
|
|
|
```yaml
|
|
# JupyterLab Configuration
|
|
jupyterlab_user: robert
|
|
jupyterlab_group: robert
|
|
jupyterlab_notebook_dir: /home/robert/Notebooks
|
|
jupyterlab_venv_dir: /home/robert/env/jupyter
|
|
|
|
# Ports
|
|
jupyterlab_port: 22181 # JupyterLab (localhost only)
|
|
jupyterlab_proxy_port: 22182 # OAuth2-Proxy (exposed to HAProxy)
|
|
|
|
# OAuth2-Proxy Configuration
|
|
jupyterlab_oauth2_proxy_dir: /etc/oauth2-proxy-jupyter
|
|
jupyterlab_oauth2_proxy_version: "7.6.0"
|
|
jupyterlab_domain: "ouranos.helu.ca"
|
|
jupyterlab_oauth2_oidc_issuer_url: "https://id.ouranos.helu.ca"
|
|
jupyterlab_oauth2_redirect_url: "https://jupyter.ouranos.helu.ca/oauth2/callback"
|
|
|
|
# OAuth2 Credentials (from vault)
|
|
jupyterlab_oauth_client_id: "{{ vault_jupyter_oauth_client_id }}"
|
|
jupyterlab_oauth_client_secret: "{{ vault_jupyter_oauth_client_secret }}"
|
|
jupyterlab_oauth2_cookie_secret: "{{ vault_jupyter_oauth2_cookie_secret }}"
|
|
|
|
# Alloy Logging
|
|
jupyterlab_syslog_port: 51491
|
|
```
|
|
|
|
## OAuth2 / Casdoor SSO
|
|
|
|
JupyterLab uses OAuth2-Proxy as a sidecar to handle Casdoor authentication. This pattern is simpler than native OAuth for single-user setups.
|
|
|
|
### Why OAuth2-Proxy Sidecar?
|
|
|
|
| Approach | Pros | Cons |
|
|
|----------|------|------|
|
|
| **OAuth2-Proxy (chosen)** | Simple setup, no JupyterLab modification | Extra service to manage |
|
|
| **Native JupyterHub OAuth** | Integrated solution | More complex, overkill for single user |
|
|
| **Token-only auth** | Simplest | Less secure, no SSO integration |
|
|
|
|
### Casdoor Application Configuration
|
|
|
|
A JupyterLab application is defined in `ansible/casdoor/init_data.json.j2`:
|
|
|
|
| Setting | Value |
|
|
|---------|-------|
|
|
| **Name** | `app-jupyter` |
|
|
| **Client ID** | `vault_jupyter_oauth_client_id` |
|
|
| **Redirect URI** | `https://jupyter.ouranos.helu.ca/oauth2/callback` |
|
|
| **Grant Types** | `authorization_code`, `refresh_token` |
|
|
|
|
### URL Strategy
|
|
|
|
| URL Type | Address | Used By |
|
|
|----------|---------|---------|
|
|
| **OIDC Issuer** | `https://id.ouranos.helu.ca` | OAuth2-Proxy (external) |
|
|
| **Redirect URL** | `https://jupyter.ouranos.helu.ca/oauth2/callback` | Browser callback |
|
|
| **Upstream** | `http://127.0.0.1:22181` | OAuth2-Proxy → JupyterLab |
|
|
|
|
### Deployment Order
|
|
|
|
1. **Deploy Casdoor first** (if not already running):
|
|
```bash
|
|
ansible-playbook casdoor/deploy.yml
|
|
```
|
|
|
|
2. **Update HAProxy** (add jupyter backend):
|
|
```bash
|
|
ansible-playbook haproxy/deploy.yml
|
|
```
|
|
|
|
3. **Deploy JupyterLab**:
|
|
```bash
|
|
ansible-playbook jupyterlab/deploy.yml
|
|
```
|
|
|
|
4. **Update Alloy** (for log forwarding):
|
|
```bash
|
|
ansible-playbook alloy/deploy.yml
|
|
```
|
|
|
|
## Integration with Other Services
|
|
|
|
### HAProxy Routing
|
|
**Backend Configuration** (`titania.incus.yml`):
|
|
```yaml
|
|
- subdomain: "jupyter"
|
|
backend_host: "puck.incus"
|
|
backend_port: 22182 # OAuth2-Proxy port
|
|
health_path: "/ping"
|
|
timeout_server: 300s # WebSocket support
|
|
```
|
|
|
|
### Alloy Log Forwarding
|
|
**Syslog Configuration** (`puck/config.alloy.j2`):
|
|
```hcl
|
|
loki.source.syslog "jupyterlab_logs" {
|
|
listener {
|
|
address = "127.0.0.1:51491"
|
|
protocol = "tcp"
|
|
labels = {
|
|
job = "jupyterlab",
|
|
}
|
|
}
|
|
forward_to = [loki.write.default.receiver]
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Service Status
|
|
```bash
|
|
ssh puck.incus
|
|
sudo systemctl status jupyterlab
|
|
sudo systemctl status oauth2-proxy-jupyter
|
|
```
|
|
|
|
### View Logs
|
|
```bash
|
|
# JupyterLab logs
|
|
sudo journalctl -u jupyterlab -f
|
|
|
|
# OAuth2-Proxy logs
|
|
sudo journalctl -u oauth2-proxy-jupyter -f
|
|
```
|
|
|
|
### Test JupyterLab Directly (bypass OAuth)
|
|
```bash
|
|
# From puck container
|
|
curl http://127.0.0.1:22181/api/status
|
|
```
|
|
|
|
### Test OAuth2-Proxy Health
|
|
```bash
|
|
curl http://puck.incus:22182/ping
|
|
```
|
|
|
|
### Verify Virtual Environment
|
|
```bash
|
|
ssh puck.incus
|
|
sudo -u robert /home/robert/env/jupyter/bin/jupyter --version
|
|
```
|
|
|
|
### Common Issues
|
|
|
|
| Issue | Solution |
|
|
|-------|----------|
|
|
| WebSocket disconnects | Verify `timeout_server: 300s` in HAProxy backend |
|
|
| OAuth redirect loop | Check `redirect_url` matches Casdoor app config |
|
|
| 502 Bad Gateway | Ensure JupyterLab service is running on port 22181 |
|
|
| Cookie errors | Verify `cookie_secret` is exactly 32 characters |
|
|
|
|
## Version Information
|
|
|
|
- **Installation Method**: Python pip in virtual environment
|
|
- **JupyterLab Version**: Latest stable (pip managed)
|
|
- **OAuth2-Proxy Version**: 7.6.0 (binary from GitHub)
|
|
- **Update Process**: Re-run deployment playbook
|
|
|
|
## References
|
|
|
|
- **JupyterLab Documentation**: https://jupyterlab.readthedocs.io/
|
|
- **OAuth2-Proxy Documentation**: https://oauth2-proxy.github.io/oauth2-proxy/
|
|
- **Jupyter AI**: https://jupyter-ai.readthedocs.io/
|
|
- **Casdoor OIDC**: https://casdoor.org/docs/integration/oidc
|