diff --git a/.github/workflows/build_docker_images.yaml b/.github/workflows/build_docker_images.yaml
new file mode 100644
index 00000000..2c619119
--- /dev/null
+++ b/.github/workflows/build_docker_images.yaml
@@ -0,0 +1,59 @@
+name: Build containers for the Thicket Tutorial
+
+on:
+ pull_request: []
+ workflow_dispatch:
+
+jobs:
+ build-containers:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: true
+ matrix:
+ tutorial_name: ["radiuss-2024"]
+ registry_url_base: ["ghcr.io/llnl"]
+ container_info: [["thicket-tutorial-hub", "docker/Dockerfile.hub"],
+ ["thicket-tutorial-init", "docker/Dockerfile.init"],
+ ["thicket-tutorial-spawn", "docker/Dockerfile.spawn"]]
+
+ steps:
+ - name: Clone the thicket-tutorial repo
+ uses: actions/checkout@v4
+
+ - name: Clean unneeded stuff in runner to make space for the Docker image
+ uses: jlumbroso/free-disk-space@v1.3.1
+ with:
+ tool-cache: false
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: false
+ swap-storage: true
+
+ - name: GHCR Login
+ if: (github.event.name != 'pull_request')
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Pull existing layers
+ env:
+ container: ${{ matrix.registry_url_base }}/${{ matrix.container_info[0] }}:${{ matrix.tutorial_name }}
+ run: |
+ docker pull ${container} || echo "${container} has not been pushed yet"
+
+ - name: Build Container
+ env:
+ container: ${{ matrix.registry_url_base }}/${{ matrix.container_info[0] }}:${{ matrix.tutorial_name }}
+ dockerfile: ${{ matrix.container_info[1] }}
+ run: |
+ docker build -f ${dockerfile} -t ${container} .
+
+ - name: Deploy Container
+ if: (github.event_name != 'pull_request')
+ env:
+ container: ${{ matrix.registry_url_base }}/${{ matrix.container_info[0] }}:${{ matrix.tutorial_name }}
+ run: docker push ${container}
diff --git a/Dockerfile.local b/Dockerfile.local
deleted file mode 100644
index 08858e7f..00000000
--- a/Dockerfile.local
+++ /dev/null
@@ -1,55 +0,0 @@
-FROM continuumio/miniconda3:4.12.0
-
-USER root
-
-ENV NB_USER=jovyan \
- NB_UID=1000 \
- HOME=/home/jovyan
-
-RUN adduser \
- --disabled-password \
- --gecos "Default User" \
- --uid ${NB_UID} \
- --home ${HOME} \
- --force-badname \
- ${NB_USER}
-
-RUN apt-get update \
- && apt-get upgrade -y \
- && apt-get install -y --no-install-recommends \
- build-essential \
- git \
- && rm -rf /var/lib/apt/lists/*
-
-RUN conda install -y -c conda-forge \
- jupyter \
- jupyterlab \
- ipython
-
-COPY ./requirements.txt ./requirements.txt
-
-RUN python3 -m pip install -r requirements.txt \
- && python3 -m pip install papermill
-
-RUN chown -R ${NB_USER} "${HOME}"
-
-USER ${NB_USER}
-WORKDIR ${HOME}
-ENV PATH="${HOME}/.local/bin:${PATH}"
-
-COPY --chown=${NB_USER} ./notebooks/01_thicket_tutorial.ipynb ./notebooks/01_thicket_tutorial.ipynb
-COPY --chown=${NB_USER} ./notebooks/02_thicket_rajaperf_clustering.ipynb ./notebooks/02_thicket_rajaperf_clustering.ipynb
-COPY --chown=${NB_USER} ./notebooks/03_extrap-with-metadata-aggregated.ipynb ./notebooks/03_extrap-with-metadata-aggregated.ipynb
-COPY --chown=${NB_USER} ./notebooks/04_stats-functions.ipynb ./notebooks/04_stats-functions.ipynb
-COPY --chown=${NB_USER} ./notebooks/05_thicket_query_language.ipynb ./notebooks/05_thicket_query_language.ipynb
-COPY --chown=${NB_USER} ./data/ ./data/
-
-COPY ./docker_scripts/entrypoint.sh /entrypoint.sh
-COPY ./docker_scripts/start.sh /start.sh
-COPY ./docker_scripts/run_all.sh /run_all.sh
-
-ENV SHELL=/usr/bin/bash
-EXPOSE 8888
-ENTRYPOINT [ "tini", "--" ]
-
-CMD [ "jupyter", "notebook" ]
diff --git a/README.md b/README.md
index 24aeadc5..50881559 100644
--- a/README.md
+++ b/README.md
@@ -36,30 +36,30 @@ or by clicking the badge at the top of this file.
We provide a Dockerfile for users to run the notebooks locally. To run locally *and interactively*, you must first build the Docker container with:
```bash
-$ docker build -t thicket-tutorial -f Dockerfile .
+docker build -t thicket-tutorial -f docker/Dockerfile.spawn .
```
Then, you must create a Docker network with:
```bash
-$ docker network create jupyterhub
+docker network create jupyterhub
```
Finally, you can launch the tutorial. To launch the tutorial without preserving any changes, run:
```bash
-$ docker run --rm -it --entrypoint /start.sh -v /var/run/docker.sock:/var/run/docker.sock --net jupyterhub --name jupyterhub -p 8888:8888 thicket-tutorial
+docker run --rm -it --entrypoint /start.sh -v /var/run/docker.sock:/var/run/docker.sock --net jupyterhub --name jupyterhub -p 8888:8888 thicket-tutorial
```
If you would rather your changes be preserved, run:
```bash
-$ docker run --rm -it --entrypoint /start.sh -v /var/run/docker.sock:/var/run/docker.sock -v .:/home/jovyan --net jupyterhub --name jupyterhub -p 8888:8888 thicket-tutorial
+docker run --rm -it --entrypoint /start.sh -v /var/run/docker.sock:/var/run/docker.sock -v .:/home/jovyan --net jupyterhub --name jupyterhub -p 8888:8888 thicket-tutorial
```
Alternatively, if you want to run the notebooks automatically (i.e., non-interactive), you can simply run the `dev_scripts/autorun.sh` script. This script executes the same commands as above, but it uses the `run_all.sh` script as an entrypoint instead of `start.sh`.
-The Docker-based code for running this tutorial locally was derived from the material from the 2023 RADIUSS tutorial for Flux, which can be found here: https://github.com/flux-framework/Tutorials/tree/master/2023-RADIUSS-AWS/JupyterNotebook
+The Docker-based code for running this tutorial locally was derived from the material from the 2024 RADIUSS tutorial for Flux, which can be found here: https://github.com/flux-framework/Tutorials/tree/master/2024-RADIUSS-AWS/JupyterNotebook
#### Podman
@@ -68,33 +68,1001 @@ If you want to use podman instead of docker, you can replace "docker" with "podm
First initialize and start podman:
```bash
-$ podman machine init
-$ podman machine start
+podman machine init
+podman machine start
```
Then build the container:
```bash
-$ podman build -t thicket-tutorial -f Dockerfile .
+podman build -t thicket-tutorial -f Dockerfile .
```
Then create a network:
```bash
-$ podman network create jupyterhub
+podman network create jupyterhub
```
Then launch the tutorial:
```bash
-$ podman run --rm -it --entrypoint /start.sh -v /var/run/docker.sock:/var/run/docker.sock --net jupyterhub --name jupyterhub -p 8888:8888 thicket-tutorial
+podman run --rm -it --entrypoint /start.sh -v /var/run/docker.sock:/var/run/docker.sock --net jupyterhub --name jupyterhub -p 8888:8888 thicket-tutorial
```
Clean up after you are done:
```bash
-$ podman machine stop
+podman machine stop
```
+### Deploying the Tutorial with Kubernetes
+
+This tutorial borrows from the infrastructure created by [Vanessa Sochat](https://github.com/vsoch) and [Dan Milroy](https://github.com/milroy) for the [Flux Tutorial](https://github.com/flux-framework/Tutorials/tree/2024-radiuss-aws/2024-RADIUSS-AWS/JupyterNotebook). Thanks to this infrastructure, this tutorial can be deployed to Kubernetes using the following tools:
+* `kubectl`
+* `helm`
+* `eksctl` (for AWS)
+* AWS CLI (optional, but recommended)
+* `gcloud` (for Google Cloud)
+
+The following instructions describe how to deploy this tutorial to Kubernetes on either Google Cloud or AWS.
+
+#### 1. Create Cluster
+
+##### Google Cloud
+
+Here is how to create the cluster on Google Cloud using [gcloud](https://cloud.google.com/sdk/docs/install) (and assuming you have logged in
+with [gcloud auth login](https://cloud.google.com/sdk/gcloud/reference/auth/login):
+
+```bash
+export GOOGLE_PROJECT=myproject
+gcloud container clusters create thicket-tutorial-jupyter --project $GOOGLE_PROJECT \
+ --zone us-central1-a --machine-type n1-standard-2 \
+ --num-nodes=4 --enable-network-policy --enable-intra-node-visibility
+```
+
+##### AWS
+
+Here is how to create an equivalent cluster on AWS (EKS). We will be using [eksctl](https://eksctl.io/introduction/), which
+you should install.
+
+```bash
+# Create an EKS cluster with autoscaling with default storage
+eksctl create cluster --config-file aws/eksctl-config.yaml
+
+# Create an EKS cluster with io1 node storage but no autoscaling, used for the RADIUSS 2023 tutorial
+eksctl create cluster --config-file aws/eksctl-radiuss-2024.yaml
+```
+
+You can find vanilla (manual) instructions [here](https://z2jh.jupyter.org/en/stable/kubernetes/amazon/step-zero-aws-eks.html) if you
+are interested in how it works. We emulate the logic there using eksctl. Then generate a secret token - we will add this to [config-aws.yaml](aws/config-aws.yaml) (without SSL) or [config-aws-ssl.yaml](aws/config-aws-ssl.yaml) (with SSL). When your cluster is ready, this will deploy an EBS CSI driver:
+
+```bash
+kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/?ref=master"
+```
+
+And install the cluster-autoscaler:
+
+```bash
+kubectl apply -f aws/cluster-autoscaler-autodiscover.yaml
+```
+
+If you want to use a different storage class than the default (`gp2`), you also need to create the new storage class (`gp3` here) and set it as the default storage class:
+
+```bash
+kubectl apply -f aws/storageclass.yaml
+kubectl patch storageclass gp3 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
+kubectl patch storageclass gp2 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
+```
+
+Most of the information I needed to read about this was [here](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md) - the Jupyter documentation wasn't super helpful beyond saying to install it. Also note that I got this (seemingly working) without the `propagateASGTags` set to true, but that is something that I've seen have issue.
+You can look at the autoscaler pod logs for information.
+
+While the spawned containers (e.g., where you run your notebook) don't use these volumes, the hub will.
+You can read about [gp2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html) class.
+Note that we will be using [config-aws.yaml](aws/config-aws.yaml) if you don't need SSL, and [config-aws-ssl.yaml](aws/config-aws-ssl.yaml) if you do. For the latter, the jupyter spawner will generate let's encrypt certificates for us, given that we have correctly configured DNS.
+
+#### 2. Deploy JupyterHub
+
+We will use [helm](https://helm.sh/docs/helm/helm_install/) to install charts and deploy.
+
+```bash
+helm repo add jupyterhub https://hub.jupyter.org/helm-chart/
+helm repo update
+```
+
+You can see the versions available:
+
+```bash
+helm search repo jupyterhub
+```
+```console
+NAME CHART VERSION APP VERSION DESCRIPTION
+bitnami/jupyterhub 4.2.0 4.0.2 JupyterHub brings the power of notebooks to gro...
+jupyterhub/jupyterhub 3.0.2 4.0.2 Multi-user Jupyter installation
+jupyterhub/pebble 1.0.1 v2.3.1 This Helm chart bootstraps Pebble: an ACME serv...
+```
+
+Note that chart versions don't always coincide with software (or "app") versions. At the time of writing this,
+we are using the jupyterhub/jupyterhub 3.0.2/4.0.2 versions, and our container bases point to 3.0.2 tags for the
+corresponding images. Next, see the values we can set, which likely will come from a config*.yaml that we will choose.
+
+```bash
+helm show values jupyterhub/jupyterhub
+```
+
+
+
+Example values for the jupyterhub helm chart
+
+```console
+# fullnameOverride and nameOverride distinguishes blank strings, null values,
+# and non-blank strings. For more details, see the configuration reference.
+fullnameOverride: ""
+nameOverride:
+
+# enabled is ignored by the jupyterhub chart itself, but a chart depending on
+# the jupyterhub chart conditionally can make use this config option as the
+# condition.
+enabled:
+
+# custom can contain anything you want to pass to the hub pod, as all passed
+# Helm template values will be made available there.
+custom: {}
+
+# imagePullSecret is configuration to create a k8s Secret that Helm chart's pods
+# can get credentials from to pull their images.
+imagePullSecret:
+ create: false
+ automaticReferenceInjection: true
+ registry:
+ username:
+ password:
+ email:
+# imagePullSecrets is configuration to reference the k8s Secret resources the
+# Helm chart's pods can get credentials from to pull their images.
+imagePullSecrets: []
+
+# hub relates to the hub pod, responsible for running JupyterHub, its configured
+# Authenticator class KubeSpawner, and its configured Proxy class
+# ConfigurableHTTPProxy. KubeSpawner creates the user pods, and
+# ConfigurableHTTPProxy speaks with the actual ConfigurableHTTPProxy server in
+# the proxy pod.
+hub:
+ revisionHistoryLimit:
+ config:
+ JupyterHub:
+ admin_access: true
+ authenticator_class: dummy
+ service:
+ type: ClusterIP
+ annotations: {}
+ ports:
+ nodePort:
+ extraPorts: []
+ loadBalancerIP:
+ baseUrl: /
+ cookieSecret:
+ initContainers: []
+ nodeSelector: {}
+ tolerations: []
+ concurrentSpawnLimit: 64
+ consecutiveFailureLimit: 5
+ activeServerLimit:
+ deploymentStrategy:
+ ## type: Recreate
+ ## - sqlite-pvc backed hubs require the Recreate deployment strategy as a
+ ## typical PVC storage can only be bound to one pod at the time.
+ ## - JupyterHub isn't designed to support being run in parallel. More work
+ ## needs to be done in JupyterHub itself for a fully highly available (HA)
+ ## deployment of JupyterHub on k8s is to be possible.
+ type: Recreate
+ db:
+ type: sqlite-pvc
+ upgrade:
+ pvc:
+ annotations: {}
+ selector: {}
+ accessModes:
+ - ReadWriteOnce
+ storage: 1Gi
+ subPath:
+ storageClassName:
+ url:
+ password:
+ labels: {}
+ annotations: {}
+ command: []
+ args: []
+ extraConfig: {}
+ extraFiles: {}
+ extraEnv: {}
+ extraContainers: []
+ extraVolumes: []
+ extraVolumeMounts: []
+ image:
+ name: jupyterhub/k8s-hub
+ tag: "3.0.2"
+ pullPolicy:
+ pullSecrets: []
+ resources: {}
+ podSecurityContext:
+ fsGroup: 1000
+ containerSecurityContext:
+ runAsUser: 1000
+ runAsGroup: 1000
+ allowPrivilegeEscalation: false
+ lifecycle: {}
+ loadRoles: {}
+ services: {}
+ pdb:
+ enabled: false
+ maxUnavailable:
+ minAvailable: 1
+ networkPolicy:
+ enabled: true
+ ingress: []
+ egress: []
+ egressAllowRules:
+ cloudMetadataServer: true
+ dnsPortsCloudMetadataServer: true
+ dnsPortsKubeSystemNamespace: true
+ dnsPortsPrivateIPs: true
+ nonPrivateIPs: true
+ privateIPs: true
+ interNamespaceAccessLabels: ignore
+ allowedIngressPorts: []
+ allowNamedServers: false
+ namedServerLimitPerUser:
+ authenticatePrometheus:
+ redirectToServer:
+ shutdownOnLogout:
+ templatePaths: []
+ templateVars: {}
+ livenessProbe:
+ # The livenessProbe's aim to give JupyterHub sufficient time to startup but
+ # be able to restart if it becomes unresponsive for ~5 min.
+ enabled: true
+ initialDelaySeconds: 300
+ periodSeconds: 10
+ failureThreshold: 30
+ timeoutSeconds: 3
+ readinessProbe:
+ # The readinessProbe's aim is to provide a successful startup indication,
+ # but following that never become unready before its livenessProbe fail and
+ # restarts it if needed. To become unready following startup serves no
+ # purpose as there are no other pod to fallback to in our non-HA deployment.
+ enabled: true
+ initialDelaySeconds: 0
+ periodSeconds: 2
+ failureThreshold: 1000
+ timeoutSeconds: 1
+ existingSecret:
+ serviceAccount:
+ create: true
+ name:
+ annotations: {}
+ extraPodSpec: {}
+
+rbac:
+ create: true
+
+# proxy relates to the proxy pod, the proxy-public service, and the autohttps
+# pod and proxy-http service.
+proxy:
+ secretToken:
+ annotations: {}
+ deploymentStrategy:
+ ## type: Recreate
+ ## - JupyterHub's interaction with the CHP proxy becomes a lot more robust
+ ## with this configuration. To understand this, consider that JupyterHub
+ ## during startup will interact a lot with the k8s service to reach a
+ ## ready proxy pod. If the hub pod during a helm upgrade is restarting
+ ## directly while the proxy pod is making a rolling upgrade, the hub pod
+ ## could end up running a sequence of interactions with the old proxy pod
+ ## and finishing up the sequence of interactions with the new proxy pod.
+ ## As CHP proxy pods carry individual state this is very error prone. One
+ ## outcome when not using Recreate as a strategy has been that user pods
+ ## have been deleted by the hub pod because it considered them unreachable
+ ## as it only configured the old proxy pod but not the new before trying
+ ## to reach them.
+ type: Recreate
+ ## rollingUpdate:
+ ## - WARNING:
+ ## This is required to be set explicitly blank! Without it being
+ ## explicitly blank, k8s will let eventual old values under rollingUpdate
+ ## remain and then the Deployment becomes invalid and a helm upgrade would
+ ## fail with an error like this:
+ ##
+ ## UPGRADE FAILED
+ ## Error: Deployment.apps "proxy" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'
+ ## Error: UPGRADE FAILED: Deployment.apps "proxy" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'
+ rollingUpdate:
+ # service relates to the proxy-public service
+ service:
+ type: LoadBalancer
+ labels: {}
+ annotations: {}
+ nodePorts:
+ http:
+ https:
+ disableHttpPort: false
+ extraPorts: []
+ loadBalancerIP:
+ loadBalancerSourceRanges: []
+ # chp relates to the proxy pod, which is responsible for routing traffic based
+ # on dynamic configuration sent from JupyterHub to CHP's REST API.
+ chp:
+ revisionHistoryLimit:
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ image:
+ name: jupyterhub/configurable-http-proxy
+ # tag is automatically bumped to new patch versions by the
+ # watch-dependencies.yaml workflow.
+ #
+ tag: "4.5.6" # https://github.com/jupyterhub/configurable-http-proxy/tags
+ pullPolicy:
+ pullSecrets: []
+ extraCommandLineFlags: []
+ livenessProbe:
+ enabled: true
+ initialDelaySeconds: 60
+ periodSeconds: 10
+ failureThreshold: 30
+ timeoutSeconds: 3
+ readinessProbe:
+ enabled: true
+ initialDelaySeconds: 0
+ periodSeconds: 2
+ failureThreshold: 1000
+ timeoutSeconds: 1
+ resources: {}
+ defaultTarget:
+ errorTarget:
+ extraEnv: {}
+ nodeSelector: {}
+ tolerations: []
+ networkPolicy:
+ enabled: true
+ ingress: []
+ egress: []
+ egressAllowRules:
+ cloudMetadataServer: true
+ dnsPortsCloudMetadataServer: true
+ dnsPortsKubeSystemNamespace: true
+ dnsPortsPrivateIPs: true
+ nonPrivateIPs: true
+ privateIPs: true
+ interNamespaceAccessLabels: ignore
+ allowedIngressPorts: [http, https]
+ pdb:
+ enabled: false
+ maxUnavailable:
+ minAvailable: 1
+ extraPodSpec: {}
+ # traefik relates to the autohttps pod, which is responsible for TLS
+ # termination when proxy.https.type=letsencrypt.
+ traefik:
+ revisionHistoryLimit:
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ image:
+ name: traefik
+ # tag is automatically bumped to new patch versions by the
+ # watch-dependencies.yaml workflow.
+ #
+ tag: "v2.10.4" # ref: https://hub.docker.com/_/traefik?tab=tags
+ pullPolicy:
+ pullSecrets: []
+ hsts:
+ includeSubdomains: false
+ preload: false
+ maxAge: 15724800 # About 6 months
+ resources: {}
+ labels: {}
+ extraInitContainers: []
+ extraEnv: {}
+ extraVolumes: []
+ extraVolumeMounts: []
+ extraStaticConfig: {}
+ extraDynamicConfig: {}
+ nodeSelector: {}
+ tolerations: []
+ extraPorts: []
+ networkPolicy:
+ enabled: true
+ ingress: []
+ egress: []
+ egressAllowRules:
+ cloudMetadataServer: true
+ dnsPortsCloudMetadataServer: true
+ dnsPortsKubeSystemNamespace: true
+ dnsPortsPrivateIPs: true
+ nonPrivateIPs: true
+ privateIPs: true
+ interNamespaceAccessLabels: ignore
+ allowedIngressPorts: [http, https]
+ pdb:
+ enabled: false
+ maxUnavailable:
+ minAvailable: 1
+ serviceAccount:
+ create: true
+ name:
+ annotations: {}
+ extraPodSpec: {}
+ secretSync:
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ image:
+ name: jupyterhub/k8s-secret-sync
+ tag: "3.0.2"
+ pullPolicy:
+ pullSecrets: []
+ resources: {}
+ labels: {}
+ https:
+ enabled: false
+ type: letsencrypt
+ #type: letsencrypt, manual, offload, secret
+ letsencrypt:
+ contactEmail:
+ # Specify custom server here (https://acme-staging-v02.api.letsencrypt.org/directory) to hit staging LE
+ acmeServer: https://acme-v02.api.letsencrypt.org/directory
+ manual:
+ key:
+ cert:
+ secret:
+ name:
+ key: tls.key
+ crt: tls.crt
+ hosts: []
+
+# singleuser relates to the configuration of KubeSpawner which runs in the hub
+# pod, and its spawning of user pods such as jupyter-myusername.
+singleuser:
+ podNameTemplate:
+ extraTolerations: []
+ nodeSelector: {}
+ extraNodeAffinity:
+ required: []
+ preferred: []
+ extraPodAffinity:
+ required: []
+ preferred: []
+ extraPodAntiAffinity:
+ required: []
+ preferred: []
+ networkTools:
+ image:
+ name: jupyterhub/k8s-network-tools
+ tag: "3.0.2"
+ pullPolicy:
+ pullSecrets: []
+ resources: {}
+ cloudMetadata:
+ # block set to true will append a privileged initContainer using the
+ # iptables to block the sensitive metadata server at the provided ip.
+ blockWithIptables: true
+ ip: 169.254.169.254
+ networkPolicy:
+ enabled: true
+ ingress: []
+ egress: []
+ egressAllowRules:
+ cloudMetadataServer: false
+ dnsPortsCloudMetadataServer: true
+ dnsPortsKubeSystemNamespace: true
+ dnsPortsPrivateIPs: true
+ nonPrivateIPs: true
+ privateIPs: false
+ interNamespaceAccessLabels: ignore
+ allowedIngressPorts: []
+ events: true
+ extraAnnotations: {}
+ extraLabels:
+ hub.jupyter.org/network-access-hub: "true"
+ extraFiles: {}
+ extraEnv: {}
+ lifecycleHooks: {}
+ initContainers: []
+ extraContainers: []
+ allowPrivilegeEscalation: false
+ uid: 1000
+ fsGid: 100
+ serviceAccountName:
+ storage:
+ type: dynamic
+ extraLabels: {}
+ extraVolumes: []
+ extraVolumeMounts: []
+ static:
+ pvcName:
+ subPath: "{username}"
+ capacity: 10Gi
+ homeMountPath: /home/jovyan
+ dynamic:
+ storageClass:
+ pvcNameTemplate: claim-{username}{servername}
+ volumeNameTemplate: volume-{username}{servername}
+ storageAccessModes: [ReadWriteOnce]
+ image:
+ name: jupyterhub/k8s-singleuser-sample
+ tag: "3.0.2"
+ pullPolicy:
+ pullSecrets: []
+ startTimeout: 300
+ cpu:
+ limit:
+ guarantee:
+ memory:
+ limit:
+ guarantee: 1G
+ extraResource:
+ limits: {}
+ guarantees: {}
+ cmd: jupyterhub-singleuser
+ defaultUrl:
+ extraPodConfig: {}
+ profileList: []
+
+# scheduling relates to the user-scheduler pods and user-placeholder pods.
+scheduling:
+ userScheduler:
+ enabled: true
+ revisionHistoryLimit:
+ replicas: 2
+ logLevel: 4
+ # plugins are configured on the user-scheduler to make us score how we
+ # schedule user pods in a way to help us schedule on the most busy node. By
+ # doing this, we help scale down more effectively. It isn't obvious how to
+ # enable/disable scoring plugins, and configure them, to accomplish this.
+ #
+ # plugins ref: https://kubernetes.io/docs/reference/scheduling/config/#scheduling-plugins-1
+ # migration ref: https://kubernetes.io/docs/reference/scheduling/config/#scheduler-configuration-migrations
+ #
+ plugins:
+ score:
+ # These scoring plugins are enabled by default according to
+ # https://kubernetes.io/docs/reference/scheduling/config/#scheduling-plugins
+ # 2022-02-22.
+ #
+ # Enabled with high priority:
+ # - NodeAffinity
+ # - InterPodAffinity
+ # - NodeResourcesFit
+ # - ImageLocality
+ # Remains enabled with low default priority:
+ # - TaintToleration
+ # - PodTopologySpread
+ # - VolumeBinding
+ # Disabled for scoring:
+ # - NodeResourcesBalancedAllocation
+ #
+ disabled:
+ # We disable these plugins (with regards to scoring) to not interfere
+ # or complicate our use of NodeResourcesFit.
+ - name: NodeResourcesBalancedAllocation
+ # Disable plugins to be allowed to enable them again with a different
+ # weight and avoid an error.
+ - name: NodeAffinity
+ - name: InterPodAffinity
+ - name: NodeResourcesFit
+ - name: ImageLocality
+ enabled:
+ - name: NodeAffinity
+ weight: 14631
+ - name: InterPodAffinity
+ weight: 1331
+ - name: NodeResourcesFit
+ weight: 121
+ - name: ImageLocality
+ weight: 11
+ pluginConfig:
+ # Here we declare that we should optimize pods to fit based on a
+ # MostAllocated strategy instead of the default LeastAllocated.
+ - name: NodeResourcesFit
+ args:
+ scoringStrategy:
+ resources:
+ - name: cpu
+ weight: 1
+ - name: memory
+ weight: 1
+ type: MostAllocated
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ image:
+ # IMPORTANT: Bumping the minor version of this binary should go hand in
+ # hand with an inspection of the user-scheduelrs RBAC resources
+ # that we have forked in
+ # templates/scheduling/user-scheduler/rbac.yaml.
+ #
+ # Debugging advice:
+ #
+ # - Is configuration of kube-scheduler broken in
+ # templates/scheduling/user-scheduler/configmap.yaml?
+ #
+ # - Is the kube-scheduler binary's compatibility to work
+ # against a k8s api-server that is too new or too old?
+ #
+ # - You can update the GitHub workflow that runs tests to
+ # include "deploy/user-scheduler" in the k8s namespace report
+ # and reduce the user-scheduler deployments replicas to 1 in
+ # dev-config.yaml to get relevant logs from the user-scheduler
+ # pods. Inspect the "Kubernetes namespace report" action!
+ #
+ # - Typical failures are that kube-scheduler fails to search for
+ # resources via its "informers", and won't start trying to
+ # schedule pods before they succeed which may require
+ # additional RBAC permissions or that the k8s api-server is
+ # aware of the resources.
+ #
+ # - If "successfully acquired lease" can be seen in the logs, it
+ # is a good sign kube-scheduler is ready to schedule pods.
+ #
+ name: registry.k8s.io/kube-scheduler
+ # tag is automatically bumped to new patch versions by the
+ # watch-dependencies.yaml workflow. The minor version is pinned in the
+ # workflow, and should be updated there if a minor version bump is done
+ # here. We aim to stay around 1 minor version behind the latest k8s
+ # version.
+ #
+ tag: "v1.26.7" # ref: https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG
+ pullPolicy:
+ pullSecrets: []
+ nodeSelector: {}
+ tolerations: []
+ labels: {}
+ annotations: {}
+ pdb:
+ enabled: true
+ maxUnavailable: 1
+ minAvailable:
+ resources: {}
+ serviceAccount:
+ create: true
+ name:
+ annotations: {}
+ extraPodSpec: {}
+ podPriority:
+ enabled: false
+ globalDefault: false
+ defaultPriority: 0
+ imagePullerPriority: -5
+ userPlaceholderPriority: -10
+ userPlaceholder:
+ enabled: true
+ image:
+ name: registry.k8s.io/pause
+ # tag is automatically bumped to new patch versions by the
+ # watch-dependencies.yaml workflow.
+ #
+ # If you update this, also update prePuller.pause.image.tag
+ #
+ tag: "3.9"
+ pullPolicy:
+ pullSecrets: []
+ revisionHistoryLimit:
+ replicas: 0
+ labels: {}
+ annotations: {}
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ resources: {}
+ corePods:
+ tolerations:
+ - key: hub.jupyter.org/dedicated
+ operator: Equal
+ value: core
+ effect: NoSchedule
+ - key: hub.jupyter.org_dedicated
+ operator: Equal
+ value: core
+ effect: NoSchedule
+ nodeAffinity:
+ matchNodePurpose: prefer
+ userPods:
+ tolerations:
+ - key: hub.jupyter.org/dedicated
+ operator: Equal
+ value: user
+ effect: NoSchedule
+ - key: hub.jupyter.org_dedicated
+ operator: Equal
+ value: user
+ effect: NoSchedule
+ nodeAffinity:
+ matchNodePurpose: prefer
+
+# prePuller relates to the hook|continuous-image-puller DaemonsSets
+prePuller:
+ revisionHistoryLimit:
+ labels: {}
+ annotations: {}
+ resources: {}
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ extraTolerations: []
+ # hook relates to the hook-image-awaiter Job and hook-image-puller DaemonSet
+ hook:
+ enabled: true
+ pullOnlyOnChanges: true
+ # image and the configuration below relates to the hook-image-awaiter Job
+ image:
+ name: jupyterhub/k8s-image-awaiter
+ tag: "3.0.2"
+ pullPolicy:
+ pullSecrets: []
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ podSchedulingWaitDuration: 10
+ nodeSelector: {}
+ tolerations: []
+ resources: {}
+ serviceAccount:
+ create: true
+ name:
+ annotations: {}
+ continuous:
+ enabled: true
+ pullProfileListImages: true
+ extraImages: {}
+ pause:
+ containerSecurityContext:
+ runAsUser: 65534 # nobody user
+ runAsGroup: 65534 # nobody group
+ allowPrivilegeEscalation: false
+ image:
+ name: registry.k8s.io/pause
+ # tag is automatically bumped to new patch versions by the
+ # watch-dependencies.yaml workflow.
+ #
+ # If you update this, also update scheduling.userPlaceholder.image.tag
+ #
+ tag: "3.9"
+ pullPolicy:
+ pullSecrets: []
+
+ingress:
+ enabled: false
+ annotations: {}
+ ingressClassName:
+ hosts: []
+ pathSuffix:
+ pathType: Prefix
+ tls: []
+
+# cull relates to the jupyterhub-idle-culler service, responsible for evicting
+# inactive singleuser pods.
+#
+# The configuration below, except for enabled, corresponds to command-line flags
+# for jupyterhub-idle-culler as documented here:
+# https://github.com/jupyterhub/jupyterhub-idle-culler#as-a-standalone-script
+#
+cull:
+ enabled: true
+ users: false # --cull-users
+ adminUsers: true # --cull-admin-users
+ removeNamedServers: false # --remove-named-servers
+ timeout: 3600 # --timeout
+ every: 600 # --cull-every
+ concurrency: 10 # --concurrency
+ maxAge: 0 # --max-age
+
+debug:
+ enabled: false
+
+global:
+ safeToShowValues: false
+```
+
+
+
+##### Changes You Might Need to Make:
+
+- Change the config*.yaml image-> name and tag that you deploy to use your images.
+- You might want to change the number of user placeholder pods
+- Also change the hub->concurrentSpawnLimit
+- Change the password, ssl secret, and domain name if applicable
+- Change the aws/eksctl-config.yaml autoscaling ranges depending on your needs.
+- Remove pullPolicy Always if you don't expect to want to update/re-pull an image every time (ideal for production)
+
+And here is how to deploy, assuming the default namespace. Please choose your cloud appropriately!
+
+```bash
+# This is for Google Cloud
+helm install thicket-tutorial-jupyter jupyterhub/jupyterhub --values gcp/config.yaml
+
+# This is for Amazon EKS without SSL
+helm install thicket-tutorial-jupyter jupyterhub/jupyterhub --values aws/config-aws.yaml
+```
+
+If you mess something up, you can change the file and run `helm upgrade`:
+
+```bash
+helm upgrade thicket-tutorial-jupyter jupyterhub/jupyterhub --values aws/config-aws.yaml
+```
+
+If you REALLY mess something up, you can tear the whole thing down and then install again:
+
+```bash
+helm uninstall thicket-tutorial-jupyter
+```
+
+Note that in practice of bringing this up and down many times, we have seen the proxy-public
+not create a handful of times. If this happens, just tear down everything, wait for all pods
+to terminate, and then start freshly. When you run a command, also note that the terminal will hang!
+You can see progress in another terminal:
+
+```bash
+kubectl get pods
+```
+
+or try watching:
+
+```bash
+kubectl get pods --watch
+```
+
+When it's done, you should see:
+
+```bash
+kubectl get pods
+
+NAME READY STATUS RESTARTS AGE
+continuous-image-puller-nvr4g 1/1 Running 0 5m31s
+hub-7d59dfb748-mrfdv 1/1 Running 0 5m31s
+proxy-d9dfbf77b-v488t 1/1 Running 0 5m31s
+user-scheduler-587fcc5479-c4mmk 1/1 Running 0 5m31s
+user-scheduler-587fcc5479-x6jmk 1/1 Running 0 5m31s
+```
+
+(The numbers of each above might vary based on the size of your cluster). And the terminal provides a lot of useful output:
+
+
+
+Output of Terminal on Completed Install
+
+```console
+NAME: thicket-tutorial-jupyter
+LAST DEPLOYED: Sun Aug 27 15:00:15 2023
+NAMESPACE: default
+STATUS: deployed
+REVISION: 1
+TEST SUITE: None
+NOTES:
+. __ __ __ __ __
+ / / __ __ ____ __ __ / /_ ___ _____ / / / / __ __ / /_
+ __ / / / / / / / __ \ / / / / / __/ / _ \ / ___/ / /_/ / / / / / / __ \
+/ /_/ / / /_/ / / /_/ / / /_/ / / /_ / __/ / / / __ / / /_/ / / /_/ /
+\____/ \__,_/ / .___/ \__, / \__/ \___/ /_/ /_/ /_/ \__,_/ /_.___/
+ /_/ /____/
+
+ You have successfully installed the official JupyterHub Helm chart!
+
+### Installation info
+
+ - Kubernetes namespace: default
+ - Helm release name: thicket-tutorial-jupyter
+ - Helm chart version: 3.0.2
+ - JupyterHub version: 4.0.2
+ - Hub pod packages: See https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/3.0.2/images/hub/requirements.txt
+
+### Followup links
+
+ - Documentation: https://z2jh.jupyter.org
+ - Help forum: https://discourse.jupyter.org
+ - Social chat: https://gitter.im/jupyterhub/jupyterhub
+ - Issue tracking: https://github.com/jupyterhub/zero-to-jupyterhub-k8s/issues
+
+### Post-installation checklist
+
+ - Verify that created Pods enter a Running state:
+
+ kubectl --namespace=default get pod
+
+ If a pod is stuck with a Pending or ContainerCreating status, diagnose with:
+
+ kubectl --namespace=default describe pod
+
+ If a pod keeps restarting, diagnose with:
+
+ kubectl --namespace=default logs --previous
+
+ - Verify an external IP is provided for the k8s Service proxy-public.
+
+ kubectl --namespace=default get service proxy-public
+
+ If the external ip remains , diagnose with:
+
+ kubectl --namespace=default describe service proxy-public
+
+ - Verify web based access:
+
+ You have not configured a k8s Ingress resource so you need to access the k8s
+ Service proxy-public directly.
+
+ If your computer is outside the k8s cluster, you can port-forward traffic to
+ the k8s Service proxy-public with kubectl to access it from your
+ computer.
+
+ kubectl --namespace=default port-forward service/proxy-public 8080:http
+
+ Try insecure HTTP access: http://localhost:8080
+```
+
+
+
+#### 3. Get Public Proxy
+
+Then to find the public proxy:
+
+```bash
+kubectl get service proxy-public
+```
+```console
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+proxy-public LoadBalancer 10.96.179.168 80:32530/TCP 7m22s
+```
+or:
+
+```bash
+kubectl get service proxy-public --output jsonpath='{.status.loadBalancer.ingress[].ip}'
+```
+
+Note that for Google, it looks like an ip address. For aws you get a string monster!
+
+```console
+a054af2758c1549f780a433e5515a9d4-1012389935.us-east-2.elb.amazonaws.com
+```
+
+This might take a minute to fully be there - if it doesn't work immediately give it that.
+At this point, you should be able to login as any user, open the notebook (nested two levels)
+and interact with Flux! Remember that if you don't see the service, try deleting everything and
+starting fresh. If that doesn't work, there might be some new error we didn't anticipate,
+and you can look at logs.
+
+#### Clean up
+
+For both:
+
+```bash
+helm uninstall thicket-tutorial-jupyter
+```
+
+For Google Cloud:
+
+```bash
+gcloud container clusters delete thicket-tutorial-jupyter
+```
+
+For AWS:
+
+```bash
+# If you don't do this first, it will tell the pods are un-evictable and loop forever
+$ kubectl delete pod --all-namespaces --all --force
+# Then delete the cluster
+$ eksctl delete cluster --config-file aws/eksctl-config.yaml --wait
+```
+
+In practice, you'll need to start deleting with `eksctl` and then you will see the pod eviction warning
+(because they were re-created) and you'll need to run the command again, and then it will clean up.
+
### License
This repository is distributed under the terms of the MIT license.
diff --git a/aws/cluster-autoscaler-autodiscover.yaml b/aws/cluster-autoscaler-autodiscover.yaml
new file mode 100644
index 00000000..27576f65
--- /dev/null
+++ b/aws/cluster-autoscaler-autodiscover.yaml
@@ -0,0 +1,180 @@
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ labels:
+ k8s-addon: cluster-autoscaler.addons.k8s.io
+ k8s-app: cluster-autoscaler
+ name: cluster-autoscaler
+ namespace: kube-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: cluster-autoscaler
+ labels:
+ k8s-addon: cluster-autoscaler.addons.k8s.io
+ k8s-app: cluster-autoscaler
+rules:
+ - apiGroups: [""]
+ resources: ["events", "endpoints"]
+ verbs: ["create", "patch"]
+ - apiGroups: [""]
+ resources: ["pods/eviction"]
+ verbs: ["create"]
+ - apiGroups: [""]
+ resources: ["pods/status"]
+ verbs: ["update"]
+ - apiGroups: [""]
+ resources: ["endpoints"]
+ resourceNames: ["cluster-autoscaler"]
+ verbs: ["get", "update"]
+ - apiGroups: [""]
+ resources: ["nodes"]
+ verbs: ["watch", "list", "get", "update"]
+ - apiGroups: [""]
+ resources:
+ - "namespaces"
+ - "pods"
+ - "services"
+ - "replicationcontrollers"
+ - "persistentvolumeclaims"
+ - "persistentvolumes"
+ verbs: ["watch", "list", "get"]
+ - apiGroups: ["extensions"]
+ resources: ["replicasets", "daemonsets"]
+ verbs: ["watch", "list", "get"]
+ - apiGroups: ["policy"]
+ resources: ["poddisruptionbudgets"]
+ verbs: ["watch", "list"]
+ - apiGroups: ["apps"]
+ resources: ["statefulsets", "replicasets", "daemonsets"]
+ verbs: ["watch", "list", "get"]
+ - apiGroups: ["storage.k8s.io"]
+ resources: ["storageclasses", "csinodes", "csidrivers", "csistoragecapacities"]
+ verbs: ["watch", "list", "get"]
+ - apiGroups: ["batch", "extensions"]
+ resources: ["jobs"]
+ verbs: ["get", "list", "watch", "patch"]
+ - apiGroups: ["coordination.k8s.io"]
+ resources: ["leases"]
+ verbs: ["create"]
+ - apiGroups: ["coordination.k8s.io"]
+ resourceNames: ["cluster-autoscaler"]
+ resources: ["leases"]
+ verbs: ["get", "update"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: cluster-autoscaler
+ namespace: kube-system
+ labels:
+ k8s-addon: cluster-autoscaler.addons.k8s.io
+ k8s-app: cluster-autoscaler
+rules:
+ - apiGroups: [""]
+ resources: ["configmaps"]
+ verbs: ["create", "list", "watch"]
+ - apiGroups: [""]
+ resources: ["configmaps"]
+ resourceNames: ["cluster-autoscaler-status", "cluster-autoscaler-priority-expander"]
+ verbs: ["delete", "get", "update", "watch"]
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: cluster-autoscaler
+ labels:
+ k8s-addon: cluster-autoscaler.addons.k8s.io
+ k8s-app: cluster-autoscaler
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: cluster-autoscaler
+subjects:
+ - kind: ServiceAccount
+ name: cluster-autoscaler
+ namespace: kube-system
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: cluster-autoscaler
+ namespace: kube-system
+ labels:
+ k8s-addon: cluster-autoscaler.addons.k8s.io
+ k8s-app: cluster-autoscaler
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: cluster-autoscaler
+subjects:
+ - kind: ServiceAccount
+ name: cluster-autoscaler
+ namespace: kube-system
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: cluster-autoscaler
+ namespace: kube-system
+ labels:
+ app: cluster-autoscaler
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: cluster-autoscaler
+ template:
+ metadata:
+ labels:
+ app: cluster-autoscaler
+ annotations:
+ prometheus.io/scrape: 'true'
+ prometheus.io/port: '8085'
+ spec:
+ priorityClassName: system-cluster-critical
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 65534
+ fsGroup: 65534
+ seccompProfile:
+ type: RuntimeDefault
+ serviceAccountName: cluster-autoscaler
+ containers:
+ - image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.26.2
+ name: cluster-autoscaler
+ resources:
+ limits:
+ cpu: 100m
+ memory: 600Mi
+ requests:
+ cpu: 100m
+ memory: 600Mi
+ command:
+ - ./cluster-autoscaler
+ - --v=4
+ - --stderrthreshold=info
+ - --cloud-provider=aws
+ - --skip-nodes-with-local-storage=false
+ - --expander=least-waste
+ - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/jupyterhub
+ volumeMounts:
+ - name: ssl-certs
+ mountPath: /etc/ssl/certs/ca-certificates.crt # /etc/ssl/certs/ca-bundle.crt for Amazon Linux Worker Nodes
+ readOnly: true
+ imagePullPolicy: "Always"
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ readOnlyRootFilesystem: true
+ volumes:
+ - name: ssl-certs
+ hostPath:
+ path: "/etc/ssl/certs/ca-bundle.crt"
\ No newline at end of file
diff --git a/aws/config-aws.yaml b/aws/config-aws.yaml
new file mode 100644
index 00000000..87931e52
--- /dev/null
+++ b/aws/config-aws.yaml
@@ -0,0 +1,62 @@
+# A few notes!
+# The hub -> authentic class defaults to "dummy"
+# We shouldn't need any image pull secrets assuming public
+# There is a note about the database being a sqlite pvc
+# (and a TODO for better solution for Kubernetes)
+
+# This is the concurrent spawn limit, likely should be increased (deafults to 64)
+hub:
+ concurrentSpawnLimit: 10
+ config:
+ DummyAuthenticator:
+ password: butter
+ JupyterHub:
+ admin_access: true
+ authenticator_class: dummy
+
+ # This is the image I built based off of jupyterhub/k8s-hub, 3.0.2 at time of writing this
+ image:
+ name: ghcr.io/llnl/thicket-tutorial-hub
+ tag: "radiuss-2024"
+ pullPolicy: Always
+
+# https://z2jh.jupyter.org/en/latest/administrator/optimization.html#scaling-up-in-time-user-placeholders
+scheduling:
+ podPriority:
+ enabled: true
+ userPlaceholder:
+ # Specify 3 dummy user pods will be used as placeholders
+ replicas: 3
+
+# This is the "spawn" image
+singleuser:
+ image:
+ name: ghcr.io/llnl/thicket-tutorial-spawn
+ tag: "radiuss-2024"
+ pullPolicy: Always
+ cpu:
+ limit: 1
+ memory:
+ limit: '4G'
+ cmd: /entrypoint.sh
+
+ # This runs as the root user, who clones and changes ownership to uid 1000
+ initContainers:
+ - name: init-myservice
+ image: ghcr.io/llnl/thicket-tutorial-init:radiuss-2024
+ command: ["/entrypoint.sh"]
+ volumeMounts:
+ - name: thicket-tutorial
+ mountPath: /home/jovyan
+
+ # This is how we get the tutorial files added
+ storage:
+ type: none
+ # gitRepo volume is deprecated so we need another way
+ # https://kubernetes.io/docs/concepts/storage/volumes/#gitrepo
+ extraVolumes:
+ - name: thicket-tutorial
+ emptyDir: {}
+ extraVolumeMounts:
+ - name: thicket-tutorial
+ mountPath: /home/jovyan
diff --git a/aws/eksctl-config.yaml b/aws/eksctl-config.yaml
new file mode 100644
index 00000000..c5f0523e
--- /dev/null
+++ b/aws/eksctl-config.yaml
@@ -0,0 +1,110 @@
+# https://www.arhea.net/posts/2020-06-18-jupyterhub-amazon-eks
+apiVersion: eksctl.io/v1alpha5
+kind: ClusterConfig
+metadata:
+ name: jupyterhub
+ region: us-east-2
+
+iam:
+ withOIDC: true
+ serviceAccounts:
+ - metadata:
+ name: cluster-autoscaler
+ namespace: kube-system
+ labels:
+ aws-usage: "cluster-ops"
+ app.kubernetes.io/name: cluster-autoscaler
+
+ # https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md
+ attachPolicy:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - "autoscaling:DescribeAutoScalingGroups"
+ - "autoscaling:DescribeAutoScalingInstances"
+ - "autoscaling:DescribeLaunchConfigurations"
+ - "autoscaling:DescribeTags"
+ - "autoscaling:SetDesiredCapacity"
+ - "autoscaling:TerminateInstanceInAutoScalingGroup"
+ - "ec2:DescribeLaunchTemplateVersions"
+ Resource: '*'
+
+ - metadata:
+ name: ebs-csi-controller-sa
+ namespace: kube-system
+ labels:
+ aws-usage: "cluster-ops"
+ app.kubernetes.io/name: aws-ebs-csi-driver
+ attachPolicy:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - "ec2:AttachVolume"
+ - "ec2:CreateSnapshot"
+ - "ec2:CreateTags"
+ - "ec2:CreateVolume"
+ - "ec2:DeleteSnapshot"
+ - "ec2:DeleteTags"
+ - "ec2:DeleteVolume"
+ - "ec2:DescribeInstances"
+ - "ec2:DescribeSnapshots"
+ - "ec2:DescribeTags"
+ - "ec2:DescribeVolumes"
+ - "ec2:DetachVolume"
+ Resource: '*'
+
+availabilityZones: ["us-east-2a", "us-east-2b", "us-east-2c"]
+managedNodeGroups:
+ - name: ng-us-east-2a
+ iam:
+ withAddonPolicies:
+ autoScaler: true
+ instanceType: m5.large
+ volumeSize: 30
+ desiredCapacity: 1
+ minSize: 1
+ maxSize: 3
+ privateNetworking: true
+ availabilityZones:
+ - us-east-2a
+ # I didn't set this, but I know it's been an issue
+ # propagateASGTags: true
+ tags:
+ k8s.io/cluster-autoscaler/enabled: "true"
+ k8s.io/cluster-autoscaler/jupyterhub: "owned"
+
+ - name: ng-us-east-2b
+ iam:
+ withAddonPolicies:
+ autoScaler: true
+ instanceType: m5.large
+ volumeSize: 30
+ desiredCapacity: 1
+ minSize: 1
+ maxSize: 3
+ privateNetworking: true
+ availabilityZones:
+ - us-east-2b
+ # propagateASGTags: true
+ tags:
+ k8s.io/cluster-autoscaler/enabled: "true"
+ k8s.io/cluster-autoscaler/jupyterhub: "owned"
+
+ - name: ng-us-east-2c
+ iam:
+ withAddonPolicies:
+ autoScaler: true
+ instanceType: m5.large
+ volumeSize: 30
+ desiredCapacity: 1
+ minSize: 1
+ maxSize: 3
+ privateNetworking: true
+ availabilityZones:
+ - us-east-2c
+ # propagateASGTags: true
+ tags:
+ k8s.io/cluster-autoscaler/enabled: "true"
+ k8s.io/cluster-autoscaler/jupyterhub: "owned"
\ No newline at end of file
diff --git a/aws/eksctl-radiuss-2024.yaml b/aws/eksctl-radiuss-2024.yaml
new file mode 100644
index 00000000..7f424951
--- /dev/null
+++ b/aws/eksctl-radiuss-2024.yaml
@@ -0,0 +1,79 @@
+# https://www.arhea.net/posts/2020-06-18-jupyterhub-amazon-eks
+apiVersion: eksctl.io/v1alpha5
+kind: ClusterConfig
+metadata:
+ name: jupyterhub
+ region: us-east-1
+
+iam:
+ withOIDC: true
+ serviceAccounts:
+ - metadata:
+ name: ebs-csi-controller-sa
+ namespace: kube-system
+ labels:
+ aws-usage: "cluster-ops"
+ app.kubernetes.io/name: aws-ebs-csi-driver
+ attachPolicy:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: Allow
+ Action:
+ - "ec2:AttachVolume"
+ - "ec2:CreateSnapshot"
+ - "ec2:CreateTags"
+ - "ec2:CreateVolume"
+ - "ec2:DeleteSnapshot"
+ - "ec2:DeleteTags"
+ - "ec2:DeleteVolume"
+ - "ec2:DescribeInstances"
+ - "ec2:DescribeSnapshots"
+ - "ec2:DescribeTags"
+ - "ec2:DescribeVolumes"
+ - "ec2:DetachVolume"
+ Resource: '*'
+
+availabilityZones:
+ - us-east-1a
+ - us-east-1b
+ - us-east-1c
+
+managedNodeGroups:
+ - name: ng-us-east-1a
+ instanceType: m6a.8xlarge
+ volumeSize: 256
+ volumeType: gp3
+ volumeIOPS: 16000
+ volumeThroughput: 512
+ desiredCapacity: 1
+ minSize: 1
+ maxSize: 6
+ privateNetworking: true
+ availabilityZones:
+ - us-east-1a
+
+ - name: ng-us-east-1b
+ instanceType: m6a.8xlarge
+ volumeSize: 256
+ volumeType: gp3
+ volumeIOPS: 16000
+ volumeThroughput: 512
+ desiredCapacity: 1
+ minSize: 1
+ maxSize: 6
+ privateNetworking: true
+ availabilityZones:
+ - us-east-1b
+
+ - name: ng-us-east-1c
+ instanceType: m6a.8xlarge
+ volumeSize: 256
+ volumeType: gp3
+ volumeIOPS: 16000
+ volumeThroughput: 512
+ desiredCapacity: 1
+ minSize: 1
+ maxSize: 6
+ privateNetworking: true
+ availabilityZones:
+ - us-east-1c
diff --git a/aws/storageclass.yaml b/aws/storageclass.yaml
new file mode 100644
index 00000000..e03c1c8a
--- /dev/null
+++ b/aws/storageclass.yaml
@@ -0,0 +1,7 @@
+kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+ name: gp3
+provisioner: kubernetes.io/aws-ebs
+volumeBindingMode: WaitForFirstConsumer
+reclaimPolicy: Delete
\ No newline at end of file
diff --git a/docker/Dockerfile.hub b/docker/Dockerfile.hub
new file mode 100644
index 00000000..a80cb926
--- /dev/null
+++ b/docker/Dockerfile.hub
@@ -0,0 +1,9 @@
+ARG JUPYTERHUB_VERSION=3.0.2
+FROM --platform=linux/amd64 jupyterhub/k8s-hub:$JUPYTERHUB_VERSION
+
+# Add template override directory and copy our example
+# Replace the default
+USER root
+# RUN mv /usr/local/share/jupyterhub/templates/login.html /usr/local/share/jupyterhub/templates/_login.html
+# COPY ./docker/login.html /usr/local/share/jupyterhub/templates/login.html
+USER jovyan
diff --git a/docker/Dockerfile.init b/docker/Dockerfile.init
new file mode 100644
index 00000000..0b07be77
--- /dev/null
+++ b/docker/Dockerfile.init
@@ -0,0 +1,13 @@
+FROM --platform=linux/amd64 alpine/git
+
+ENV NB_USER=jovyan \
+ NB_UID=1000 \
+ HOME=/home/jovyan
+
+RUN adduser \
+ -D \
+ -g "Default user" \
+ -u ${NB_UID} \
+ -h ${HOME} \
+ ${NB_USER}
+COPY ./docker/init-entrypoint.sh /entrypoint.sh
diff --git a/docker/Dockerfile.spawn b/docker/Dockerfile.spawn
new file mode 100644
index 00000000..eb536d32
--- /dev/null
+++ b/docker/Dockerfile.spawn
@@ -0,0 +1,87 @@
+FROM --platform=linux/amd64 continuumio/miniconda3:4.12.0
+
+USER root
+
+ENV NB_USER=jovyan \
+ NB_UID=1000 \
+ HOME=/home/jovyan
+
+RUN adduser \
+ --disabled-password \
+ --gecos "Default User" \
+ --uid ${NB_UID} \
+ --home ${HOME} \
+ --force-badname \
+ ${NB_USER}
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install -y --no-install-recommends \
+ ca-certificates \
+ dnsutils \
+ iputils-ping \
+ build-essential \
+ vim \
+ git \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN conda install -y -c conda-forge \
+ jupyter \
+ jupyterlab \
+ ipython
+
+# COPY ./requirements.txt ./requirements.txt
+# COPY ./docker/requirements-cloud.txt ./requirements-cloud.txt
+COPY ./environment.yml ./environment.yml
+
+# RUN python3 -m pip install -r requirements.txt \
+# && python3 -m pip install papermill
+
+# RUN conda install -y -c conda-forge --file requirements.txt
+# RUN conda install -y -c conda-forge --file requirements-cloud.txt
+# RUN python3 -m pip install papermill && \
+# python3 -m pip install ipython==7.34.0 && \
+RUN conda env update -n base --file environment.yml
+# RUN python3 -m IPython kernel install
+
+# RUN python3 -m pip install -r requirements-cloud.txt && \
+# python3 -m pip install ipython==7.34.0 && \
+# python3 -m IPython kernel install
+
+RUN python3 -m pip install jupyter_app_launcher && \
+ python3 -m pip install --upgrade jupyter-server && \
+ python3 -m pip install jupyter-launcher-shortcuts && \
+ mkdir -p /usr/local/share/jupyter/lab/jupyter_app_launcher
+
+COPY ./docker/jupyter-launcher.yaml /usr/local/share/jupyter/lab/jupyter_app_launcher/jp_app_launcher.yaml
+
+ENV JUPYTER_APP_LAUNCHER_PATH /usr/local/share/jupyter/lab/jupyter_app_launcher
+
+RUN chmod -R 777 ~/ /home/jovyan
+
+USER ${NB_USER}
+WORKDIR ${HOME}
+
+COPY ./thicket-logo.png ${HOME}/thicket-logo.png
+
+ENV SHELL=/bin/bash
+EXPOSE 8888
+ENTRYPOINT [ "tini", "--" ]
+
+COPY ./docker/entrypoint.sh /entrypoint.sh
+COPY ./docker/start.sh /start.sh
+COPY ./docker/run_all.sh /run_all.sh
+
+ENV PATH "${HOME}/.local/bin:${PATH}"
+RUN mkdir -p $HOME/.local/share && \
+ chmod 777 $HOME/.local/share
+
+CMD [ "jupyter", "lab" ]
+
+COPY ./notebooks/01_thicket_tutorial.ipynb ./notebooks/01_thicket_tutorial.ipynb
+COPY ./notebooks/02_thicket_rajaperf_clustering.ipynb ./notebooks/02_thicket_rajaperf_clustering.ipynb
+COPY ./notebooks/03_extrap-with-metadata-aggregated.ipynb ./notebooks/03_extrap-with-metadata-aggregated.ipynb
+COPY ./notebooks/04_stats-functions.ipynb ./notebooks/04_stats-functions.ipynb
+COPY ./notebooks/05_thicket_query_language.ipynb ./notebooks/05_thicket_query_language.ipynb
+COPY ./notebooks/06_groupby_aggregate_of_multirun_data.ipynb ./notebooks/06_groupby_aggregate_of_multirun_data.ipynb
+COPY ./data/ ./data/
diff --git a/docker_scripts/entrypoint.sh b/docker/entrypoint.sh
similarity index 100%
rename from docker_scripts/entrypoint.sh
rename to docker/entrypoint.sh
diff --git a/docker/init-entrypoint.sh b/docker/init-entrypoint.sh
new file mode 100755
index 00000000..0c0f7847
--- /dev/null
+++ b/docker/init-entrypoint.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Copy the notebook icon
+# This would be for the customized launcher, not working yet
+# wget https://flux-framework.org/assets/images/Flux-logo-mark-only-full-color.png
+# mv Flux-logo-mark-only-full-color.png /home/jovyan/flux-icon.png
+
+# We need to clone to the user home, and then change permissions to uid 1000
+# That uid is shared by jovyan here and the spawn container
+# git clone https://github.com/rse-ops/flux-radiuss-tutorial-2023 /home/jovyan/flux-tutorial
+chown -R 1000 /home/jovyan
\ No newline at end of file
diff --git a/docker/jupyter-launcher.yaml b/docker/jupyter-launcher.yaml
new file mode 100644
index 00000000..2f0877ea
--- /dev/null
+++ b/docker/jupyter-launcher.yaml
@@ -0,0 +1,131 @@
+- title: "Chapter 1: Thicket 101"
+ description: Intro to Thicket and basics
+ type: jupyterlab-commands
+ icon: /home/jovyan/thicket-logo.png
+ source:
+ - label: Thicket Tutorial
+ id: 'filebrowser:open-path'
+ args:
+ path: ./notebooks/01_thicket_tutorial.ipynb
+ icon: /home/jovyan/thicket-logo.png
+ catalog: Notebook
+- title: "Chapter 2: Clustering RAJAPerf"
+ description: Using Thicket to cluster data from the RAJA Performance Suite
+ type: jupyterlab-commands
+ icon: /home/jovyan/thicket-logo.png
+ source:
+ - label: Thicket Tutorial
+ id: 'filebrowser:open-path'
+ args:
+ path: ./notebooks/02_thicket_rajaperf_clustering.ipynb
+ icon: /home/jovyan/thicket-logo.png
+ catalog: Notebook
+- title: "Chapter 3: Modeling with Extra-P"
+ description: Modeling applications using Thicket and Extra-P
+ type: jupyterlab-commands
+ icon: /home/jovyan/thicket-logo.png
+ source:
+ - label: Thicket Tutorial
+ id: 'filebrowser:open-path'
+ args:
+ path: ./notebooks/03_extrap-with-metadata-aggregated.ipynb
+ icon: /home/jovyan/thicket-logo.png
+ catalog: Notebook
+- title: "Chapter 4: Stats and Visualization"
+ description: Using Thicket to calculate statistics and visualize performance across runs
+ type: jupyterlab-commands
+ icon: /home/jovyan/thicket-logo.png
+ source:
+ - label: Thicket Tutorial
+ id: 'filebrowser:open-path'
+ args:
+ path: ./notebooks/04_stats-functions.ipynb
+ icon: /home/jovyan/thicket-logo.png
+ catalog: Notebook
+- title: "Chapter 5: Call Graph Query Language"
+ description: Using Thicket's query language for advanced filtering
+ type: jupyterlab-commands
+ icon: /home/jovyan/thicket-logo.png
+ source:
+ - label: Thicket Tutorial
+ id: 'filebrowser:open-path'
+ args:
+ path: ./notebooks/05_thicket_query_language.ipynb
+ icon: /home/jovyan/thicket-logo.png
+ catalog: Notebook
+- title: "Chapter 6: Composing Datasets with Groupby-Aggregate"
+ description: Using Thicket's groupby-aggregate functionality to compose datasets
+ type: jupyterlab-commands
+ icon: /home/jovyan/thicket-logo.png
+ source:
+ - label: Thicket Tutorial
+ id: 'filebrowser:open-path'
+ args:
+ path: ./notebooks/06_groupby_aggregate_of_multirun_data.ipynb
+ icon: /home/jovyan/thicket-logo.png
+ catalog: Notebook
+
+- title: Thicket ReadTheDocs
+ description: Documentation for Thicket
+ source: https://thicket.readthedocs.io/en/latest/
+ type: url
+ catalog: Thicket Resources
+ args:
+ sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+# - title: Thicket Repository
+# description: Repository for Thicket
+# source: https://github.com/llnl/thicket
+# type: url
+# catalog: Thicket Resources
+# args:
+# sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+- title: Hatchet Documentation
+ description: Documentation for Hatchet
+ source: https://llnl-hatchet.readthedocs.io/en/latest/
+ type: url
+ catalog: Thicket Resources
+ args:
+ sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+# - title: Hatchet Repository
+# description: Repository for Hatchet
+# source: https://github.com/llnl/hatchet
+# type: url
+# catalog: Thicket Resources
+# args:
+# sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+
+# - title: Thicket Paper
+# description: HPDC'23 Paper on Thicket
+# source: https://doi.org/10.1145/3588195.3592989
+# type: url
+# catalog: Thicket Publications
+# args:
+# sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+# - title: Hatchet Paper
+# description: SC'19 Paper on Thicket
+# source: https://doi.org/10.1145/3295500.3356219
+# type: url
+# catalog: Thicket Publications
+# args:
+# sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+# - title: Hatchet/Thicket Query Language Paper
+# description: eScience'22 Paper on the Hatchet/Thicket Query Language
+# source: https://doi.org/10.1109/eScience55777.2022.00039
+# type: url
+# catalog: Thicket Publications
+# args:
+# sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+# - title: Hatchet Interactive Visualization Paper
+# description: 2024 TVCG Paper on Hatchet's Interactive Visualizations
+# source: https://doi.org/10.1109/TVCG.2024.3354561
+# type: url
+# catalog: Thicket Publications
+# args:
+# sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
+# - title: Hatchet Improvements Paper
+# description: 2020 ProTools Workshop Paper at SC
+# source: https://doi.org/10.1109/HUSTProtools51951.2020.00013
+# type: url
+# catalog: Thicket Publications
+# args:
+# sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups']
diff --git a/docker_scripts/run_all.sh b/docker/run_all.sh
similarity index 100%
rename from docker_scripts/run_all.sh
rename to docker/run_all.sh
diff --git a/docker/start.sh b/docker/start.sh
new file mode 100755
index 00000000..79f0a5db
--- /dev/null
+++ b/docker/start.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+/opt/conda/bin/jupyter-lab --ip=0.0.0.0
\ No newline at end of file
diff --git a/docker_scripts/start.sh b/docker_scripts/start.sh
deleted file mode 100755
index 877f861d..00000000
--- a/docker_scripts/start.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-/opt/conda/bin/jupyter-notebook --ip=0.0.0.0
\ No newline at end of file
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 00000000..d856517c
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,113 @@
+channels:
+ - conda-forge
+dependencies:
+ - pip
+ - psutil==5.9.5
+ - pip:
+ - llnl-thicket[extrap,plotting]==2024.1.0
+ - ipython==8.13.0
+ - scikit-learn
+ - alembic==1.11.3
+ - anyio==3.7.1
+ - argon2-cffi==23.1.0
+ - argon2-cffi-bindings==21.2.0
+ - arrow==1.2.3
+ - asttokens==2.2.1
+ - async-generator==1.10
+ - async-lru==2.0.4
+ - attrs==23.1.0
+ - babel==2.12.1
+ - backcall==0.2.0
+ - beautifulsoup4==4.12.2
+ - bleach==6.0.0
+ - certifi==2023.7.22
+ - certipy==0.1.3
+ - cffi==1.15.1
+ - charset-normalizer==3.2.0
+ - comm==0.1.4
+ - cryptography==41.0.3
+ - debugpy==1.6.7.post1
+ - decorator==5.1.1
+ - defusedxml==0.7.1
+ - executing==1.2.0
+ - fastjsonschema==2.18.0
+ - fqdn==1.5.1
+ - greenlet==2.0.2
+ - idna==3.4
+ - ipykernel==6.25.1
+ - isoduration==20.11.0
+ - jedi==0.19.0
+ - jinja2==3.1.2
+ - json5==0.9.14
+ - jsonpointer==2.4
+ - jsonschema[format-nongpl]==4.19.0
+ - jsonschema-specifications==2023.7.1
+ - jupyter-client==8.3.0
+ - jupyter-core==5.3.1
+ - jupyter-events==0.7.0
+ - jupyter-lsp==2.2.0
+ - jupyter-server==2.7.2
+ - jupyter-server-terminals==0.4.4
+ - jupyter-telemetry==0.1.0
+ - jupyterhub==4.0.2
+ - jupyterlab==4.0.5
+ - jupyterlab-pygments==0.2.2
+ - jupyterlab-server==2.24.0
+ - mako==1.2.4
+ - markupsafe==2.1.3
+ - matplotlib-inline==0.1.6
+ - mistune==3.0.1
+ - nbclassic==1.0.0
+ - nbclient==0.8.0
+ - nbconvert==7.7.4
+ - nbformat==5.9.2
+ - nbgitpuller==1.2.0
+ - nest-asyncio==1.5.7
+ - notebook-shim==0.2.3
+ - oauthlib==3.2.2
+ - overrides==7.4.0
+ - packaging==23.1
+ - pamela==1.1.0
+ - pandocfilters==1.5.0
+ - parso==0.8.3
+ - pexpect==4.8.0
+ - pickleshare==0.7.5
+ - platformdirs==3.10.0
+ - prometheus-client==0.17.1
+ - prompt-toolkit==3.0.39
+ - ptyprocess==0.7.0
+ - pure-eval==0.2.2
+ - pycparser==2.21
+ - pygments==2.16.1
+ - pyopenssl==23.2.0
+ - python-dateutil==2.8.2
+ - python-json-logger==2.0.7
+ - pyyaml==6.0.1
+ - pyzmq==25.1.1
+ - referencing==0.30.2
+ - requests==2.31.0
+ - rfc3339-validator==0.1.4
+ - rfc3986-validator==0.1.1
+ - rpds-py==0.9.2
+ - ruamel-yaml==0.17.32
+ - ruamel-yaml-clib==0.2.7
+ - send2trash==1.8.2
+ - six==1.16.0
+ - sniffio==1.3.0
+ - soupsieve==2.4.1
+ - sqlalchemy==2.0.20
+ - stack-data==0.6.2
+ - terminado==0.17.1
+ - tinycss2==1.2.1
+ - tornado==6.3.3
+ - traitlets==5.9.0
+ - typing-extensions==4.7.1
+ - uri-template==1.3.0
+ - urllib3==2.0.4
+ - wcwidth==0.2.6
+ - webcolors==1.13
+ - webencodings==0.5.1
+ - websocket-client==1.6.1
+ # - ipython==7.15.0
+ # - ipython-genutils==0.2.0
+ # - psutil==5.9.5
diff --git a/gcp/config.yaml b/gcp/config.yaml
new file mode 100644
index 00000000..51e67f9a
--- /dev/null
+++ b/gcp/config.yaml
@@ -0,0 +1,62 @@
+# A few notes!
+# The hub -> authentic class defaults to "dummy"
+# We shouldn't need any image pull secrets assuming public
+# There is a note about the database being a sqlite pvc
+# (and a TODO for better solution for Kubernetes)
+
+# This is the concurrent spawn limit, likely should be increased (deafults to 64)
+hub:
+ concurrentSpawnLimit: 10
+ config:
+ DummyAuthenticator:
+ password: butter
+ JupyterHub:
+ admin_access: true
+ authenticator_class: dummy
+
+ # This is the image I built based off of jupyterhub/k8s-hub, 3.0.2 at time of writing this
+ image:
+ name: ghcr.io/LLNL/thicket-jupyter-hub
+ tag: "radiuss-2024"
+ pullPolicy: Always
+
+# https://z2jh.jupyter.org/en/latest/administrator/optimization.html#scaling-up-in-time-user-placeholders
+scheduling:
+ podPriority:
+ enabled: true
+ userPlaceholder:
+ # Specify 3 dummy user pods will be used as placeholders
+ replicas: 3
+
+# This is the "spawn" image
+singleuser:
+ image:
+ name: ghcr.io/LLNL/thicket-jupyter-spawn
+ tag: "radiuss-2024"
+ pullPolicy: Always
+ cpu:
+ limit: 1
+ memory:
+ limit: '4G'
+ cmd: /entrypoint.sh
+
+# initContainers:
+# - name: init-myservice
+# image: alpine/git
+# command: ["git", "clone", "https://github.com/rse-ops/flux-radiuss-tutorial-2023", "/home/jovyan/flux-tutorial"]
+# volumeMounts:
+# - name: flux-tutorial
+# mountPath: /home/jovyan
+
+ # This is how we get the tutorial files added
+ storage:
+ type: none
+
+ # gitRepo volume is deprecated so we need another way
+ # https://kubernetes.io/docs/concepts/storage/volumes/#gitrepo
+ extraVolumes:
+ - name: thicket-tutorial
+ emptyDir: {}
+ extraVolumeMounts:
+ - name: thicket-tutorial
+ mountPath: /home/jovyan/
\ No newline at end of file
diff --git a/thicket-logo.png b/thicket-logo.png
new file mode 100644
index 00000000..35e5c7b2
Binary files /dev/null and b/thicket-logo.png differ
diff --git a/tmp/requirements-cloud.txt b/tmp/requirements-cloud.txt
new file mode 100644
index 00000000..8122f2d8
--- /dev/null
+++ b/tmp/requirements-cloud.txt
@@ -0,0 +1,339 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# Use the "Run workflow" button at https://github.com/jupyterhub/zero-to-jupyterhub-k8s/actions/workflows/watch-dependencies.yaml
+#
+alembic==1.11.3
+ # via jupyterhub
+anyio==3.7.1
+ # via jupyter-server
+argon2-cffi==23.1.0
+ # via
+ # jupyter-server
+ # nbclassic
+argon2-cffi-bindings==21.2.0
+ # via argon2-cffi
+arrow==1.2.3
+ # via isoduration
+asttokens==2.2.1
+ # via stack-data
+async-generator==1.10
+ # via jupyterhub
+async-lru==2.0.4
+ # via jupyterlab
+attrs==23.1.0
+ # via
+ # jsonschema
+ # referencing
+babel==2.12.1
+ # via jupyterlab-server
+backcall==0.2.0
+ # via ipython
+beautifulsoup4==4.12.2
+ # via nbconvert
+bleach==6.0.0
+ # via nbconvert
+certifi==2023.7.22
+ # via requests
+certipy==0.1.3
+ # via jupyterhub
+cffi==1.15.1
+ # via
+ # argon2-cffi-bindings
+ # cryptography
+charset-normalizer==3.2.0
+ # via requests
+comm==0.1.4
+ # via ipykernel
+cryptography==41.0.3
+ # via pyopenssl
+debugpy==1.6.7.post1
+ # via ipykernel
+decorator==5.1.1
+ # via ipython
+defusedxml==0.7.1
+ # via nbconvert
+executing==1.2.0
+ # via stack-data
+fastjsonschema==2.18.0
+ # via nbformat
+fqdn==1.5.1
+ # via jsonschema
+greenlet==2.0.2
+ # via sqlalchemy
+idna==3.4
+ # via
+ # anyio
+ # jsonschema
+ # requests
+ipykernel==6.25.1
+ # via
+ # jupyterlab
+ # nbclassic
+ipython==8.13.0
+ # via ipykernel
+ipython-genutils==0.2.0
+ # via nbclassic
+isoduration==20.11.0
+ # via jsonschema
+jedi==0.19.0
+ # via ipython
+jinja2==3.1.2
+ # via
+ # jupyter-server
+ # jupyterhub
+ # jupyterlab
+ # jupyterlab-server
+ # nbclassic
+ # nbconvert
+json5==0.9.14
+ # via jupyterlab-server
+jsonpointer==2.4
+ # via jsonschema
+jsonschema[format-nongpl]==4.19.0
+ # via
+ # jupyter-events
+ # jupyter-telemetry
+ # jupyterlab-server
+ # nbformat
+jsonschema-specifications==2023.7.1
+ # via jsonschema
+jupyter-client==8.3.0
+ # via
+ # ipykernel
+ # jupyter-server
+ # nbclassic
+ # nbclient
+jupyter-core==5.3.1
+ # via
+ # ipykernel
+ # jupyter-client
+ # jupyter-server
+ # jupyterlab
+ # nbclassic
+ # nbclient
+ # nbconvert
+ # nbformat
+jupyter-events==0.7.0
+ # via jupyter-server
+jupyter-lsp==2.2.0
+ # via jupyterlab
+jupyter-server==2.7.2
+ # via
+ # jupyter-lsp
+ # jupyterlab
+ # jupyterlab-server
+ # nbclassic
+ # nbgitpuller
+ # notebook-shim
+jupyter-server-terminals==0.4.4
+ # via jupyter-server
+jupyter-telemetry==0.1.0
+ # via jupyterhub
+jupyterhub==4.0.2
+ # via -r requirements.in
+jupyterlab==4.0.5
+ # via -r requirements.in
+jupyterlab-pygments==0.2.2
+ # via nbconvert
+jupyterlab-server==2.24.0
+ # via jupyterlab
+mako==1.2.4
+ # via alembic
+markupsafe==2.1.3
+ # via
+ # jinja2
+ # mako
+ # nbconvert
+matplotlib-inline==0.1.6
+ # via
+ # ipykernel
+ # ipython
+mistune==3.0.1
+ # via nbconvert
+nbclassic==1.0.0
+ # via -r requirements.in
+nbclient==0.8.0
+ # via nbconvert
+nbconvert==7.7.4
+ # via
+ # jupyter-server
+ # nbclassic
+nbformat==5.9.2
+ # via
+ # jupyter-server
+ # nbclassic
+ # nbclient
+ # nbconvert
+nbgitpuller==1.2.0
+ # via -r requirements.in
+nest-asyncio==1.5.7
+ # via
+ # ipykernel
+ # nbclassic
+notebook-shim==0.2.3
+ # via
+ # jupyterlab
+ # nbclassic
+oauthlib==3.2.2
+ # via jupyterhub
+overrides==7.4.0
+ # via jupyter-server
+packaging==23.1
+ # via
+ # ipykernel
+ # jupyter-server
+ # jupyterhub
+ # jupyterlab
+ # jupyterlab-server
+ # nbconvert
+pamela==1.1.0
+ # via jupyterhub
+pandocfilters==1.5.0
+ # via nbconvert
+parso==0.8.3
+ # via jedi
+pexpect==4.8.0
+ # via ipython
+pickleshare==0.7.5
+ # via ipython
+platformdirs==3.10.0
+ # via jupyter-core
+prometheus-client==0.17.1
+ # via
+ # jupyter-server
+ # jupyterhub
+ # nbclassic
+prompt-toolkit==3.0.39
+ # via ipython
+psutil==5.9.5
+ # via ipykernel
+ptyprocess==0.7.0
+ # via
+ # pexpect
+ # terminado
+pure-eval==0.2.2
+ # via stack-data
+pycparser==2.21
+ # via cffi
+pygments==2.16.1
+ # via
+ # ipython
+ # nbconvert
+pyopenssl==23.2.0
+ # via certipy
+python-dateutil==2.8.2
+ # via
+ # arrow
+ # jupyter-client
+ # jupyterhub
+python-json-logger==2.0.7
+ # via
+ # jupyter-events
+ # jupyter-telemetry
+pyyaml==6.0.1
+ # via jupyter-events
+pyzmq==25.1.1
+ # via
+ # ipykernel
+ # jupyter-client
+ # jupyter-server
+ # nbclassic
+referencing==0.30.2
+ # via
+ # jsonschema
+ # jsonschema-specifications
+ # jupyter-events
+requests==2.31.0
+ # via
+ # jupyterhub
+ # jupyterlab-server
+rfc3339-validator==0.1.4
+ # via
+ # jsonschema
+ # jupyter-events
+rfc3986-validator==0.1.1
+ # via
+ # jsonschema
+ # jupyter-events
+rpds-py==0.9.2
+ # via
+ # jsonschema
+ # referencing
+ruamel-yaml==0.17.32
+ # via jupyter-telemetry
+ruamel-yaml-clib==0.2.7
+ # via ruamel-yaml
+send2trash==1.8.2
+ # via
+ # jupyter-server
+ # nbclassic
+six==1.16.0
+ # via
+ # asttokens
+ # bleach
+ # python-dateutil
+ # rfc3339-validator
+sniffio==1.3.0
+ # via anyio
+soupsieve==2.4.1
+ # via beautifulsoup4
+sqlalchemy==2.0.20
+ # via
+ # alembic
+ # jupyterhub
+stack-data==0.6.2
+ # via ipython
+terminado==0.17.1
+ # via
+ # jupyter-server
+ # jupyter-server-terminals
+ # nbclassic
+tinycss2==1.2.1
+ # via nbconvert
+tornado==6.3.3
+ # via
+ # ipykernel
+ # jupyter-client
+ # jupyter-server
+ # jupyterhub
+ # jupyterlab
+ # nbclassic
+ # nbgitpuller
+ # terminado
+traitlets==5.9.0
+ # via
+ # comm
+ # ipykernel
+ # ipython
+ # jupyter-client
+ # jupyter-core
+ # jupyter-events
+ # jupyter-server
+ # jupyter-telemetry
+ # jupyterhub
+ # jupyterlab
+ # matplotlib-inline
+ # nbclassic
+ # nbclient
+ # nbconvert
+ # nbformat
+typing-extensions==4.7.1
+ # via
+ # alembic
+ # sqlalchemy
+uri-template==1.3.0
+ # via jsonschema
+urllib3==2.0.4
+ # via requests
+wcwidth==0.2.6
+ # via prompt-toolkit
+webcolors==1.13
+ # via jsonschema
+webencodings==0.5.1
+ # via
+ # bleach
+ # tinycss2
+websocket-client==1.6.1
+ # via jupyter-server
\ No newline at end of file
diff --git a/requirements.txt b/tmp/requirements.txt
similarity index 100%
rename from requirements.txt
rename to tmp/requirements.txt