有了前面ceph RBD的基础,再来看cephfs就简单很多了。本文介绍了kubernetes上使用cephfs的方法,重点介绍了kubernetes上用cephfs支持storage class的解法。

cephfs

cephfs类似nfs。cephfs服务器上配置完成后,客户端可以将远端目录mount到本地。cephfs服务器的配置就不说了,有人搞定的感觉就是好。主要来说说客户端mount。

cephfs有两种mount方式:内核(即mount命令)、用户态(ceph-fuse)。

内核mount

因为上文RBD介绍里已经在本地 /etc/ceph/ 下配置好了,所以下面可以直接mount。

# mount -t ceph 1.2.3.4:/ /mnt/cephfs/ -o name=admin,secret=`ceph auth print-key client.admin`
# df -h|grep cephfs
172.25.60.3:/   17T   63G   17T   1% /mnt/cephfs

上面将整个cephfs都挂载到了 /mnt/cephfs 目录下。如果你不想这么大范围怎么做呢?cephfs没有像RBD一样有pool+image这样比较好的隔离手段,其做法是可以在cephfs根目录下创建一个子目录,mount的时候指定这个子目录。例如。

# mkdir /mnt/cephfs/xxx
# mkdir /mnt/xxx
# mount -t ceph 1.2.3.4:/xxx /mnt/xxx/ -o name=admin,secret=`ceph auth print-key client.admin`
# df -h|grep xxx
172.25.60.3:/xxx   17T   63G   17T   1% /mnt/xxx

注意,这时看到的虽然是子目录,但其可用大小和根目录mount是一样的。为什么呢?我们用的centos 7.3的内核版本不支持。据说更高内核版本的客户端+服务器可以支持。

用户态mount

cephfs还支持用户态mount,方法是使用ceph-fuse命令。

yum install ceph-fuse -y

注意如果centos只是配置了centos、epel两个源,可能会因为版本依赖问题而无法安装ceph-fuse。此时应该配置使用ceph自己的repo。

rpm -Uvh  http://mirrors.ustc.edu.cn/centos/7/extras/x86_64/Packages/epel-release-7-9.noarch.rpm
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7

安装后就可以ceph-fuse了。

# ceph-fuse -r /xxx /mnt/xxx/ 
ceph-fuse[1108]: starting ceph client
2018-05-22 23:32:21.846814 7fc560879ec0 -1 init, newargv = 0x7fc56a916780 newargc=11
ceph-fuse[1108]: starting fuse
# df -h|grep xxx
ceph-fuse        17T   63G   17T   1% /mnt/xxx

可以看到,挂载成功了,和内核mount一样,其大小也是整个cephfs的size。

但通过配置,ceph-fuse可以限定用户mount的大小。

限定用户态mount的size

需要两步:

1 设置用户待使用目录的attr

setfattr -n ceph.quota.max_bytes -v 100000000 /mnt/cephfs/xxx     # 100 MB

2 用户mount时,指定--client-quota参数

# ceph-fuse -r  /xxx /mnt/xxx --client-quota   
# df -h|grep xxx
ceph-fuse        92M     0   92M   0% /mnt/xxx
# cd /mnt/xxx
# dd if=/dev/zero of=test bs=1M count=100  #在/mnt/xxx目录下创建一个test文件,大小是100MB
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 1.23772 s, 84.7 MB/s
# ls -l -h      # 可以创建成功
total 101M
-rw-r--r-- 1 root root 100M May 22 23:47 test
# df -h |grep xxx   #没空间了
ceph-fuse        92M     -     -    - /mnt/xxx
# dd if=/dev/zero of=test2 bs=1M count=100    # 创建一个test2文件,大小100MB,失败,提示超过quota了
dd: error writing ‘test2’: Disk quota exceeded
1+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000886542 s, 0.0 kB/s
# ls -l -h      #只有文件名,没有内容
total 101M
-rw-r--r-- 1 root root 100M May 22 23:47 test
-rw-r--r-- 1 root root    0 May 22 23:47 test2

需要注意:

  • 二者缺一不可。如果只是设置了setfattr,会发现一个很神奇的现象:用户mount了以后,的确可以看到像上面的size大小,但其实是限制不住的,用户可以写超过 setfattr的限制。
  • quota的检测会略微延迟一点,ceph说是几秒钟,所以我们看到第一个100MB的文件是创建成功了的

对于k8s来说,其实是够用的,因为mount是由k8s来做的,容器的用户不参与这个过程,所以可以为其设置 –client-quota。我给k8s提了一个PR,还在等社区的意见

用户态、内核态卸载,其实都可以 umount 命令。当然用户态也可以用更专业的:

fusermount -u mountpoint  --> 可以直接 umount

cephfs用作Volume

贴个yaml略过。

apiVersion: v1
kind: Pod
metadata:
  name: cephfs2
spec:
  containers:
  - name: cephfs-rw
    image: gcr.io/nginx
    volumeMounts:
    - mountPath: "/mnt/cephfs"
      name: cephfs
  volumes:
  - name: cephfs
    cephfs:
      monitors:
      - 1.2.3.4:6789
      user: "admin"
      secretRef:
        name: ceph-secret
      readOnly: true

cephfs用作PV/PVC

贴个yaml略过。PVC、Pod就不贴了。跟RBD里是一样的。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: cephfs-pv3
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  cephfs:
    monitors:
      - 1.2.3.4:6789
    path: /xxx
    user: admin
    secretRef:
      name: ceph-secret
    readOnly: false
  persistentVolumeReclaimPolicy: Recycle

cephfs用作storage class

重点来说说storage class。你可能会说,哎,官方支持storage class的存储类型里,明明没有cephfs呀。

对,但也不全对。

目前k8s(v1.10)的确是不支持cephfs用作storage class的,但是我们已经有了ceph集群,而RBD又不支持 ReadWriteMany,这样就没办法做共享存储类型的Volume了,使用场景会受一些限制。而为了这个场景再单独去维护一套glusterfs,又很不划算,当然是能用cephfs做storage class最完美啦。

社区其实是有项目做这个的,就是 external storage,只是现在还在孵化器里。

所谓external storage其实就是一个 controller,它会去监听 apiserver 的storage class api的变化,当发现有cephfs的请求,它会拿来处理:根据用户的请求,创建PV,并将PV的创建请求发给api server;PV创建后再将其与PVC绑定。

同时,controller的 cephfs-provisioner会调用 cephfs_provisioner.py 脚本去cephfs集群上去创建目录,目录名规则为 "kubernetes-dynamic-pvc-%s", uuid.NewUUID()

const {
  provisionCmd       = "/usr/local/bin/cephfs_provisioner"
}

func (p *cephFSProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
   args := []string{"-n", share, "-u", user}
	if p.enableQuota {
		capacity := options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
		requestBytes := strconv.FormatInt(capacity.Value(), 10)
		args = append(args, "-s", requestBytes)
	}
	cmd := exec.Command(provisionCmd, args...)
	// set env
	cmd.Env = []string{
		"CEPH_CLUSTER_NAME=" + cluster,
		"CEPH_MON=" + strings.Join(mon[:], ","),
		"CEPH_AUTH_ID=" + adminID,
		"CEPH_AUTH_KEY=" + adminSecret}

	output, cmdErr := cmd.CombinedOutput()

在ceph上看,目录树如下

├── kubernetes
│   ├── kubernetes-dynamic-pvc-0f1354bc-75e6-11e8-9910-0a580af4035a
│   ├── kubernetes-dynamic-pvc-14e82c0a-7603-11e8-9910-0a580af4035a
│   ├── kubernetes-dynamic-pvc-1dadffbf-75e5-11e8-9910-0a580af4035a
...
├── _kubernetes:kubernetes-dynamic-pvc-0f1354bc-75e6-11e8-9910-0a580af4035a.meta
├── _kubernetes:kubernetes-dynamic-pvc-14e82c0a-7603-11e8-9910-0a580af4035a.meta
├── _kubernetes:kubernetes-dynamic-pvc-1dadffbf-75e5-11e8-9910-0a580af4035a.meta
...

也就是说,external-storage作为ceph集群的一个用户,自己维护了不同PVC和cephfs目录的对应关系。

cephfs的external storage controller的部署可以参考这里

部署后,创建一个名为cephfs的storage classs。此时,需要指定到cephfs的地址、登录账号信息。比如说用户有两种cephfs:普通机械硬盘和快速的SSD盘,那么可以创建两个sc,一个用来做shared-slow,一个用来做shared-fast。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: cephfs
provisioner: ceph.com/cephfs
parameters:
    monitors: 1.2.3.4:6789
    adminId: admin
    adminSecretName: ceph-secret
    adminSecretNamespace: "cephfs"

再创建个pvc,指定storage class为cephfs;创建Pod,挂载这个pvc。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "cephfs"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
---
kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: gcr.io/nginx
    volumeMounts:
      - name: pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: pvc
      persistentVolumeClaim:
        claimName: claim

很完美对不对?naive。目前external storage并不支持设置cephfs的大小,因此,所有容器看到的挂载目录都是一样大的,显然离可用还有一定距离。

不过,由于external storage用的是python client,所以fix下也很简单,你可以参考我的PR。当然了,别忘了修改kubelet的这个PR

external-storage的PR已经合入了,kubernetes的还没有,需要考虑不同ceph版本兼容的问题。

服用了这两个PR以后,你也可以和我一样快乐的使用cephfs sc了。

Ref: