在 Kubernetes 中使用 JuiceFS

JuiceFS 可以作为存储卷(Volume)挂载到 Kubernetes Pod 中作为持久化存储。

相比于相比普通云盘(如 AWS EBS,阿里云弹性云盘等),JuiceFS 支持更多适合云环境使用的特性:

  • 支持 ReadWriteMany 访问模式,可以同时挂载到多个 Pod 作为共享存储并发读写,

  • 文件系统挂载不受可用区限制,在单可用区失效情况下,支持工作负载的跨可用区迁移,实现高可用

  • 弹性容量高达 10Pi,无需进行扩容操作

  • 支持快速拷贝,生成数据副本用于离线调测(内测中)

在 Kubernetes 中使用 JuiceFS 可以通过以下三种方式:

  • CSI (推荐用于 Kubernetes v1.13 及以上版本)

  • flexVolume (适用于 Kubernetes v1.2 及以上版本)

  • hostPath (可用于所有 Kubernetes 版本)

针对不同的使用场景,可以选择合适的集成方式。

通过 CSI 存储卷

JuiceFS CSI 驱动 实现了 容器存储接口(CSI) 规范,用于在容器编排器中管理 JuiceFS 文件系统生命周期。

安装 JuiceFS CSI 驱动

将驱动部署到 Kubernetes 集群:

  1. kubectl apply -f https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s.yaml

在 Kubernetes 集群中部署完 JuiceFS CSI 驱动后,即可通过熟悉的 Kubernetes API 对象 PersistentVolumePersistentVolumesStorageClass 来使用 JuiceFS。

升级 JuiceFS CSI 驱动

JuiceFS CSI 驱动包含两个组件需要升级:

  • JuiceFS CSI 驱动本身

  • JuiceFS CSI 驱动中包含的 JuiceFS 客户端

升级 JuiceFS CSI 驱动

  1. 停止所有正在使用 JuiceFS CSI 驱动的 Pod

  2. 升级 JuiceFS CSI 驱动的容器

    • 如果你正在使用带有 latest 标签的 JuiceFS CSI 驱动的镜像, 那么可以直接运行

      1. kubectl rollout restart -f k8s.yaml

      并且确保 juicefs-csi-controllerjuicefs-csi-node 这两个 Pod 重启了.

    • 如果你把 JuiceFS CSI 驱动的镜像固定到了某个版本, 那么直接修改你的 k8s.yaml 文件, 改成新的版本, 然后

      1. kubectl apply -f k8s.yaml

      例如从 0.3.0 版本升级到 0.4.0:

      1. $ sed -i 's+juicedata/juicefs-csi-driver:0.3.0+juicedata/juicefs-csi-driver:0.4.0+g' k8s.yaml
      2. $ cat k8s.yaml
      3. ...
      4. ---
      5. apiVersion: apps/v1
      6. kind: StatefulSet
      7. ...
      8. spec:
      9. containers:
      10. - args:
      11. - --endpoint=$(CSI_ENDPOINT)
      12. - --logtostderr
      13. - --nodeid=$(NODE_ID)
      14. - --v=5
      15. env:
      16. - name: CSI_ENDPOINT
      17. value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock
      18. image: juicedata/juicefs-csi-driver:0.4.0
      19. ...
      20. $ kubectl apply -f k8s.yaml

升级 JuiceFS CSI 驱动里的 JuiceFS 客户端

我们的 JuiceFS CSI 驱动镜像现在支持自动升级里面包含的 JuiceFS 客户端, 自动更新可以关闭. 如果处于打开状态, 那么每次把 PersistentVolume 挂载到 Pod 的时候都会自动更新客户端. 我们同样提供了不同的方式来控制这个开关:

  • 如果你正在使用带有 latest 标签的 JuiceFS CSI 驱动的镜像, 那么自动更新默认是打开的, 每次在挂载的时候都会尝试更新, 如果不用更新那么只会消耗极短的检测时间.

  • 如果你把 JuiceFS CSI 驱动的镜像固定到了某个版本, 那么自动更新默认是关闭的. 可以通过环境变量的方式打开:

    • 我们提供了以下两个环境变量来设置自动更新:

      • JFS_AUTO_UPGRADE: 如果设置了任意值表示自动更新打开, 否则是关闭

      • JFS_AUTO_UPGRADE_TIMEOUT: 用来表示自动更新最多会花费的时间, 超时就会取消更新, 单位是秒, 默认值是 10

    • 那么只要在部署 JuiceFS CSI controller 驱动的时候配置好这个环境变量就可以了, 例如:

      1. $ cat k8s.yaml
      2. ...
      3. ---
      4. apiVersion: apps/v1
      5. kind: StatefulSet
      6. ...
      7. spec:
      8. containers:
      9. - args:
      10. - --endpoint=$(CSI_ENDPOINT)
      11. - --logtostderr
      12. - --nodeid=$(NODE_ID)
      13. - --v=5
      14. env:
      15. - name: CSI_ENDPOINT
      16. value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock
      17. - name: JFS_AUTO_UPGRADE
      18. value: "true"
      19. - name: JFS_AUTO_UPGRADE_TIMEOUT
      20. value: "15"
      21. image: juicedata/juicefs-csi-driver:0.4.0
      22. ...
      23. $ kubectl apply -f k8s.yaml

      注意这种方式还是需要重新部署一次 JuiceFS CSI 驱动来保证 controller 和 node 驱动都被应用了环境变量

在 JuiceFS 中动态创建存储卷

JuiceFS CSI 驱动支持根据指定 StorageClass 按需提供存储,例如:

  1. apiVersion: storage.k8s.io/v1
  2. kind: StorageClass
  3. metadata:
  4. name: juicefs-sc
  5. namespace: default
  6. provisioner: csi.juicefs.com
  7. parameters:
  8. csi.storage.k8s.io/provisioner-secret-name: juicefs-secret
  9. csi.storage.k8s.io/provisioner-secret-namespace: default
  10. csi.storage.k8s.io/node-publish-secret-name: juicefs-secret
  11. csi.storage.k8s.io/node-publish-secret-namespace: default

其中所用的密钥 juicefs-secret 必须包含访问 JuiceFS 所需的 nametokenaccesskeysecretkey。这些信息可以在 JuiceFS 控制中心 获取。文件系统目前需要手工创建,用于满足对该 StorageClass 的存储请求。每一个创建的持久化存储卷对应于 JuiceFS 里的一个子目录。

在 Kubernetes 中创建 Secret 的示例命令如下:

  1. kubectl create secret generic juicefs-secret \
  2. --from-literal=name=${JUICEFS_NAME} \
  3. --from-literal=token=${JUICEFS_TOKEN} \
  4. --from-literal=accesskey=${JUICEFS_ACCESSKEY} \
  5. --from-literal=secretkey=${JUICEFS_SECRETKEY}

存储卷的动态供给由 PersistenVolumeClaim 对象的创建触发。例如以下代码将发起对 juicefs-sc 类型存储的请求:

  1. ---
  2. apiVersion: v1
  3. kind: PersistentVolumeClaim
  4. metadata:
  5. name: juicefs-pvc
  6. namespace: default
  7. spec:
  8. accessModes:
  9. - ReadWriteMany
  10. resources:
  11. requests:
  12. storage: 10Pi
  13. storageClassName: juicefs-sc

系统监测到对 juicefs-sc 类型存储的请求后,会连同 StorageClass 中引用的密钥一起向 JuiceFS 插件发起 CreateVolume 调用。JuiceFS 插件将临时挂载所配置的文件系统,并创建独立的子目录供给该存储卷,随后自动创建一个 PersistentVolume 对象来代表新创建的存储卷。Kubernetes 监测到 PersistenVolume 的创建,将其绑定到 PersistentVolumeClaim 中。

动态供给适用于为应用程序提供一个新的持久化存储空间的场景,如果想访问 JuiceFS 里的现有内容,参见下节。

注解

由于 JuiceFS 是个弹性文件系统,本身并没有容量限制,PVC 和 PV 里存储容量并不会用于创建文件系统。 但是由于 Kubernetes 中这是个必选参数,所以必须指定一个值,可以是任意有效值,例如 10Pi。

加载 JuiceFS 中现有内容

对于已经保存在 JuiceFS 中的数据,可以手动创建 PersistentVolume 提供给 Kubernetes 集群中的应用访问,示例如下:

  1. ---
  2. apiVersion: v1
  3. kind: PersistentVolume
  4. metadata:
  5. name: juicefs-pv
  6. labels:
  7. juicefs-name: ten-pb-fs
  8. spec:
  9. capacity:
  10. storage: 10Pi
  11. volumeMode: Filesystem
  12. accessModes:
  13. - ReadWriteMany
  14. persistentVolumeReclaimPolicy: Retain
  15. csi:
  16. driver: csi.juicefs.com
  17. volumeHandle: juicefs-name
  18. fsType: juicefs
  19. nodePublishSecretRef:
  20. name: juicefs-secret
  21. namespace: default

其中 volumeHandle 为需要访问的 JuiceFS 文件系统名称,juicefs-secret 里包含访问该文件系统所需的 nametokenaccesskeysecretkey 等信息。

  1. ---
  2. apiVersion: v1
  3. kind: PersistentVolumeClaim
  4. metadata:
  5. name: juicefs-pvc
  6. namespace: default
  7. spec:
  8. accessModes:
  9. - ReadWriteMany
  10. volumeMode: Filesystem
  11. resources:
  12. requests:
  13. storage: 10Pi
  14. selector:
  15. matchLabels:
  16. juicefs-name: ten-pb-fs

在这个例子中,PersistentVolumeClaim 通过 labels 来选择对应的 PersistentVolume

在应用里访问 JuiceFS

无论是通过动态创建还是加载已有内容,在 PersistentVolumeClaim 准备就绪后,应用即可将其作为存储卷使用,示例如下:

  1. ---
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: juicefs-app
  6. namespace: default
  7. spec:
  8. containers:
  9. - args:
  10. - -c
  11. - while true; do echo $(date -u) >> /data/out.txt; sleep 5; done
  12. command:
  13. - /bin/sh
  14. image: centos
  15. name: app
  16. volumeMounts:
  17. - mountPath: /data
  18. name: juicefs-pv
  19. volumes:
  20. - name: juicefs-pv
  21. persistentVolumeClaim:
  22. claimName: juicefs-pvc

完整的代码和步骤请参见项目代码库中的 基本示例静态供给示例

定制挂载参数

为满足不同的使用场景,JuiceFS 可以通过 csi/volumeAttributes/mountOptions 灵活定制挂载参数,示例补丁如下:

  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4. name: juicefs-with-metacache
  5. spec:
  6. csi:
  7. volumeAttributes:
  8. mountOptions: "metacache,cache-size=100,cache-dir=/var/foo"

完整的代码和步骤请参见项目代码库中的 加载参数示例

子目录挂载

如果想将 JuiceFS 其中一个子目录作为存储卷挂载,可以通过 csi/volumeAttributes/subPath 指定,示例补丁如下:

  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4. name: juicefs-name-subpath
  5. spec:
  6. csi:
  7. volumeAttributes:
  8. subPath: sub-path-in-juicefs

完整的代码和步骤请参见项目代码库中的 子目录挂载示例

多点读写

JuiceFS 支持 ReadWriteMany 访问方式,可以同时挂载到多个 Pod 中作为共享存储。例如:

  1. ---
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: scaling-app
  6. spec:
  7. template:
  8. spec:
  9. containers:
  10. - name: app
  11. image: centos
  12. command: ["/bin/sh"]
  13. args: ["-c", "while true; do echo $(date -u) >> /data/out-$(POD).txt; sleep 5; done"]
  14. env:
  15. - name: POD
  16. valueFrom:
  17. fieldRef:
  18. fieldPath: metadata.name
  19. volumeMounts:
  20. - name: data
  21. mountPath: /data
  22. volumes:
  23. - name: data
  24. persistentVolumeClaim:
  25. claimName: juicefs-shared

弹性伸缩所创建的 Pod 可共享同一个存储卷。完整的代码和步骤请参见项目代码库中的 多点读写示例

CSI 适用版本

在 Kubernetes v1.13 中, CSI 的支持已被提升为 GA 。CSI 支持在 Kubernetes v1.9 里作为 alpha 特性引入,在 Kubernetes v1.10 中提升为 beta。

如果要在更早的版本里使用 JuiceFS,可以考虑通过 flexVolumehostPath 挂载。

通过 flexVolume 存储卷

脚本 juicefs 同时也是 Flex 卷的驱动,支持 init, mount and unmount 操作。

你需要把 juicefs 放到所有主机的 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/juicedata~juicefs/juicefs 位置, 并且将它改成可执行的(需要 Python2.6+ 或者 Python3)。

  1. $ wget https://juicefs.com/static/juicefs
  2. $ chmod +x juicefs
  3. $ cp juicefs /usr/libexec/kubernetes/kubelet-plugins/volume/exec/juicedata~juicefs/juicefs

在配置文件中使用明文密钥

你可以像下面这样定义一个 Flex 卷并使用 juicedata/juicefs 作为驱动:

  1. volumes:
  2. - name: test
  3. flexVolume:
  4. driver: "juicedata/juicefs"
  5. options:
  6. name: "my-jfs"
  7. token: "TOKEN"
  8. accesskey: "ACCESSKEY"
  9. secretkey: "SECRETKEY"

你还可以把其他挂载参数加入到 options 里,比如 cacheSizecacheDir 完整的参数列表请参考 juicefs mount -h

每个 Pod 会使用一个独立的挂载点,跟 Pod 有相同的生命周期。

Kubernetes 密钥管理

或许你不想把密钥的明文放到 Pod 配置文件中,使用 Kubernetes 的密钥管理可以做到。

先创建一个:

  1. apiVersion: v1
  2. kind: Secret
  3. metadata:
  4. name: my-jfs
  5. type: juicedata/juicefs
  6. data:
  7. token: BASE64(TOKEN)
  8. accesskey: BASE64(ACCESSKEY)
  9. secretkey: BASE64(SECRETKEY)

用下面的方法可以生成秘密的 base64 编码:

  1. $ echo -n TOKEN | base64

然后你可以在 Pod 配置文件中使用它:

  1. volumes:
  2. - name: test
  3. flexVolume:
  4. driver: "juicedata/juicefs"
  5. secretRef:
  6. name: my-jfs
  7. options:
  8. name: "my-jfs"

使用 JuiceFS 的配置文件

不管是使用明文还是 Kubernetes 密钥,Flex 卷都会把它们通过命令行参数的方式传递给驱动,比如:

  1. /usr/libexec/kubernetes/kubelet-plugins/volume/exec/juicedata~juicefs/juicefs mount /var/lib/kubelet/pods/aa8ef19f-ba1f-11e7-ab55-0800279245af/volumes/juicedata~juicefs/test '{"kubernetes.io/fsType":"","kubernetes.io/pod.name":"test","kubernetes.io/pod.namespace":"default","kubernetes.io/pod.uid":"aa8ef19f-ba1f-11e7-ab55-0800279245af","kubernetes.io/pvOrVolumeName":"test","kubernetes.io/readwrite":"rw","kubernetes.io/secret/accesskey":"","kubernetes.io/secret/secretkey":"","kubernetes.io/secret/token":"TOKEN","kubernetes.io/serviceAccount.name":"default","name":"NAME"}'

这样看起来也不是很安全,其他人通过 ps 等方法可以看到密钥明文或者编码后的, 通过 JuiceFS 的配置文件(/root/.juicefs/my-jfs.conf)我们可以把这些从命令行中隐藏起来, 它是一个有如下内容的 JSON 文件:

  1. {“token”: TOKEN”, accesskey”: ACCESSKEY”, secretkey”: SECRETKEY“}

一旦你把这个配置文件部署到所有主机的 /root/.juicefs/my-jfs.conf, 就不需要在 Pod 配置文件中使用明文或者密码了,如下:

  1. volumes:
  2. - name: test
  3. flexVolume:
  4. driver: "juicedata/juicefs"
  5. options:
  6. name: "NAME"

通过 hostPath 存储卷

可以把 JuiceFS 在所有主机中挂载到同一个挂载点(比如 /jfs), 然后使用 HostPath 驱动把它(或者某个子目录)绑定到容器中:

  1. volumes:
  2. - name: test-volume
  3. hostPath:
  4. path: /jfs
  5. # this field is optional
  6. type: Directory

所有在同一个主机中的容器会共享同一个客户端(以及文件系统缓存等)。

注意: HostPath 未来可能会在未来 Kubernetes 版本中被本地卷(Local volume)取代。

更多特性

更多支撑 Kuberentes 中应用的特性正在开发内测中,如有需求请通过浏览器右下角的客户支持与我们即时沟通: