在 Kubernetes 中使用 JuiceFS

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

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

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

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

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

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

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

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

    • (可用于所有 Kubernetes 版本)

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

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

    将驱动部署到 Kubernetes 集群:

    在 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 驱动的容器

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

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

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

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

      • 那么只要在部署 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. image: juicedata/juicefs-csi-driver:0.4.0
        21. ...
        22. $ 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 类型存储的请求:

    系统监测到对 juicefs-sc 类型存储的请求后,会连同 中引用的密钥一起向 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

    无论是通过动态创建还是加载已有内容,在 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. kind: PersistentVolume
    2. metadata:
    3. name: juicefs-with-metacache
    4. spec:
    5. csi:
    6. volumeAttributes:
    7. mountOptions: "metacache,cache-size=100,cache-dir=/var/foo"

    完整的代码和步骤请参见项目代码库中的 。

    子目录挂载

    1. kind: PersistentVolume
    2. metadata:
    3. name: juicefs-name-subpath
    4. spec:
    5. csi:
    6. volumeAttributes:
    7. 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 可共享同一个存储卷。完整的代码和步骤请参见项目代码库中的 。

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

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

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

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

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

    你可以像下面这样定义一个 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 配置文件中使用明文或者密码了,如下:

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

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

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

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

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