NetApp Community Update
This site will enter Read Only mode on July 23 as we prepare to move to a new platform. You will still be able to view content, but posting and replying will be temporarily disabled.
We're excited to launch our new Community experience on July 30 and more information will follow soon.
Stay connected during the transition - Join our Discord community today.

Tech ONTAP Blogs

Dataset Manager on Kubernetes - a JupyterLab example

moglesby
NetApp
39 Views
When I introduced the NetApp DataOps Toolkit's Dataset Manager module, I focused on what it does for data scientists working in Python on a traditional Linux or macOS host. Instead of thinking about ONTAP volumes and NFS mounts, you now work with datasets. These datasets appear as simple, named directories on your local filesystem, but they are backed by the enterprise-grade power of NetApp ONTAP.

It works great out-of-the-box when you control the host. However, a lot of data science work happens inside containers on Kubernetes. JupyterLab is one of the most common environments for that work, and Kubernetes is where many teams want to run it. So the natural question is, how do you use Dataset Manager from inside a JupyterLab pod?

Today, I'm introducing a new example that answers that question. It shows how to run Dataset Manager inside a JupyterLab container on Kubernetes, with all configuration details supplied from outside the pod. The full step-by-step implementation is on GitHub. In this post, I'll walk through the technical details and explain why the example is structured the way it is.

Dataset Manager recap


If you haven't read my Dataset Manager introduction, here's the short version.

Dataset Manager is a higher-level abstraction built on top of the NetApp DataOps Toolkit's self-service volume management functions. Instead of thinking about ONTAP volumes and NFS mounts, you work with datasets, which appear as local directories but are backed by ONTAP storage. During one-time setup, a single root ONTAP volume is created and mounted. After that, every dataset you create becomes its own ONTAP volume, and it automatically appears in your local environment as a subdirectory of that root volume.

In Python, it looks like this:
 
from netapp_dataops.traditional.datasets import Dataset

dataset = Dataset(name="my_training_data", max_size="500GB")
print(f"Data lives here: {dataset.local_file_path}")

You don't have to submit a ticket to request a volume, and you don't need to run a mount command. The dataset appears as a local directory, ready to use with pandas, NumPy, PyTorch, or whatever tools you already use.

That simplicity is what we want to preserve in Kubernetes. The challenge is getting Dataset Manager configured inside of the pod.

The initial config problem


On a traditional host, Dataset Manager setup is performed using an interactive CLI configuration wizard:

netapp_dataops_cli.py config

That wizard walks you through connecting to ONTAP and configuring the Dataset Manager root volume. It also stores your ONTAP credentials. In order to store the credentials, it expects a working OS keyring. There's also the matter of adding the mount for the root volume. It's the only mount that you need, but you (or a remote admin) still need to be able to run a mount operation to add it.

This presents multiple problems when working in a Kubernetes environment. A container image typically doesn't ship with the kind of OS keyring the toolkit expects, and you can't run a mount operation from inside a standard unprivileged Kubernetes container. Configuration and credentials have to be supplied from outside the pod before Dataset Manager can do anything useful.

The new JupyterLab example addresses these challenges by externalizing all of that initial setup:

  • An administrator creates the Dataset Manager root volume on ONTAP and exposes it to Kubernetes via a PVC (PersistentVolumeClaim).
  • Kubernetes injects Dataset Manager configuration (config.json), ONTAP credentials, and the ONTAP API TLS certificate via a Secret and ConfigMap.
  • A startup script runs before JupyterLab starts: it links config.json into place and loads credentials into a temporary in-memory keyring.

After that one-time external setup, the data scientist's experience inside the notebook is unchanged. Dataset Manager creates child datasets through the ONTAP API; new datasets appear as subdirectories under the root volume path that Kubernetes already mounted into the pod. This is the exact same root-volume architecture I described in my v3.0 introduction.

Architecture


The README for the example includes a deep dive into the architecture, but a few things are worth calling out here.

Two PVCs, one file browser


The example deliberately separates workspace storage from dataset storage:

  • /home/jovyan/work - notebooks, scripts, and other project files (ReadWriteOnce PVC from your default StorageClass)
  • /home/jovyan/datasetsDataset Manager root volume (ReadWriteMany PVC)

JupyterLab's default file browser root is /home/jovyan, so you see both directories immediately when you first log into JupyterLab. Your notebooks live in work/. Your datasets live in datasets/. Deleting the workspace PVC does not remove your datasets.

Root volume exposed via static NFS PV


Before deploying JupyterLab, you create the Dataset Manager root volume once on ONTAP. The example then exposes it to Kubernetes through a static NFS PV (PersistentVolume) and PVC with ReadWriteMany access. That lets multiple JupyterLab pods share the same datasets if you scale out later.

Custom image, minimal surface area


The example container image extends the Jupyter Docker Stacks base scipy-notebook image and adds three things: The netapp-dataops-traditional Python package, a simple Python keyring backend, and a minimal startup script. No secrets are baked into the image. Instead, everything is injected at runtime from Kubernetes primitives. The startup script is registered in Jupyter Docker Stacks' start-notebook.d hook directory so that it runs automatically before JupyterLab starts.

Credential handling without writing secrets to disk


One of the trickier parts of running the DataOps Toolkit in a container is credential management. The toolkit reads ONTAP credentials from the system keyring. In a Kubernetes environment, you need a way to securely get those credentials into the container.

The example uses two complementary techniques:

  1. Env-var injection with immediate cleanup: The ONTAP username and password secrets are injected as temporary environment variables (ONTAP_USERNAME and ONTAP_PASSWORD) from a Kubernetes Secret. They are not mounted as files. At startup, the script copies both values into the keyring, then unsets both environment variables so they don't remain in the shell environment. This means that they won't be visible to a user working in the JupyterLab UI.
  2. Memory-backed keyring: An temporary emptyDir volume with medium: Memory is mounted at /run/netapp-keyring. XDG_DATA_HOME points the keyring backend there, so the keyring file lives in RAM (tmpfs) rather than in the user's workspace or the container's writable layer.

The startup script also symlinks config.json from the Secret mount rather than copying it to persistent storage. Connection settings (hostname, SVM, data LIF, root volume name, mountpoint) live in config.json. Credentials live in the temporary in-memory keyring.

For the ONTAP API TLS certificate, the example uses a ConfigMap rather than a Secret. Mounting it from a ConfigMap means you can rotate the cert without rebuilding the container image (just update the ConfigMap and restart the pod).
 
Secret / ConfigMap key How it reaches the pod
config.json Mounted read-only at /etc/netapp-dataops/config.json
username Temporary ONTAP_USERNAME env var --> temporary in-memory keyring --> unset
password Temporary ONTAP_PASSWORD env var --> temporary in-memory keyring --> unset
ontap_cert.pem ConfigMap mounted at /etc/netapp-dataops/ontap_cert.pem

Using Dataset Manager in notebooks


Once the pod is running, the experience inside JupyterLab is the same as on a traditional host. Just open a notebook and get started:
 
from netapp_dataops.traditional.datasets import Dataset, get_datasets
import pandas as pd

# List existing datasets
print(f"Existing datasets: {len(get_datasets())}")

# Create a new dataset
training = Dataset(name="training_v1", max_size="500GB")
print(f"Dataset path: {training.local_file_path}") # /home/jovyan/datasets/training_v1

# Work with data using standard tools
df = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
df.to_parquet(f"{training.local_file_path}/data.parquet")

# Snapshot and clone for experimentation
training.snapshot(name="before_tuning")
experiment = training.clone(name="tuning_branch")

The full Dataset Manager API is available, including snapshots, clones, and file enumeration. See the Dataset Manager README for the complete reference.

Getting started


The example covers everything you need to get going:

  1. Create the Dataset Manager root volume on ONTAP
  2. Expose it to Kubernetes via a static NFS PV/PVC
  3. Build and push the custom JupyterLab image
  4. Create the Kubernetes Secret and ConfigMap for config, credentials, and TLS
  5. Deploy JupyterLab
  6. Start creating datasets in notebooks

Conclusion


Dataset Manager was designed to remove storage administration bottlenecks from the data science workflow. This example extends that simplicity into Kubernetes by moving initial configuration out of the pod and into the platform, where it belongs. Once that's done, the toolkit handles dataset lifecycle management through the ONTAP API, and you get the same Python-native, filesystem-based experience you would get outside of Kubernetes. You can create a dataset, work with it like any directory, and snapshot and clone as needed.

If you're running data science on Kubernetes with NetApp ONTAP storage, give it a try and let us know what you think. If you have any suggestions for improving the example, be sure to let us know. The full NetApp DataOps Toolkit, including Dataset Manager, is open-source and available at on GitHub.
Public