Skip to content

Latest commit

History

44 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

AIS-XNAT Deployment for k3s

XNAT deployment on k3s with NFS-backed storage for the Australian Imaging Service.

Prerequisites

  • Ubuntu 20.04+ or similar Linux distribution
  • Minimum 4GB RAM, 2 CPU cores
  • 600GB+ storage for NFS server
  • Helm 3.x installed

Directory Structure

ais-devstack/
├── README.md # This file
├── manifests/
│ ├── pv.yaml # Persistent Volumes (NFS-backed)
│ ├── pvc.yaml # Persistent Volume Claims
│ ├── configmap.yaml # XNAT init script ConfigMap
│ ├── kustomization.yaml # Kustomize patches for StatefulSet
│ ├── kustomize.sh # Helm post-renderer script
│ └── values.yaml # XNAT Helm chart values (domain config)
├── nfs-server/
│ └── values.yaml # NFS server Helm chart values
├── plugins/
│ └── container-service-*.jar # XNAT plugins (auto-copied during install)
├── jupyterhub/ # JupyterHub layer (one neurodesk Helm chart)
│ ├── INSTALL.sh # JupyterHub orchestrator (infra + chart)
│ ├── UNINSTALL.sh # JupyterHub uninstall
│ ├── 6-install-neurodesk.sh # wrapper -> neurodesk/install.sh
│ ├── neurodesk/ # the consolidated chart + devstack values
│ └── ... # See jupyterhub/README.md
└── scripts/
├── install.sh # XNAT install (then prompts for JupyterHub)
└── uninstall.sh # XNAT uninstallation

Quick Start

Install

cd /home/ubuntu/ais-devstack
chmod +x scripts/*.sh manifests/kustomize.sh
./scripts/install.sh

Uninstall

The uninstall script automatically detects whether you're running MicroK8s or k3s:

./scripts/uninstall.sh

It will:

  • Remove XNAT and related resources
  • Optionally remove NFS server and CSI driver
  • Optionally remove the entire Kubernetes distribution (MicroK8s or k3s)

Manual Installation

If you prefer step-by-step installation:

Step 1: Install k3s

curl -sfL https://get.k3s.io | sh -
# Configure kubectl
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER:$USER~/.kube/config

Step 2: Install Helm (if not installed)

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Step 3: Install NGINX Ingress Controller

k3s comes with Traefik by default, but XNAT requires nginx-specific annotations for large file uploads:

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.publishService.enabled=true

Step 4: Install NFS CSI Driver

helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs \
--namespace kube-system \
--set kubeletDir=/var/lib/kubelet

Step 5: Deploy NFS Server

kubectl create namespace storage
# Deploy using the nfs-server helm chart
helm install nfs-server ./nfs-server \
--namespace storage \
--values nfs-server/values.yaml
# Wait for pod to be ready
kubectl -n storage get pods -w
# Create required directories
kubectl -n storage exec deploy/nfs-server -- mkdir -p \
/exports/gpfs /exports/xnat/data/build /exports/xnat/plugins

Step 6: Deploy XNAT

# Create namespace
kubectl create namespace ais-xnat
# Apply storage resources
kubectl apply -f manifests/pv.yaml
kubectl apply -f manifests/pvc.yaml
kubectl apply -f manifests/configmap.yaml
# Add AIS Helm repo
helm repo add ais https://australian-imaging-service.github.io/charts
helm repo update
# Install XNAT with kustomize patches
chmod +x manifests/kustomize.sh
helm install xnat-web ais/xnat \
--namespace ais-xnat \
--values manifests/values.yaml \
--post-renderer ./manifests/kustomize.sh

Step 7: Verify Installation

# Watch pods start up
kubectl -n ais-xnat get pods -w
# Port forward to access locally
kubectl -n ais-xnat port-forward svc/xnat-web 8080:80

Access XNAT at http://localhost:8080 (default: admin/admin)

Key Differences from MicroK8s

ComponentMicroK8sk3s
Kubelet path/var/snap/microk8s/common/var/lib/kubelet/var/lib/kubelet
Default storage classmicrok8s-hostpathlocal-path
Default ingressnginx (addon)Traefik (requires nginx install)
kubectlmicrok8s kubectlkubectl
helmmicrok8s helmhelm
Config locationSnap-based/etc/rancher/k3s/

Configuration

Ingress Host

Edit manifests/values.yaml to change the ingress hostname:

xnat-web:
ingress:
hosts:
- host: your-domain.example.com

OpenID Connect (OIDC) Authentication

Both XNAT and JupyterHub use OIDC for authentication. Choose one provider and configure both services to use it.

Supported Providers:

  • Google OIDC - Recommended for development and testing (easy setup, works worldwide)
  • AAF - Australian Access Federation (for Australian research institutions)

Important: Both XNAT and JupyterHub must use the same OIDC provider. Users need to exist in both systems with matching usernames.


Option A: Google OIDC Setup

Step 1: Create Google OAuth Credentials

  1. Go to Google Cloud Console

  2. Create a new project or select an existing one

  3. Navigate to APIs & Services > Credentials

  4. Click Create Credentials > OAuth client ID

  5. Select Web application

  6. Configure the OAuth client:

    FieldValue
    NameXNAT + JupyterHub
    Authorized JavaScript originshttps://your-domain.example.com
    Authorized redirect URIshttps://your-domain.example.com/openid-login
    https://your-domain.example.com/hub/oauth_callback
  7. Click Create and note down the Client ID and Client Secret

Step 2: Configure XNAT for Google

Update manifests/values.yaml:

xnat-web:
plugins:
openid-auth-plugin:
- name: "Google Authentication"provider:
id: googleenabled: "google"siteUrl: "https://your-domain.example.com"openid:
google:
clientId: "your-google-client-id.apps.googleusercontent.com"clientSecret: "your-google-client-secret"# Other settings already configured in template

Step 3: Configure JupyterHub for Google

Update jupyterhub/5-jupyterhub-values.yaml:

hub:
config:
GenericOAuthenticator:
client_id: "your-google-client-id.apps.googleusercontent.com"client_secret: "your-google-client-secret"oauth_callback_url: "https://your-domain.example.com/hub/oauth_callback"# Google URLs already configured in templateextraEnv:
OIDC_PROVIDER_PREFIX: "google"# Ensures username format matches XNAT

Option B: AAF Setup (Australian Access Federation)

Step 1: Register with AAF

  1. Go to AAF Service Manager (production) or Test AAF (testing)

  2. Register two separate services:

    ServiceCallback URL
    XNAThttps://your-domain.example.com/openid-login
    JupyterHubhttps://your-domain.example.com/hub/oauth_callback
  3. Note down the Client ID and Client Secret for each service

Step 2: Configure XNAT for AAF

Update manifests/values.yaml - comment out Google, uncomment AAF:

xnat-web:
plugins:
openid-auth-plugin:
- name: "AAF Authentication"provider:
id: aafenabled: "aaf"siteUrl: "https://your-domain.example.com"openid:
# google: ... (comment out)aaf:
accessTokenUri: https://central.aaf.edu.au/providers/op/tokenuserAuthUri: https://central.aaf.edu.au/providers/op/authorizeclientId: "your-aaf-xnat-client-id"clientSecret: "your-aaf-xnat-client-secret"scopes: "openid,profile,email"# Other settings already configured in template

Step 3: Configure JupyterHub for AAF

Update jupyterhub/5-jupyterhub-values.yaml - comment out Google, uncomment AAF:

hub:
config:
GenericOAuthenticator:
# Google settings (comment out)# client_id: ...# AAF settings (uncomment)client_id: "your-aaf-jupyterhub-client-id"client_secret: "your-aaf-jupyterhub-client-secret"oauth_callback_url: "https://your-domain.example.com/hub/oauth_callback"authorize_url: "https://central.aaf.edu.au/providers/op/authorize"token_url: "https://central.aaf.edu.au/providers/op/token"userdata_url: "https://central.aaf.edu.au/providers/op/userinfo"login_service: "AAF"scope: [openid, profile, email, eduperson_principal_name]extraEnv:
OIDC_PROVIDER_PREFIX: "aaf"# Ensures username format matches XNAT

Apply Configuration Changes

After updating the configuration files:

# Upgrade XNAT
helm upgrade xnat-web ais/xnat \
--namespace ais-xnat \
--values manifests/values.yaml \
--post-renderer ./manifests/kustomize.sh
# Upgrade JupyterHub
helm upgrade jupyterhub jupyterhub/jupyterhub -n jupyter \
--values jupyterhub/5-jupyterhub-values.yaml

Storage Size

Edit nfs-server/values.yaml:

persistence:
size: 600Gi # Adjust as needed

Troubleshooting

XNAT pod stuck in Init

kubectl -n ais-xnat describe pod xnat-web-0
kubectl -n ais-xnat logs xnat-web-0 -c home-init

NFS connection issues

# Check NFS server is running
kubectl -n storage get pods
kubectl -n storage logs deploy/nfs-server
# Check PV/PVC binding
kubectl get pv
kubectl -n ais-xnat get pvc

Database issues

kubectl -n ais-xnat logs xnat-web-0-postgresql-0

Plugins

Plugins in the plugins/ directory are automatically copied to the NFS server during installation.

Pre-installed plugins:

  • container-service-3.7.2-uq-fat.jar - Container service plugin

To add more plugins:

  1. Place JAR files in plugins/ before running install, OR
  2. Copy manually after installation:
# Copy plugin jar to NFS server
kubectl -n storage cp my-plugin.jar \
$(kubectl -n storage get pods -l app=nfs-server -o name | cut -d/ -f2):/exports/xnat/plugins/
# Restart XNAT to load new plugins
kubectl -n ais-xnat rollout restart statefulset xnat-web

JupyterHub Integration

JupyterHub provides interactive Jupyter notebooks integrated with XNAT. The jupyterhub/ directory contains the JupyterHub deployment as a git subtree from ais-jupyterhub.

Install JupyterHub

Option 1: During XNAT installation, answer "y" when prompted:

Install JupyterHub? (y/N): y

Option 2: Install separately after XNAT is running:

cd jupyterhub
cp neurodesk/values-devstack.yaml.template neurodesk/values-devstack.yaml # fill in secrets
./INSTALL.sh

The JupyterHub layer is the consolidated neurodesk Helm chart (see jupyterhub/neurodesk/README.md). INSTALL.sh installs the infra it needs (Longhorn, the NFS workspace, monitoring) and then the chart.

Uninstall JupyterHub

cd jupyterhub && ./UNINSTALL.sh # JupyterHub layer only (keeps infra + XNAT)

Update JupyterHub from Upstream

The jupyterhub/ directory is a git subtree. To pull updates from the upstream ais-jupyterhub repository:

git subtree pull --prefix=jupyterhub \
https://github.com/Australian-Imaging-Service/ais-jupyterhub.git \
Development_AB --squash

To push local changes back to upstream (if you have write access):

git subtree push --prefix=jupyterhub \
https://github.com/Australian-Imaging-Service/ais-jupyterhub.git \
Development_AB

JupyterHub Documentation

See the following files in jupyterhub/ for more details:

  • README.md - Architecture overview
  • XNAT-CONFIGURATION.md - XNAT plugin setup
  • TROUBLESHOOTING.md - Common issues and solutions

About

Local development stack setup and documentation for AIS

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages