|
| 1 | +--- |
| 2 | +title: Admin setup |
| 3 | +description: Deploying and configuring the Data Science Pack as a cluster administrator. |
| 4 | +--- |
| 5 | + |
| 6 | +This is the administrator's entry point: what the chart needs from the cluster, what it |
| 7 | +derives on its own, and where each knob lives. For a five-minute install, start with |
| 8 | +[Quick Start](/quick-start/) instead. |
| 9 | + |
| 10 | +## What the cluster must provide |
| 11 | + |
| 12 | +| Requirement | Why | Optional? | |
| 13 | +|---|---|---| |
| 14 | +| [nebari-operator](https://github.com/nebari-dev/nebari-operator) | Reconciles the `NebariApp` into routing, TLS, and a Keycloak OIDC client | Yes — set `nebariapp.enabled: false` | |
| 15 | +| Envoy Gateway | The `NebariApp`'s HTTPRoute attaches to it | With the operator | |
| 16 | +| cert-manager | Issues the TLS certificate for the hub hostname | With the operator | |
| 17 | +| Keycloak (`bitnami/keycloakx`) | Identity provider; the operator provisions the hub client in it | With the operator | |
| 18 | +| A ReadWriteMany StorageClass | Per-group shared directories | Yes — see [Shared Storage](/shared-storage/) | |
| 19 | +| A default (RWO) StorageClass | Per-user home PVCs and Nebi workspace PVCs | No | |
| 20 | +| Namespace label `nebari.dev/managed=true` | The operator ignores `NebariApp`s in unlabeled namespaces | No, when the operator is used | |
| 21 | + |
| 22 | +Without the operator the chart still installs — dummy authenticator, no routing, no shared |
| 23 | +Keycloak. That is the local-development path, not a deployment mode. |
| 24 | + |
| 25 | +## One required field |
| 26 | + |
| 27 | +The chart is built around a single input. Everything else is derived by subdomain |
| 28 | +convention and can be overridden individually: |
| 29 | + |
| 30 | +```yaml |
| 31 | +keycloak: |
| 32 | + hostname: keycloak.example.com |
| 33 | +``` |
| 34 | +
|
| 35 | +From that one value: |
| 36 | +
|
| 37 | +| Derived | Rule | Example | |
| 38 | +|---|---|---| |
| 39 | +| Base domain | `keycloak.hostname` minus its first label | `example.com` | |
| 40 | +| Hub hostname | `<subdomains.hub>.<base>` | `hub.example.com` | |
| 41 | +| Nebi external URL | `https://<subdomains.nebi>.<base>` | `https://nebi.example.com` | |
| 42 | +| Keycloak token URL | `https://<keycloak.hostname>/realms/<realm>/…/token` | — | |
| 43 | +| Hub OIDC client ID | `jupyterhub-<release>-<chart>` | `jupyterhub-data-science-pack-nebari-data-science-pack` | |
| 44 | +| Nebi OIDC client ID | `nebi-<nebi.releaseName>-nebari-nebi-pack` | `nebi-nebi-pack-nebari-nebi-pack` | |
| 45 | + |
| 46 | +:::caution[Derivation needs a dotted hostname] |
| 47 | +The base domain is `keycloak.hostname` with its first label stripped. A single-label value |
| 48 | +like `keycloak` yields an empty base domain, and the hub hostname, Nebi URL, and token URL |
| 49 | +all come out empty — the chart renders, and nothing routes. Set `nebariapp.hostname` and |
| 50 | +`nebi.remoteURL` explicitly in that case. |
| 51 | +::: |
| 52 | + |
| 53 | +## Install |
| 54 | + |
| 55 | +```bash |
| 56 | +helm repo add nebari https://raw.githubusercontent.com/nebari-dev/helm-repository/gh-pages/ |
| 57 | +helm repo update |
| 58 | +
|
| 59 | +kubectl create namespace data-science |
| 60 | +kubectl label namespace data-science nebari.dev/managed=true |
| 61 | +
|
| 62 | +helm install data-science-pack nebari/nebari-data-science-pack \ |
| 63 | + --namespace data-science \ |
| 64 | + --set keycloak.hostname=keycloak.example.com |
| 65 | +``` |
| 66 | + |
| 67 | +Also available as an OCI artifact: |
| 68 | + |
| 69 | +```bash |
| 70 | +helm install data-science-pack \ |
| 71 | + oci://quay.io/nebari/charts/nebari-data-science-pack --version <version> |
| 72 | +``` |
| 73 | + |
| 74 | +:::note[The release name is load-bearing] |
| 75 | +Two things are derived from it: the hub OIDC client ID, and the Secret name the hub mounts |
| 76 | +at `/etc/oauth` |
| 77 | +(`{Release.Name}-{Chart.Name}-oidc-client`, hardcoded in |
| 78 | +`jupyterhub.hub.extraVolumes`). Installing under a non-default release name means updating |
| 79 | +that `secretName` — and the matching `secretKeyRef` under `jupyterhub.hub.extraEnv` — or the |
| 80 | +hub silently falls back to dummy auth. |
| 81 | +::: |
| 82 | + |
| 83 | +## Where each knob lives |
| 84 | + |
| 85 | +Configuration splits across three layers. Knowing which one you are in explains most |
| 86 | +"my value did nothing" reports. |
| 87 | + |
| 88 | +| Layer | Path | What it is | |
| 89 | +|---|---|---| |
| 90 | +| Chart values | `keycloak`, `subdomains`, `nebariapp`, `singleuser`, `singleuserCuller`, `sharedStorage`, `nebi`, `rbac` | This chart's own values | |
| 91 | +| Chart-derived hub config | `jupyterhub.custom.*` | Read by the Python files in `jupyterhub_config.d/` via `get_chart_config()` | |
| 92 | +| Upstream passthrough | everything else under `jupyterhub.*` | Handed verbatim to [Zero to JupyterHub](https://z2jh.jupyter.org/) | |
| 93 | + |
| 94 | +Field-by-field detail for all three is in the [Values reference](/values-reference/). |
| 95 | + |
| 96 | +:::caution[Two z2jh lists replace rather than merge] |
| 97 | +`jupyterhub.hub.extraVolumes` and `extraVolumeMounts` are lists, so overriding either |
| 98 | +**replaces** the chart's entries. Both carry required mounts — `custom-config` |
| 99 | +(the `jupyterhub_config.d` ConfigMap) and `oauth-client` (the OIDC Secret). Drop them and |
| 100 | +the hub comes up with an empty config directory and dummy auth. |
| 101 | + |
| 102 | +Re-include the chart's entries in any override. The same applies to |
| 103 | +`jupyterhub.hub.initContainers`, which carries the CA-bundle merge step. |
| 104 | +::: |
| 105 | + |
| 106 | +## What the chart creates |
| 107 | + |
| 108 | +Beyond the z2jh subchart's own objects: |
| 109 | + |
| 110 | +| Object | Template | Purpose | |
| 111 | +|---|---|---| |
| 112 | +| `NebariApp` | `nebariapp.yaml` | Routing, TLS, Keycloak client, landing-page card | |
| 113 | +| Hub config ConfigMap | `hub-config.yaml` | The four `jupyterhub_config.d/` Python files | |
| 114 | +| Singleuser config ConfigMap | `singleuser-config.yaml` | Per-pod config mounted by the spawner | |
| 115 | +| Nebi config ConfigMap | `singleuser-nebi-config.yaml` | Admin-provisioned Nebi registries — only when customized | |
| 116 | +| Shared PVC (+ NFS server) | `shared-pvc.yaml`, `nfs-server.yaml` | Per-group shared storage | |
| 117 | +| NFS client installer | `nfs-client-installer.yaml` | DaemonSet installing `nfs-common`, opt-in | |
| 118 | +| Keycloak RBAC bootstrap Job | `keycloak-rbac-bootstrap-job.yaml` | post-install/upgrade hook; groups mapper + shared-mount role | |
| 119 | +| Two NetworkPolicies | `singleuser-gateway-egress.yaml`, `hub-nebi-networkpolicy.yaml` | Egress the subchart's policy does not cover | |
| 120 | + |
| 121 | +## The Keycloak bootstrap job |
| 122 | + |
| 123 | +`rbac.bootstrap.enabled` defaults to `true`. It runs as a post-install/post-upgrade hook in |
| 124 | +the `keycloak` namespace, authenticates with the admin credentials Secret, and is |
| 125 | +idempotent — it skips cleanly when `kcAdminCredentialSecret` is unset, so the chart still |
| 126 | +installs on clusters that have not surfaced one. |
| 127 | + |
| 128 | +It does four things: |
| 129 | + |
| 130 | +1. Adds the `oidc-group-membership-mapper` to the `groups` client scope. Without it the |
| 131 | + `groups` claim is empty, and both shared storage and `access: yaml` profile gating |
| 132 | + silently fall back to "no groups". |
| 133 | +2. Creates the `allow-group-directory-creation-role` client role on the hub client. |
| 134 | +3. Enables `serviceAccountsEnabled` on the hub client and binds |
| 135 | + `realm-management.{view-clients,view-groups,view-realm}` to its service account. |
| 136 | +4. Assigns the shared-mount role to the groups listed in `rbac.bootstrap.sharedMountGroups`. |
| 137 | + |
| 138 | +Set `enabled: false` for BYO-Keycloak or local development. Override `namespace`, |
| 139 | +`kcAdminCredentialSecret`, and `kcHost` for non-bitnami Keycloak layouts. |
| 140 | + |
| 141 | +## Integrations |
| 142 | + |
| 143 | +- **[Nebi](/nebi-integration/)** — the environment manager. Ships into user pods via an init |
| 144 | + container and needs a matching OIDC client for token exchange. |
| 145 | +- **[MLflow](/mlflow-integration/)** — experiment tracking. Two values, one of which is a |
| 146 | + NetworkPolicy that has to name the pod port rather than the service port. |
| 147 | +- **[NebariApp](/nebariapp-integration/)** — the CRD fields this chart sets and why. |
| 148 | + |
| 149 | +## User-facing configuration |
| 150 | + |
| 151 | +- **[Server profiles](/server-profiles/)** — sizes, images, and per-group gating. |
| 152 | +- **[Shared Storage](/shared-storage/)** — per-group directories and RWX requirements. |
| 153 | + |
| 154 | +## Verify a deployment |
| 155 | + |
| 156 | +```bash |
| 157 | +kubectl -n data-science get pods |
| 158 | +kubectl -n data-science get nebariapp,httproute,certificate |
| 159 | +
|
| 160 | +# The operator only acts on labeled namespaces |
| 161 | +kubectl get namespace data-science -o jsonpath='{.metadata.labels}' |
| 162 | +
|
| 163 | +# The hub reads its OAuth client from this Secret; absent means dummy auth |
| 164 | +kubectl -n data-science get secret data-science-pack-nebari-data-science-pack-oidc-client |
| 165 | +
|
| 166 | +# Did the Keycloak bootstrap hook succeed? |
| 167 | +kubectl -n keycloak get jobs -l app.kubernetes.io/instance=data-science-pack |
| 168 | +``` |
| 169 | + |
| 170 | +Then log in through Keycloak and check that the profile selector appears with the sizes you |
| 171 | +expect. An empty or unexpectedly short list usually means the `groups` claim is missing — |
| 172 | +see [Server profiles](/server-profiles/#gating-profiles-by-group). |
0 commit comments