05月20, 2018

Kubernetes持久化存储Cephfs

Kubernetes持久化存储Cephfs

上一篇 kubernetes持久化存储Ceph RBD介绍了Ceph RBD在kubernetes中的使用,本篇将会介绍Cephfs在kubernetes中的使用。

环境这里不再重复介绍,直接开始我们对Cephfs在kubernetes的使用。熟悉kubernetes volume的同学应该了解,kubernetes 对volume的提供支持“静态PV”和“动态PV”两种方式。

  • 静态PV

    集群管理员创建一些PV。它们带有可供集群用户使用的实际存储的细节。之后便可用于PVC消费。

  • 动态PV

    相比静态PV而言,动态PV无需管理员手动创建PV,PV的操作是由一个叫StorageClass的控制器创建。对于Cephfs volume来说,截止到kubernetes 1.10版本并没有直接提供Cephfs的StorageClass。 alt

    虽然官方并没有直接提供对Cephfs StorageClass的支持,但是社区给出了类似的解决方案 external-storage/cephfs,后面我们会使用到。

静态PV

secret 的创建见上一篇,这里不再重复

书写Cephfs PV 对象文件

$ vim cephfs-pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: cephfs-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  cephfs:
    monitors:
      - 192.168.0.3:6789
    user: kube
    secretRef:
      name: secret-for-cephfs
    readOnly: false
  persistentVolumeReclaimPolicy: Recycle

创建PV

$ kubectl create -f cephfs-pv.yaml -n cephfs
persistentvolume "cephfs-pv" created

$ kubectl get pv -n cephfs
NAME        CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
cephfs-pv   1Gi        RWX           Recycle         Available

创建PVC

$ vim cephfs-pv-claim.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cephfs-pv-claim
  namespace: cephfs 
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

deployment 中使用 PV

metadata:
  name: cephfs-pvc
  namespace: cephfs
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: cephfs-pvc
    spec:
      containers:
      - name: nginx
        image: busybox:latest
        volumeMounts:
        - name: cephfs-pv 
          mountPath: /data/cephfs
          readOnly: false 
      volumes:
      - name: cephfs 
        persistentVolumeClaim:
          claimName: cephfs-pv-claim

这样,在同一个deployment中的2个pod容器内都可以读写同一个cephfs volume了。

动态PV

对于规模不大的容器平台而言,静态PV方式就可以满足需求;但是当容器规模很大时,你不知道用户什么时候创建PV,因此动态PV方式就非常有用了。下面我们看下如何基于 external-storage/cephfs 来实现动态PV。

部署 cephfs-provisioner

cephfs-provisioner 是 kubernetes 官方社区提供的Cephfs的StorageClass支持。用于动态的调用后端存储Cephfs创建PV。

cephfs-provisoner 的实现机制如图所示:

alt 它主要是包含两部分:

  • cephfs-provisoner.go

    是cephfs-provisoner(cephfs的StorageClass)的核心,主要是 watch kubernetes中 PVC 资源的CURD事件,然后以命令行方式调用 cephfs_provisor.py脚本创建PV。

  • cephfs_provisoner.py

    python 脚本实现的与cephfs交互的命令行工具。cephfs-provisoner 对cephfs端volume的创建都是通过该脚本实现。里面封装了volume的增删改查等功能。

创建 StorageClass
[external-storage/ceph/cephfs/example]#kubectl create -f class.yaml 

[external-storage/ceph/cephfs/example]# kubectl get sc -n cephfs
NAME      PROVISIONER       AGE
cephfs    ceph.com/cephfs   33d
基于RBAC授权方式启动cephfs-provisioner
[external-storage/ceph/cephfs/deploy/rbac/]# tree
.
├── clusterrolebinding.yaml
├── clusterrole.yaml
├── deployment.yaml
├── rolebinding.yaml
├── role.yaml
└── serviceaccount.yaml

[external-storage/ceph/cephfs/deploy/rbac/]# kubectl create -f  *

[external-storage/ceph/cephfs/deploy/rbac/]# kubectl get deploy -n cephfs
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
cephfs-provisioner   1         1         1            1           38d

这样动态创建PV的环境就准备好了。

动态PV方式,无需再手动创建PV,只需创建PVC,cephfs-provisoner会自动的为该PVC在后端cephfs存储上创建对应的PV。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-1
  annotations:
    volume.beta.kubernetes.io/storage-class: "cephfs"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

此时,我们创建PVC后,会看到同时会自动创建并绑定上一个PV。

# kubectl get pvc -n cephfs
NAME      STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-1     Bound     pvc-6668937d-4932-11e8-af69-f0921c10a7bc   1Gi        RWX            cephfs         24d

# kubectl get pv -n cephfs
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM           STORAGECLASS   REASON    AGE
pvc-6668937d-4932-11e8-af69-f0921c10a7bc   1Gi        RWX            Delete           Bound     cephfs/pvc-1    cephfs                   24d

至此,基于 cephfs 的动态PV方式就介绍完了。

cephfs-provisoner的坑

在使用 cephfs-provisoner 与公司 cephfs对接的时候遇到了一些坑,在这里一并说一下。

  • 路径问题

    在 cephfs_provisioner/cephfs_provisioner.py 的实现中,默认在cephfs中创建的根目录名是”kubernetes“,所有后续创建的卷都在cephfs端的 /kubernetes/ 一级目录下 ,代码实现中以VOlUME_GROUP="kubernetes"定义。 因此,如果你不想在 cephfs 中以该名字命名,需要修改VOLUME_GROUP值。

    另外,它的二级目录也是固定的为/kubernetes/volumes,二级目录是python-cephfs模块中由DEFAULT_VOL_PREFIX变量定义:

    POOL_PREFIX = "fsvolume_"
    DEFAULT_VOL_PREFIX = "/volumes"
    DEFAULT_NS_PREFIX = "fsvolumens_"
    

    因此,要定制化在cephfs端的一级和二级目录,需要修改这两个地方。

  • 权限问题

    这里遇到两个权限问题:

    1. 挂载权限问题

      默认python-cephfs模块在连接cephfs挂载时是mount的根目录”/“,如下代码所示:

      def connect(self, premount_evict = None):
        """
      
        :param premount_evict: Optional auth_id to evict before mounting the filesystem: callers
                               may want to use this to specify their own auth ID if they expect
                               to be a unique instance and don't want to wait for caps to time
                               out after failure of another instance of themselves.
        """
        log.debug("Connecting to RADOS with config {0}...".format(self.conf_path))
        self.rados = rados.Rados(
            name="client.{0}".format(self.auth_id),
            clustername=self.cluster_name,
            conffile=self.conf_path,
            conf={}
        )
        self.rados.connect()
      
        log.debug("Connection to RADOS complete")
      
        log.debug("Connecting to cephfs...")
        self.fs = cephfs.LibCephFS(rados_inst=self.rados)
        log.debug("CephFS initializing...")
        self.fs.init()
        if premount_evict is not None:
            log.debug("Premount eviction of {0} starting".format(premount_evict))
            self.evict(premount_evict)
            log.debug("Premount eviction of {0} completes".format(premount_evict))
        log.debug("CephFS mounting...")
        self.fs.mount()
        log.debug("Connection to cephfs complete")
      

      而根目录只有admin用户才有权限进行挂载,所以这个地方除了admin用户外,没办法使用其它用户。但是,我们在使用cephfs时,为了安全,管理员是不会给我们admin用户使用的,所以这个地方肯定是接受不了的。为此,我们定制修改了libcephfs2和python-cephfs的源码,增加了connect_with_path方法,允许指定挂载的目录,这样我们只要在挂载时不使用根目录即可。 以下是python-cephfs添加的方法:

      def connect_with_path(self, mount_path, premount_evict = None):
        """
      
        :param premount_evict: Optional auth_id to evict before mounting the filesystem: callers
                               may want to use this to specify their own auth ID if they expect
                               to be a unique instance and don't want to wait for caps to time
                               out after failure of another instance of themselves.
        """
        log.debug("Connecting to RADOS with config {0}...".format(self.conf_path))
        self.rados = rados.Rados(
            name="client.{0}".format(self.auth_id),
            clustername=self.cluster_name,
            conffile=self.conf_path,
            conf={}
        )
        self.rados.connect()
      
        log.debug("Connection to RADOS complete")
      
        log.debug("Connecting to cephfs...")
        self.fs = cephfs.LibCephFS(rados_inst=self.rados)
        log.debug("CephFS initializing...")
        self.fs.init()
        if premount_evict is not None:
            log.debug("Premount eviction of {0} starting".format(premount_evict))
            self.evict(premount_evict)
            log.debug("Premount eviction of {0} completes".format(premount_evict))
        log.debug("CephFS mounting {0}...".format(mount_path))
        self.fs.mount_with_path(mount_path)
        log.debug("Connection to cephfs complete")
      
        # Recover from partial auth updates due to a previous
        # crash.
        self.recover()
      
    2. 读写权限问题

      在 cephfs_provisoner.py 的实现中,默认添加了对 cephfs namespace的支持,因此在对volume授权时会添加对namespace 相关的权限设置。因为,我们使用的ceph版本luminous没有对namespace进行支持,所以,在使用时产生了创建的volume挂载到pod内后没有读写权限"input/output error"的问题。 此时,你在cephfs端查看卷的读写权限时,你可以看到目录读写权限都是问号:

      ??????????    ??      ?             ?                         ?  pvc-66b0f8ff-4932-11e8-af69-f0921c10a7bc
      

      cephfs_provisoner.py 中相关代码如下:

        pool_name = self._volume_client._get_ancestor_xattr(path, "ceph.dir.layout.pool")
        namespace = self._volume_client.fs.getxattr(path, "ceph.dir.layout.pool_namespace")
        ...
        want_mds_cap = 'allow r,allow {0} path={1}'.format(want_access_level, path)
        want_osd_cap = 'allow {0} pool={1} namespace={2}'.format(
        want_access_level, pool_name, namespace)
      

      我们要做的就是修改这部分逻辑,把namespace相关部分给去掉,修改后的逻辑为:

       # 去掉下面两行
       pool_name = self._volume_client._get_ancestor_xattr(path, "ceph.dir.layout.pool")
       namespace = self._volume_client.fs.getxattr(path, "ceph.dir.layout.pool_namespace")
      
        # 下面两行修改后 
        want_mds_cap = 'allow r,allow {0} path=/kube{1}'.format(want_access_level, path)
        want_osd_cap = 'allow {0}'.format(want_access_level)
      

以上是我们在 kubernetes 中使用 cephfs 的过程。

本文链接:https://www.opsdev.cn/post/k8s-cephfs.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。