Skip to main content

资源优化

Kubernetes 的一大好处就是促进资源充分利用,在 JuiceFS CSI 驱动中,也有不少方面可以做资源占用优化,甚至带来一定的性能提升。在这里集中罗列介绍。

配置资源请求和约束​

每一个使用着 JuiceFS PV 的容器,都对应着一个 Mount Pod(会智能匹配和复用),因此为 Mount Pod 配置合理的资源声明,将是最有效的优化资源占用的手段。关于配置资源请求(request)和约束(limit),可以详读 Kubernetes 官方文档,此处不赘述。

JuiceFS Mount Pod 的 requests 默认为 1 CPU 和 1GiB Memory,limits 默认为 5 CPU 和 5GiB Memory。考虑到 JuiceFS 的使用场景多种多样,1C1G 的资源请求可能不一定适合你的集群,调整的时候需要注意:

  • 不论 requests 如何调节,limits.cpu 都不建议低于 4,过低的 CPU 限制会系统性降低 JuiceFS 挂载点的性能,即便在监控中发现实际资源用量很低,也应该保留比较宽容的 limits.cpu,默认为 5;
  • requests 应该根据实际资源用量进行调整,比如通过监控发现 Mount Pod 只使用了 0.1 CPU、100MiB Memory,应该尊重实际监控数据,将 requests 调整为 0.1 CPU,100MiB Memory,避免过大的 requests 造成资源闲置,甚至导致容器无法调度,或者抢占其他应用容器(Preemption);
  • 通常情况下,不建议 requests 和 limits 差距过大。如果实际用量高于 requests,这便是「资源超售」(Overcommitment),严重的超售会影响集群稳定性。比方说 Mount Pod 的 requests 是 1 CPU、1GiB 内存,limits 是 16 CPU、64GiB 内存,实际资源用量达到 8 CPU、20 GiB 内存,此时虽然根据 requests 的定义,允许容器调度到节点上,但实际资源占用远高于 requests,节点可能出现各种资源挤占的问题,比如 CPU Throttle、OOM。因此这种情况下,应该根据实际用量,将 requests 调整为实际用量。

如果你安装了 Kubernetes Metrics Server,可以方便地用类似下方命令查看 CSI 驱动组件的实际资源占用:

# 查看 Mount Pod 实际资源占用
kubectl top pod -n kube-system -l app.kubernetes.io/name=juicefs-mount

# 查看 CSI Controller,CSI Node 实际资源占用,依同样的原理调整其资源声明
kubectl top pod -n kube-system -l app.kubernetes.io/name=juicefs-csi-driver

在 ConfigMap 中配置资源声明​

从 v0.24 开始,CSI 驱动支持在 ConfigMap 中定制 Mount Pod 和 sidecar 容器,修改资源定义非常简便:

values-mycluster.yaml
globalConfig:
mountPodPatch:
- resources:
requests:
cpu: 100m
memory: 512Mi

修改完毕以后,滚动升级应用或者删除重建 Mount Pod,便会按照新的资源定义重建。

按比例配置 sidecar 资源声明​

从 v0.32.0 开始,如果使用 Sidecar 模式,可以将 sidecar 容器资源配置为使用同一个 PVC 的应用容器资源的一定比例:

values-mycluster.yaml
globalConfig:
mountPodPatch:
- resourcePercentages:
requests:
cpu: "50%"
memory: "50%"
limits:
cpu: "80%"
memory: "80%"
mountOptions:
- buffer-size=50%

resourcePercentages.requests 根据应用容器的资源请求计算,resourcePercentages.limits 根据应用容器的资源限制计算。如果多个应用容器挂载了同一个 PVC,会先累加这些容器的资源,再按比例计算。该配置只支持 cpu 和 memory。

如果计算出来的 limits 太小,会使用 minLimits 作为下限。未设置 minLimits 时,默认下限是 100m CPU 和 300Mi 内存。如果 minLimits 设置过低,mount 容器可能无法启动;实际所需的最小资源可能和文件系统规模有关。

如果需要自定义下限,可以单独设置:

resourcePercentages:
minLimits:
cpu: 500m
memory: 512Mi

buffer-size 挂载参数也支持百分比。buffer-size=50% 会按照 mount 容器或 sidecar 容器最终的 memory limit 计算,并转换为 MiB 后传给 JuiceFS。如果百分比超过 100%,会按 memory limit 封顶。

在 PVC 配置资源声明(不推荐)​

提示

从 v0.24 开始,CSI 驱动支持在 ConfigMap 中定制 Mount Pod 和 sidecar 容器,本小节所介绍的方式已经不再推荐使用。

自 0.23.4 开始,在 PVC 的 annotations 中可以自由配置资源声明,由于 annotations 可以随时更改,因此这种方式也能灵活地调整资源定义。但也要注意:

  • 修改以后,已有的 Mount Pod 并不会自动按照新的配置重建。需要删除 Mount Pod,才能以新的资源配置触发创建新的 Mount Pod。
  • 必须配置好挂载点自动恢复,重建后 Mount Pod 的挂载点才能传播回应用 Pod。
  • 就算配置好了挂载点自动恢复,重启过程也会造成服务闪断,注意在应用空间做好错误处理。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
annotations:
juicefs/mount-cpu-request: 100m
juicefs/mount-cpu-limit: "1" # 数字必须以引号封闭,作为字符串传入
juicefs/mount-memory-request: 500Mi
juicefs/mount-memory-limit: 1Gi
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi

留空资源设置​

如果需要在 Mount Pod 中省略特定的 resources 字段(不填 requests 或者 limits),那么可以将对应字段设置为“0”:

juicefs/mount-cpu-limit: "0"
juicefs/mount-memory-limit: "0"
# 如果 Mount Pod 静息资源用量较低,请参考下方设为极低值,勿设置为 0
juicefs/mount-cpu-requests: "1m"
juicefs/mount-memory-requests: "4Mi"

应用配置以后,新创建的 Mount Pod 会将“0”解释为省略不填,最终效果如下:

resources:
requests:
cpu: 1m
memory: 4Mi

之所以不建议将 requests 设置为“0”,原因是 Kubernetes 在面对缺失 requests 时,会将其解释为与 limits 相等,也就是说,如果你设置了如下 Mount Pod 资源:

# 错误示范,请勿模仿
juicefs/mount-cpu-limit: "32"
juicefs/mount-memory-limit: "64Gi"
# 将 requests 设为 0,导致 csi-node 将其删去、省略不填
juicefs/mount-cpu-requests: "0"
juicefs/mount-memory-requests: "0"

那么按照 requests = limits 的规则,最终渲染结果往往不符合用户预期,也无法创建 Mount Pod,造成资源创建失败。

resources:
limits:
cpu: 32
memory: 64Gi
requests:
cpu: 32
memory: 64Gi

其他方式(不推荐)​

注意

推荐优先使用上方介绍的 PVC annotations 方式,这种方式支持动态变更,所以是我们更为推荐的方式。而下方介绍的方式一旦设置成功,就无法修改,只能删除重建 PV,已不再推荐使用。

静态配置中,可以在 PersistentVolume 中配置资源请求和约束:

apiVersion: v1
kind: PersistentVolume
metadata:
name: juicefs-pv
labels:
juicefs-name: ten-pb-fs
spec:
capacity:
storage: 10Pi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: csi.juicefs.com
volumeHandle: juicefs-pv
fsType: juicefs
nodePublishSecretRef:
name: juicefs-secret
namespace: default
volumeAttributes:
juicefs/mount-cpu-limit: 5000m
juicefs/mount-memory-limit: 5Gi
juicefs/mount-cpu-request: 100m
juicefs/mount-memory-request: 500Mi

动态配置中,可以在 StorageClass 中配置资源请求和约束:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: juicefs-sc
provisioner: csi.juicefs.com
parameters:
csi.storage.k8s.io/provisioner-secret-name: juicefs-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/node-publish-secret-name: juicefs-secret
csi.storage.k8s.io/node-publish-secret-namespace: default
juicefs/mount-cpu-limit: 5000m
juicefs/mount-memory-limit: 5Gi
juicefs/mount-cpu-request: 100m
juicefs/mount-memory-request: 500Mi

在 0.23.4 以及之后的版本中,由于支持参数模板化,因此可以在 StorageClass 的 parameters 字段中引用 PVC 的注解:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: juicefs-sc
provisioner: csi.juicefs.com
parameters:
...
juicefs/mount-cpu-limit: ${.pvc.annotations.csi.juicefs.com/mount-cpu-limit}
juicefs/mount-memory-limit: ${.pvc.annotations.csi.juicefs.com/mount-memory-limit}
juicefs/mount-cpu-request: ${.pvc.annotations.csi.juicefs.com/mount-cpu-request}
juicefs/mount-memory-request: ${.pvc.annotations.csi.juicefs.com/mount-memory-request}

需要注意,由于已经支持在 PVC annotations 定义 Mount Pod 资源,已不需要用到此配置方法。

如果你使用 Helm 管理 StorageClass,则直接在 values.yaml 中定义:

values.yaml
storageClasses:
- name: juicefs-sc
enabled: true
...
mountPod:
resources:
requests:
cpu: "100m"
memory: "500Mi"
limits:
cpu: "5"
memory: "5Gi"

为 Mount Pod 设置合理的缓存大小​

在云环境中,节点通常会设置驱逐信号,其中 nodefs.available 为节点的根文件系统的可用磁盘空间。JuiceFS 默认缓存大小为 100GiB,默认的缓存目录的最小剩余空间占比 free-space-ratio 为 0.1,默认的缓存大小很可能触发节点驱逐。建议根据节点的实际磁盘空间,设置合理的缓存大小。

缓存大小 cache-size 和缓存目录的最小剩余空间占比 free-space-ratio 可以在挂载参数中设置,具体参考挂载参数一节。

为 Mount Pod 设置非抢占式 PriorityClass​

提示
  • 建议默认为 Mount Pod 设置非抢占式 PriorityClass
  • 如果 CSI 驱动的运行模式为「Sidecar 模式」,则不会遇到以下问题。

CSI Node 在创建 Mount Pod 时,会默认给其设置 PriorityClass 为 system-node-critical,目的是为了在机器资源不足时,Mount Pod 不会被驱逐。

但在 Mount Pod 创建时,若机器资源不足,system-node-critical 会使得调度器为 Mount Pod 开启抢占,此时可能会影响到节点上已有的业务。若不希望现有的业务被影响,可以设置 Mount Pod 的 PriorityClass 为非抢占式的,具体方式如下:

  1. 在集群中创建一个非抢占式 PriorityClass,更多 PriorityClass 信息参考官方文档:

    apiVersion: scheduling.k8s.io/v1
    kind: PriorityClass
    metadata:
    name: juicefs-mount-priority-nonpreempting
    value: 1000000000 # 值越大,优先级越高,范围为 -2,147,483,648 到 1,000,000,000(含)。应尽可能大,确保 Mount Pod 不会被驱逐
    preemptionPolicy: Never # 非抢占式
    globalDefault: false
    description: "This priority class used by JuiceFS Mount Pod."
  2. 为 CSI Node Service 和 CSI Controller Service 添加 JUICEFS_MOUNT_PRIORITY_NAME 这个环境变量,值为上述 PriorityClass 名,同时添加环境变量 JUICEFS_MOUNT_PREEMPTION_POLICY 为 Never,设置 Mount Pod 的抢占策略为 Never:

    kubectl -n kube-system set env -c juicefs-plugin daemonset/juicefs-csi-node JUICEFS_MOUNT_PRIORITY_NAME=juicefs-mount-priority-nonpreempting JUICEFS_MOUNT_PREEMPTION_POLICY=Never
    kubectl -n kube-system set env -c juicefs-plugin statefulset/juicefs-csi-controller JUICEFS_MOUNT_PRIORITY_NAME=juicefs-mount-priority-nonpreempting JUICEFS_MOUNT_PREEMPTION_POLICY=Never

为相同的 StorageClass 复用 Mount Pod​

备注

使用静态 PV 时,该方法不可用。

默认情况下,仅在多个应用 Pod 使用相同 PV 时,Mount Pod 才会被复用。如果你希望进一步降低开销,可以更加激进地复用 Mount Pod,让使用相同 StorageClass 创建出来的所有 PV,都复用同一个 Mount Pod(当然了,复用只能发生在同一个节点)。不同的应用 Pod,将会绑定挂载点下不同的路径,实现一个挂载点为多个应用容器提供服务。

为相同 StorageClass PV 复用 Mount Pod,需要为 CSI Node Service 添加 STORAGE_CLASS_SHARE_MOUNT 这个环境变量:

kubectl -n kube-system set env -c juicefs-plugin daemonset/juicefs-csi-node STORAGE_CLASS_SHARE_MOUNT=true

或者采用 Helm 安装时,在 values.yaml 中添加如下配置:

values.yaml
node:
# When set true, enable application pods using same sc share the same mount pod
storageClassShareMount: true

可想而知,高度复用意味着更低的隔离程度,如果 Mount Pod 发生意外,挂载点异常,影响面也会更大,因此如果你决定启用该复用策略,请务必同时启用「挂载点自动恢复」,以及合理增加 「Mount Pod 的资源请求」。

为相同的 File System 复用 Mount Pod 新增自 v0.30.0​

StorageClass 复用的粒度是 StorageClass,如果你有多个 StorageClass 指向同一个 JuiceFS 文件系统,或者使用静态 PV,那么它们会创建出不同的 Mount Pod。

如果你希望更进一步降低开销,可以让使用相同 JuiceFS 文件系统的所有 PV,都复用同一个 Mount Pod(当然了,复用只能发生在同一个节点)。

为相同 File System PV 复用 Mount Pod,需要为 CSI Node Service 添加 FS_SHARE_MOUNT 这个环境变量:

kubectl -n kube-system set env -c juicefs-plugin daemonset/juicefs-csi-node FS_SHARE_MOUNT=true

或者才用 Helm 安装时,在 values.yaml 中添加如下配置:

values.yaml
node:
fsShareMount: true
备注

当同一个文件系统中的配置不同时,则可能不会被复用,比如:

  • 不同的挂载参数(Mount Options)。
  • 社区版中多个 meta 对应多个同名的文件系统。则会 fallback 到基于 PV 的复用。
  • 私有部署场景中有多个集群,但是文件名称相同的文件系统。则会 fallback 到基于 PV 的复用。

配置 Mount Pod 退出时清理缓存​

详见「缓存相关章节」。

延迟删除 Mount Pod​

Mount Pod 是支持复用的,由 JuiceFS CSI Node 以引用计数的方式进行管理:当没有任何应用 Pod 在使用该 Mount Pod 创建出来的 PV 时,JuiceFS CSI Node 会删除 Mount Pod。

但在不少场景中调度频繁,容器转瞬即逝,这时可以为 Mount Pod 配置延迟删除,这样一来,如果短时间内还有新应用 Pod 使用相同的 Volume,Mount Pod 能够被继续复用,免除了反复销毁创建的开销。

控制延迟删除 Mount Pod 的配置项形如 juicefs/mount-delete-delay: 1m,单位支持 ns(纳秒)、us(微秒)、ms(毫秒)、s(秒)、m(分钟)、h(小时)。

配置好延迟删除后,当引用计数归零,Mount Pod 会被打上 juicefs-delete-at 的注解(annotation),标记好删除时间,到达设置的删除时间后,Mount Pod 才会被删除。但如果在此期间有新的应用 Pod 欲使用该 PV,注解 juicefs-delete-at 就被清空,Mount Pod 的删除计划随之取消,得以继续复用。

配置方法​

建议通过 ConfigMap 来为 Mount Pod 配置延迟删除。

values-mycluster.yaml
globalConfig:
mountPodPatch:
# 为指定 PVC 设置延迟删除
- pvcSelector:
matchLabels:
mylabel1: "value1"
annotations:
juicefs-delete-delay: 5m

# 为所有 PVC 设置延迟删除
- annotations:
juicefs-delete-delay: 5m

如果你仍在使用旧版 CSI 驱动,无法配置 ConfigMap,那么需要继续使用旧版的配置方法。取决于你所使用的是动态还是静态 PV,需要在不同的地方填写配置。

静态配置需要在 PV 定义中配置延迟删除的时长,修改 volumeAttributes 字段,添加 juicefs/mount-delete-delay,设置为需要的时长:

apiVersion: v1
kind: PersistentVolume
metadata:
name: juicefs-pv
labels:
juicefs-name: ten-pb-fs
spec:
capacity:
storage: 10Pi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: csi.juicefs.com
volumeHandle: juicefs-pv
fsType: juicefs
nodePublishSecretRef:
name: juicefs-secret
namespace: default
volumeAttributes:
juicefs/mount-delete-delay: 1m

动态配置则需要在 StorageClass 定义中配置延迟删除的时长,修改 parameters 字段,添加 juicefs/mount-delete-delay,设置为需要的时长:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: juicefs-sc
provisioner: csi.juicefs.com
parameters:
csi.storage.k8s.io/provisioner-secret-name: juicefs-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/node-publish-secret-name: juicefs-secret
csi.storage.k8s.io/node-publish-secret-namespace: default
juicefs/mount-delete-delay: 1m

PV 回收策略​

回收策略决定了 PVC 或 PV 被删除后,存储里的数据何去何从。常用的回收策略是保留(Retain)和删除(Delete),保留回收策略需要用户自己回收资源(包括 PV、JuiceFS 上的数据),而删除回收策略则意味着 PV 及 JuiceFS 上的数据会随着 PVC 删除而直接清理掉。

静态配置​

静态配置中,只支持 Retain 回收策略:

apiVersion: v1
kind: PersistentVolume
metadata:
name: juicefs-pv
labels:
juicefs-name: ten-pb-fs
spec:
capacity:
storage: 10Pi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: csi.juicefs.com
volumeHandle: juicefs-pv
fsType: juicefs
nodePublishSecretRef:
name: juicefs-secret
namespace: default

动态配置​

动态配置默认的回收策略为 Delete,可以在 StorageClass 定义中修改为 Retain:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: juicefs-sc
provisioner: csi.juicefs.com
reclaimPolicy: Retain
parameters:
csi.storage.k8s.io/provisioner-secret-name: juicefs-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/node-publish-secret-name: juicefs-secret
csi.storage.k8s.io/node-publish-secret-namespace: default

仅在某些节点上运行 CSI Node Service​

JuiceFS CSI 驱动的组件分为 CSI Controller、CSI Node Service 及 Mount Pod,详细可参考 JuiceFS CSI 驱动架构。

默认情况下,CSI Node Service(DaemonSet)会在所有 Kubernetes 节点上启动,如果希望进一步减少资源占用,则可按照本节介绍的方式,让 CSI Node 仅在实际需要使用 JuiceFS 的节点上启动。

配置节点标签​

先为需要使用 JuiceFS 的节点加上相应的标签,比方说为执行模型训练的节点打上标签:

# 根据实际情况为 Kubernetes 节点加上标签
kubectl label node [node-1] [node-2] app=model-training

修改 JuiceFS CSI 驱动安装配置​

除了 nodeSelector,Kubernetes 还提供更多方式控制容器调度,参考将 Pod 指派给节点。

注意

如果使用 nodeSelector 将 CSI-node 驱动部署到选定的节点,那么使用了 JuiceFS PV 的应用,也需要加上相同的 nodeSelector,才能保证分配到能够提供文件系统服务的节点上。

通过 Helm 安装​

在 values.yaml 中添加如下配置:

values.yaml
node:
nodeSelector:
# 根据实际情况修改节点标签
app: model-training

安装 JuiceFS CSI 驱动:

helm install juicefs-csi-driver juicefs/juicefs-csi-driver -n kube-system -f ./values.yaml

通过 kubectl 安装​

在 k8s.yaml 中新增 nodeSelector 配置:

k8s.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: juicefs-csi-node
namespace: kube-system
...
spec:
...
template:
spec:
nodeSelector:
# 根据实际情况修改节点标签
app: model-training
containers:
- name: juicefs-plugin
...
...

安装 JuiceFS CSI 驱动:

kubectl apply -f k8s.yaml