在 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 集群:
kubectl apply -f https://raw.githubusercontent.com/juicedata/juicefs-csi-driver/master/deploy/k8s.yaml
在 Kubernetes 集群中部署完 JuiceFS CSI 驱动后,即可通过熟悉的 Kubernetes API 对象 PersistentVolume
、 PersistentVolumes
、 StorageClass
来使用 JuiceFS。
升级 JuiceFS CSI 驱动
JuiceFS CSI 驱动包含两个组件需要升级:
JuiceFS CSI 驱动本身
JuiceFS CSI 驱动中包含的 JuiceFS 客户端
升级 JuiceFS CSI 驱动
停止所有正在使用 JuiceFS CSI 驱动的 Pod
升级 JuiceFS CSI 驱动的容器
如果你正在使用带有
latest
标签的 JuiceFS CSI 驱动的镜像, 那么可以直接运行kubectl rollout restart -f k8s.yaml
并且确保
juicefs-csi-controller
和juicefs-csi-node
这两个 Pod 重启了.如果你把 JuiceFS CSI 驱动的镜像固定到了某个版本, 那么直接修改你的 k8s.yaml 文件, 改成新的版本, 然后
kubectl apply -f k8s.yaml
例如从 0.3.0 版本升级到 0.4.0:
$ sed -i 's+juicedata/juicefs-csi-driver:0.3.0+juicedata/juicefs-csi-driver:0.4.0+g' k8s.yaml
$ cat k8s.yaml
...
---
apiVersion: apps/v1
kind: StatefulSet
...
spec:
containers:
- args:
- --endpoint=$(CSI_ENDPOINT)
- --logtostderr
- --nodeid=$(NODE_ID)
- --v=5
env:
- name: CSI_ENDPOINT
value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock
image: juicedata/juicefs-csi-driver:0.4.0
...
$ 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 驱动的时候配置好这个环境变量就可以了, 例如:
$ cat k8s.yaml
...
---
apiVersion: apps/v1
kind: StatefulSet
...
spec:
containers:
- args:
- --endpoint=$(CSI_ENDPOINT)
- --logtostderr
- --nodeid=$(NODE_ID)
- --v=5
env:
- name: CSI_ENDPOINT
value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock
- name: JFS_AUTO_UPGRADE
value: "true"
- name: JFS_AUTO_UPGRADE_TIMEOUT
value: "15"
image: juicedata/juicefs-csi-driver:0.4.0
...
$ kubectl apply -f k8s.yaml
注意这种方式还是需要重新部署一次 JuiceFS CSI 驱动来保证 controller 和 node 驱动都被应用了环境变量
在 JuiceFS 中动态创建存储卷
JuiceFS CSI 驱动支持根据指定 StorageClass
按需提供存储,例如:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: juicefs-sc
namespace: default
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-secret
必须包含访问 JuiceFS 所需的 name
、 token
、 accesskey
、 secretkey
。这些信息可以在 JuiceFS 控制中心 获取。文件系统目前需要手工创建,用于满足对该 StorageClass
的存储请求。每一个创建的持久化存储卷对应于 JuiceFS 里的一个子目录。
在 Kubernetes 中创建 Secret
的示例命令如下:
kubectl create secret generic juicefs-secret \
--from-literal=name=${JUICEFS_NAME} \
--from-literal=token=${JUICEFS_TOKEN} \
--from-literal=accesskey=${JUICEFS_ACCESSKEY} \
--from-literal=secretkey=${JUICEFS_SECRETKEY}
存储卷的动态供给由 PersistenVolumeClaim
对象的创建触发。例如以下代码将发起对 juicefs-sc
类型存储的请求:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: juicefs-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Pi
storageClassName: juicefs-sc
系统监测到对 juicefs-sc
类型存储的请求后,会连同 StorageClass
中引用的密钥一起向 JuiceFS 插件发起 CreateVolume
调用。JuiceFS 插件将临时挂载所配置的文件系统,并创建独立的子目录供给该存储卷,随后自动创建一个 PersistentVolume
对象来代表新创建的存储卷。Kubernetes 监测到 PersistenVolume
的创建,将其绑定到 PersistentVolumeClaim
中。
动态供给适用于为应用程序提供一个新的持久化存储空间的场景,如果想访问 JuiceFS 里的现有内容,参见下节。
注解
由于 JuiceFS 是个弹性文件系统,本身并没有容量限制,PVC 和 PV 里存储容量并不会用于创建文件系统。 但是由于 Kubernetes 中这是个必选参数,所以必须指定一个值,可以是任意有效值,例如 10Pi。
加载 JuiceFS 中现有内容
对于已经保存在 JuiceFS 中的数据,可以手动创建 PersistentVolume
提供给 Kubernetes 集群中的应用访问,示例如下:
---
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-name
fsType: juicefs
nodePublishSecretRef:
name: juicefs-secret
namespace: default
其中 volumeHandle
为需要访问的 JuiceFS 文件系统名称,juicefs-secret
里包含访问该文件系统所需的 name
、 token
、 accesskey
、 secretkey
等信息。
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: juicefs-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
volumeMode: Filesystem
resources:
requests:
storage: 10Pi
selector:
matchLabels:
juicefs-name: ten-pb-fs
在这个例子中,PersistentVolumeClaim
通过 labels
来选择对应的 PersistentVolume
。
在应用里访问 JuiceFS
无论是通过动态创建还是加载已有内容,在 PersistentVolumeClaim
准备就绪后,应用即可将其作为存储卷使用,示例如下:
---
apiVersion: v1
kind: Pod
metadata:
name: juicefs-app
namespace: default
spec:
containers:
- args:
- -c
- while true; do echo $(date -u) >> /data/out.txt; sleep 5; done
command:
- /bin/sh
image: centos
name: app
volumeMounts:
- mountPath: /data
name: juicefs-pv
volumes:
- name: juicefs-pv
persistentVolumeClaim:
claimName: juicefs-pvc
完整的代码和步骤请参见项目代码库中的 基本示例 和 静态供给示例 。
定制挂载参数
为满足不同的使用场景,JuiceFS 可以通过 csi/volumeAttributes/mountOptions
灵活定制挂载参数,示例补丁如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: juicefs-with-metacache
spec:
csi:
volumeAttributes:
mountOptions: "metacache,cache-size=100,cache-dir=/var/foo"
完整的代码和步骤请参见项目代码库中的 加载参数示例 。
子目录挂载
如果想将 JuiceFS 其中一个子目录作为存储卷挂载,可以通过 csi/volumeAttributes/subPath
指定,示例补丁如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: juicefs-name-subpath
spec:
csi:
volumeAttributes:
subPath: sub-path-in-juicefs
完整的代码和步骤请参见项目代码库中的 子目录挂载示例 。
多点读写
JuiceFS 支持 ReadWriteMany
访问方式,可以同时挂载到多个 Pod 中作为共享存储。例如:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: scaling-app
spec:
template:
spec:
containers:
- name: app
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out-$(POD).txt; sleep 5; done"]
env:
- name: POD
valueFrom:
fieldRef:
fieldPath: metadata.name
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: juicefs-shared
弹性伸缩所创建的 Pod 可共享同一个存储卷。完整的代码和步骤请参见项目代码库中的 多点读写示例 。
CSI 适用版本
在 Kubernetes v1.13 中, CSI 的支持已被提升为 GA 。CSI 支持在 Kubernetes v1.9 里作为 alpha 特性引入,在 Kubernetes v1.10 中提升为 beta。
如果要在更早的版本里使用 JuiceFS,可以考虑通过 flexVolume
或 hostPath
挂载。
通过 flexVolume 存储卷
脚本 juicefs
同时也是 Flex 卷的驱动,支持 init
, mount
and unmount
操作。
你需要把 juicefs
放到所有主机的 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/juicedata~juicefs/juicefs
位置, 并且将它改成可执行的(需要 Python2.6+ 或者 Python3)。
$ wget https://juicefs.com/static/juicefs
$ chmod +x juicefs
$ cp juicefs /usr/libexec/kubernetes/kubelet-plugins/volume/exec/juicedata~juicefs/juicefs
在配置文件中使用明文密钥
你可以像下面这样定义一个 Flex 卷并使用 juicedata/juicefs
作为驱动:
volumes:
- name: test
flexVolume:
driver: "juicedata/juicefs"
options:
name: "my-jfs"
token: "TOKEN"
accesskey: "ACCESSKEY"
secretkey: "SECRETKEY"
你还可以把其他挂载参数加入到 options
里,比如 cacheSize
和 cacheDir
完整的参数列表请参考 juicefs mount -h
。
每个 Pod 会使用一个独立的挂载点,跟 Pod 有相同的生命周期。
Kubernetes 密钥管理
或许你不想把密钥的明文放到 Pod 配置文件中,使用 Kubernetes 的密钥管理可以做到。
先创建一个:
apiVersion: v1
kind: Secret
metadata:
name: my-jfs
type: juicedata/juicefs
data:
token: BASE64(TOKEN)
accesskey: BASE64(ACCESSKEY)
secretkey: BASE64(SECRETKEY)
用下面的方法可以生成秘密的 base64 编码:
$ echo -n TOKEN | base64
然后你可以在 Pod 配置文件中使用它:
volumes:
- name: test
flexVolume:
driver: "juicedata/juicefs"
secretRef:
name: “my-jfs”
options:
name: "my-jfs"
使用 JuiceFS 的配置文件
不管是使用明文还是 Kubernetes 密钥,Flex 卷都会把它们通过命令行参数的方式传递给驱动,比如:
/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 文件:
{“token”: “TOKEN”, “accesskey”: “ACCESSKEY”, “secretkey”: ”SECRETKEY“}
一旦你把这个配置文件部署到所有主机的 /root/.juicefs/my-jfs.conf
, 就不需要在 Pod 配置文件中使用明文或者密码了,如下:
volumes:
- name: test
flexVolume:
driver: "juicedata/juicefs"
options:
name: "NAME"
通过 hostPath 存储卷
可以把 JuiceFS 在所有主机中挂载到同一个挂载点(比如 /jfs
), 然后使用 HostPath 驱动把它(或者某个子目录)绑定到容器中:
volumes:
- name: test-volume
hostPath:
path: /jfs
# this field is optional
type: Directory
所有在同一个主机中的容器会共享同一个客户端(以及文件系统缓存等)。
注意: HostPath 未来可能会在未来 Kubernetes 版本中被本地卷(Local volume)取代。
更多特性
更多支撑 Kuberentes 中应用的特性正在开发内测中,如有需求请通过浏览器右下角的客户支持与我们即时沟通:
支持 本地持久化存储卷 作为缓存以优化性能
支持 CSI 存储卷克隆
支持 CSI 存储卷快照与恢复