自定义用户存储#

在本指南中,我们将把“存储”描述为一个“卷” (volume) - 用户数据所在的磁盘上的一个位置。

Kubernetes 负责持久卷的创建和分配,它在底层使用云提供商的 API 来发出相应的命令。因此,我们关于卷的大部分讨论将描述 Kubernetes 对象。

JupyterHub 使用 Kubernetes 来管理用户存储。主要有两种 Kubernetes 对象参与为 Pod 分配存储:

  • 一个 PersistentVolumeClaim (PVC),即持久卷声明,它指定了需要哪种类型的存储。其配置在你的 config.yaml 文件中指定。

  • 一个 PersistentVolume (PV),即持久卷,是用户数据实际所在的卷。它由 Kubernetes 使用 PVC 中的详细信息创建。

作为 Kubernetes 对象,它们可以通过标准的 kubectl 命令进行查询(例如 kubectl --namespace=<your-namespace> get pvc)。

在 JupyterHub 中,每个用户都有自己的 PersistentVolumeClaim 对象,代表附加到其账户的数据。当一个新用户启动他们的 JupyterHub 服务器时,会为该用户创建一个 PersistentVolumeClaim。这个声明告诉 Kubernetes 需要什么类型的存储(例如,SSD vs. HDD)以及需要多大的存储空间。Kubernetes 会检查是否存在该用户的 PersistentVolume 对象(因为这是一个新用户,所以不会存在)。如果没有 PV 对象存在,那么 Kubernetes 将使用 PVC 为该用户创建一个新的 PV 对象。

现在用户的 PV 已经存在,Kubernetes 接下来必须将该 PV 附加(或“挂载”)到用户的 Pod(运行用户代码的容器)上。一旦完成,用户就可以在 JupyterHub 中访问他们的 PV 了。请注意,当用户登录时,这一切都是在后台自动发生的。

PersistentVolumeClaims 和 PersistentVolumes 不会被删除,除非 JupyterHub 管理员明确删除了 PersistentVolumeClaim。当用户关闭他们的服务器时,他们的用户 Pod 会被删除,他们的卷会从 Pod 中分离,但是 PVCPV 对象仍然存在。将来,当用户再次登录时,JupyterHub 会检测到该用户有一个预先存在的 PVC,并会简单地将其附加到他们的新 Pod 上,而不是创建一个新的 PVC

这个过程可能会出现什么问题?#

当 Kubernetes 使用 PVC 为新用户创建一个 PV 时,它实际上是在向 Kubernetes 运行所在的云提供商的底层 API 发送命令。有时,对特定 PV 的请求可能会失败——例如,如果你的账户已达到可用磁盘空间的总量限制。

另一个常见问题是,集群中单个节点可以同时挂载的卷的数量有限。请查阅你的云提供商文档,了解你所请求的存储资源的限制详情。

注意

一些云提供商对每个节点可挂载的磁盘数量有限制。由于 JupyterHub 为每个用户分配一个磁盘用于持久化存储,这就限制了在任何时间点一个节点上可以运行的用户数量。如果你需要用户拥有持久化存储,并且最终达到了这个限制,你必须使用更多的节点来容纳每个用户的磁盘。在这种情况下,我们建议为每个节点分配更少的资源(例如 RAM),因为你将在单个节点上容纳更少的用户。

配置#

大多数存储配置是在集群级别完成的,并非 JupyterHub 独有。然而,有些部分是 JupyterHub 特有的,我们将在这里演示如何配置它们。

请注意,对于已存在的用户,除非销毁旧的 PVC,否则不会创建新的 PVC。如果你通过 config.yaml 更新了用户的 PVC 配置,那么任何用户都会为他们创建新的 PVC,但用户则不会。要为旧用户强制升级存储类型,你需要手动删除他们的 PVC(例如 kubectl --namespace=<your-namespace> delete pvc <pvc-name>)。这将删除该用户的所有数据,因此如果你想保留他们的数据,我们建议先备份他们的文件系统。

在你删除了用户的 PVC 之后,当他们下次登录时,系统会根据你更新后的 PVC 规范为他们创建一个新的 PVC

置备的存储类型#

StorageClass 对象用于确定为你的用户置备哪种类型的 PersistentVolume。大多数主流的云提供商都有一个标记为默认的 StorageClass。你可以通过执行以下命令来找出你的默认 StorageClass

kubectl get storageclass

然后寻找名称旁边带有 (default) 标记的对象。

要更改为用户置备的 PersistentVolume 的类型,请执行以下步骤:

  1. 遵循 Kubernetes 文档创建一个新的 StorageClass 对象。

  2. 在你的 config.yaml 文件中指定你刚刚创建的 StorageClass 的名称。

    singleuser:
      storage:
        dynamic:
          storageClass: <storageclass-name>
    
  3. 执行一次 helm upgrade

请注意,这只会影响新登录的用户。我们建议你在用户开始大量使用你的集群之前完成此操作。

我们将在这里提供主流云提供商的示例,但通常会参考 Kubernetes 的文档。

Google Cloud#

在 Google Cloud 上,默认的 StorageClass 将置备标准的 Google 持久性磁盘。这些磁盘运行在机械硬盘(Hard Disk)上。为了获得更好的性能,你可能希望使用固态硬盘(SSD)。要使用 SSD,你可以创建一个新的 StorageClass,首先将以下 yaml 内容放入一个新文件中。我们建议使用一个描述性的名称,如 storageclass.yaml,我们将在下面使用这个名称。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: jupyterhub-user-ssd
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
allowedTopologies:
  - matchLabelExpressions:
      - key: failure-domain.beta.kubernetes.io/zone
        values:
          - <your-cluster-zone>

请将 <your-cluster-zone> 替换为你创建集群时所在的可用区(Zone)(你可以通过 gcloud container clusters list 找到)。

接下来,通过在命令行运行 kubectl apply -f storageclass.yaml 来创建这个对象。Kubernetes 文档中有更多关于各个字段含义的信息。最重要的字段是 parameters.type,它指定了你希望使用的存储类型。有两个选项:

  • pd-ssd 使 StorageClass 置备 SSD。

  • pd-standard 将置备非 SSD 的磁盘。

一旦你创建了这个 StorageClass,你就可以在你的 config.yaml 文件中通过以下配置来配置 JupyterHub 的 PVC 模板:

singleuser:
  storage:
    dynamic:
      storageClass: jupyterhub-user-ssd

请注意,对于 storageClass:,我们使用的是我们在上面的 metadata.name 中指定的名字。

置备的存储大小#

你可以在你的 config.yaml 文件的 PVC 配置中设置 JupyterHub 请求的存储大小。

singleuser:
  storage:
    capacity: 2Gi

这将为每个用户请求一个 2Gi 的卷。默认设置为每个用户请求一个 10Gi 的卷。

我们建议你使用 IEC 二进制前缀(Ki、Mi、Gi 等)来指定你想要的存储量。2Gi (IEC 二进制前缀) 是 (2 * 1024 * 1024 * 1024) 字节,而 2G (SI 十进制前缀) 是 (2 * 1000 * 1000 * 1000) 字节。

关闭每个用户的持久化存储#

如果你不希望用户拥有任何持久化存储,可以将其关闭。编辑 config.yaml 文件,并将存储类型设置为 none

singleuser:
  storage:
    type: none

接下来应用更改

更改应用后,新用户将不再被分配持久化的 $HOME 目录。任何当前正在运行的用户在他们的服务器重启之前仍然可以访问他们的存储。你可能需要使用 kubectl 手动删除当前用户的 PVC,以回收可能已经分配的云磁盘。你可以通过以下命令获取当前的 PVC 列表:

kubectl --namespace=<your-namespace> get pvc

然后你可以使用以下命令删除你不需要的 PVC

kubectl --namespace=<your-namespace> delete pvc <pvc-name>

请记住,删除某人的 PVC 将会删除他们所有的数据,所以请谨慎操作!

额外的存储卷#

如果你已经有一个在 JupyterHub 之外创建的 PersistentVolumePersistentVolumeClaim,你可以将它们挂载到用户 Pod 内部。例如,如果你有一个名为 jupyterhub-shared-volume 的共享 PersistentVolumeClaim,你可以将它作为 /home/shared 挂载到所有用户 Pod 中:

singleuser:
  storage:
    extraVolumes:
      - name: jupyterhub-shared
        persistentVolumeClaim:
          claimName: jupyterhub-shared-volume
    extraVolumeMounts:
      - name: jupyterhub-shared
        mountPath: /home/shared

请注意,如果你想将一个卷挂载到多个 Pod 中,该卷必须支持合适的访问模式