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