3.4.执子之手:Ceph RBD

死生契阔,与子成说。执子之手,与子偕老。——《诗经》

    前边三个章节我们已经搭建好了K8S、Ceph、Harbor,基础环境已经有了,接下来这个章节就让K8S和Ceph联动起来,将Ceph作为K8S的底层存储引擎让二者协同工作,不论是之后的KubeSphere还是TiDB都可以直接使用本章配置的结果来访问底层存储。

    K8S和Ceph集成有三种实现方式:

  • Volumes存储卷

  • PV/PVC持久化卷/持久化卷声明

  • StorageClass动态存储,动态创建PV、PVC

    Ceph支持K8S存储有两种类型:

  • CephFS:文件系统

  • Ceph RBD:块存储

注意:一般写CephFS会将Ceph和FS连写,而RBD则中间加一空白来写!

1. ceph-csi

    全称Container Storage Interface,它是Ceph的一个插件,它的目的是定义行业标准“容器存储接口”,使存储供应商(SP)可以开发一个符合CSI标准的插件并使其能够在多个容器编排(CO)系统中工作。

    本章K8S通过ceph-csi(csi plugin)来接入ceph存储(csi相关组件的分析以rbd为例进行分析),对csi系统结构、涉及的k8s对象和组件进行简单介绍,以及k8s对存储进行相关操作流程分析,存储相关操作包括存储创建、存储扩容、存储挂载、解除存储、存储删除操作。

1.1. 整体架构

图参考文档中内容重绘,PV/PVC/SC此处不解释其作用(烂大街的概念),下文主要围绕CSI部分。

VolumeAttachment组件

    该组件记录了PV挂载的相关信息:挂载到哪个Node节点、由哪个Volume Plugin来挂载等。AD Controller会创建一个VolumeAttachment,而External-Attacher通过观察该VolumeAttachment,根据状态属性来执行存储的挂载、卸载操作。

CSINode

    CSINode记录了csi plugin相关信息(NodeId、DriverName、拓扑),当Node Driver Register向kubelet注册一个csi plugin之后,会创建(更新)一个CSINode对象,记录csi plugin的相关信息。

Volume Plugin

    该组件扩展各种存储类型的卷管理能力,实现第三方存储各种操作能力和K8S存储系统结合,并调用第三方存储的接口或命令,从而提供数据卷的创建/删除、attach/detach、mount/umount具体操作实现,可以认为是第三方存储代理人。

  • in-tree:在K8S源码内部实现,和K8S一起发布、管理,更新迭代慢、灵活性差。

  • out-of-tree:代码独立于K8S,由存储厂商实现,有csi, flexvolume两种实现。

CSI Plugin

    ceph-csi就属于csi plugin,它主要分为ControllerServer和NodeServer,负责不同的存储操作。

External Plugin

    主要包含四个子组件(参考图),它辅助csi plugin组件共同完成存储相关的操作。

组件作用

provisioner

Watch PVC对象,调用csi plugin创建存储,最终创建PV。

attacher

Watch VolumeAttachment对象,调用csi plugin做attach/dettach操作。

resizer

Watch PVC对象,调用csi plugin来做存储扩容。

snapshotter

创建快照。

Node-Driver-Registrar

    该组件负责实现csi plugin(NodeServer)的注册,让kubelet感知csi plugin的存在。

AD/PV Controller

    PV Controller负责PV、PVC绑定和生命周期管理(创建/删除底层存储、创建/删除PV对象、PV和PVC对象状态变更)。

    AD Controller全称Attachment/Detachment控制器,主要负责创建、删除VolumeAttachment对象,并调用volume plugin来做存储设备的Attach/Detach操作(将数据卷挂载到特定node节点上/从特定node节点上解除挂载),以及更新node.Status.VolumesAttached等。

关于这部分内容参考引用博文第一章,这里就不再做过多介绍,本章节的基本介绍只是起一个抛砖引玉的作用。

1.2. CSI插件

    ceph-csi的块设备功能要K8S v1.13以上或更高版本才可用,它动态提供了RBD映像以支持K8S卷并将这些RBD映像映射为工作节点上的块设备(可选地挂载映像中包含的文件系统)运行引用RBD支持卷的Pod。它的整体结构如下:

    所以您可以通过kubernetes sidecar部署provisioner, attacher, resizer, driver-registrar, snapshotter组件以支持CSI功能。ceph-csi插件实现了支持CSI的Container Orchestrator(OC)和Ceph集群之间的接口,允许动态供应Ceph卷并将它们附加到工作负载中。默认情况下ceph-csi使用RBD内核模块,可能不支持所有的Ceph CRUSH可调参数或RBD映像等特性。


2. Ceph RBD部署

2.1.Ceph: Pool

  1. 默认情况下,Ceph块设备使用rbd池,使用下边命令为K8S创建一个池:

    # 存储池名称:k8s.store
    # 在目前三节点的集群中,推荐后边不使用128 128,而是32 32两个值,否则等待更新的时间会很长
    ceph osd pool create k8s.store 128 128
    # 新创建的池必须在使用之前初始化,此时需要使用rbd工具初始化
    rbd pool init k8s.store

    您可以登录Ceph集群管理界面等待任务执行完成(截图中可以看到Applications类型rbd):

  2. 为K8S创建新的Ceph账号

    # 账号信息
    ceph auth get-or-create client.k8s \
        mon 'profile rbd' \
        osd 'profile rbd pool=k8s.store' \
        mgr 'profile rbd pool=k8s.store'
    # 查看创建账号
    ceph auth list
  3. 账号创建完成后,ceph-csi需要一个存储在K8S中的ConfigMap对象来定义Ceph集群的Ceph mon地址

    ceph mon dump
    ceph -s|grep mon

2.2.K8S: ConfigMap

  1. 将部署配置放在~/deploy/ceph中,clusterID需要和上边取得的id保持一致,并且需要修改mon地址,多个地址后面要使用逗号分隔。

    cd /root/deploy/ceph
    vim config-map-csi.yaml

    文件内容如下:

    # 文件:config-map-csi.yaml
    # 名称:ceph-csi-config
    apiVersion: v1
    kind: ConfigMap
    data:
        config.json: |-
            [
                {
                    "clusterID":"79ed3cf4-6a04-11ed-8db2-fa163ec820ba",
                    "monitors":[
                        "192.168.0.154:6789",
                        "192.168.0.123:6789",
                        "192.168.0.208:6789"
                    ]
                }
            ]
    metadata:
        name: ceph-csi-config
  2. 在K8S中创建该ConfigMap对象:

    kubectl apply -f config-map-csi.yaml
    kubectl get cm ceph-csi-config
  3. 按照步骤4-5创建额外的几个ConfigMap(必须)

    # 文件:config-map-csi-kms.yaml
    # 名称:ceph-csi-encryption-kms-config
    apiVersion: v1
    kind: ConfigMap
    data:
        config.json: |-
            {}
    metadata:
        name: ceph-csi-encryption-kms-config
    
    ------
    # 文件:config-map-ceph.yaml
    # 名称:ceph-config
    apiVersion: v1
    kind: ConfigMap
    data:
        ceph.conf: |
            [global]
            auth_cluster_required = cephx
            auth_service_required = cephx
            auth_client_required = cephx
        # keyring is a required key and its value should be empty
        keyring: |
    metadata:
        name: ceph-config

2.3.K8S: RBAC

  1. ceph-csi需要创建ServiceAccount和RBAC用于访问集群内部信息,先下载yaml文件:

    # csi-provisioner-rbac.yaml
    wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-provisioner-rbac.yaml
    
    # csi-nodeplugin-rbac.yaml
    wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-nodeplugin-rbac.yaml
    # /root/deploy/ceph目录下
    ll
  2. 将下载的两个yaml文件内容配置到K8S中:

    kubectl apply -f csi-provisioner-rbac.yaml
    kubectl apply -f csi-nodeplugin-rbac.yaml

2.4.K8S: provisioner

  1. 下载两个yaml文件到本地:

    # csi-rbdplugin-provisioner.yaml
    wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin-provisioner.yaml
    
    # csi-rbdplugin.yaml
    wget https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/rbd/kubernetes/csi-rbdplugin.yaml
    
    ll
  2. 查看第一个yaml文件中的镜像,手动拉取镜像(推荐)

    # csi-rbdplugin-provisioner.yaml
    # Resizer
    docker pull registry.k8s.io/sig-storage/csi-resizer:v1.6.0
    # Attacher
    docker pull registry.k8s.io/sig-storage/csi-attacher:v4.0.0
    # Snapshotter
    docker pull registry.k8s.io/sig-storage/csi-snapshotter:v6.1.0
    # Provisioner
    docker pull registry.k8s.io/sig-storage/csi-provisioner:v3.3.0
    
    # csi-rbdplugin.yaml
    docker pull registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.6.0
    
    # 关键镜像(两个文件都需要)
    docker pull quay.io/cephcsi/cephcsi:canary
  3. 将两个配置文件内容配置到K8S中:

    kubectl apply -f csi-rbdplugin.yaml
    kubectl apply -f csi-rbdplugin-provisioner.yaml
  4. 查看csi插件是否已经启动,FailedScheduling问题参考问题章节:

2.5.K8S: Secret

  1. 获取ceph用户信息,找到client.admin用户,复制后边的Key,然后创建一个Secret对象:

    # 文件:secret-csi-rbd.yaml
    # 名称:csi-rbd-secret
    apiVersion: v1
    kind: Secret
    metadata:
        name: csi-rbd-secret
        namespace: default
    stringData:
        userID: k8s
        userKey: xxx
  2. 最终结果如下:

    kubectl get secret
    kubectl get cm

2.6.K8S: StorageClass

  1. 先获取集群ID

    ceph -s|grep id|awk -F "[ ]+" '{print $3}'
  2. 创建新的yaml文件(StorageClass)

    # 文件:sc-csi-rbd.yaml
    # 名称:ceph-rbd-k8s
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
        # storageclass名称
        name: ceph-rbd-k8s   
        # 设置默认,有这步就不用后边手工处理了
        annotations:
            storageclass.kubernetes.io/is-default-class: "true"
    provisioner: rbd.csi.ceph.com   
    parameters:
        # ceph集群id
        clusterID: 79ed3cf4-6a04-11ed-8db2-fa163ec820ba 
        # pool名称 k8s.store   
        pool: k8s.store            
        # rbd特性
        imageFeatures: layering   
        csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
        csi.storage.k8s.io/provisioner-secret-namespace: default
        csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
        csi.storage.k8s.io/controller-expand-secret-namespace: default
        csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
        csi.storage.k8s.io/node-stage-secret-namespace: default
    reclaimPolicy: Delete           #pvc回收机制
    allowVolumeExpansion: true      #对扩展卷进行扩展
    # StorageClass 动态创建的 PersistentVolume 
    # 将使用类中 mountOptions 字段指定的挂载选项
    mountOptions:                   
        - discard
  3. 将Yaml文件内容配置到K8S

    kubectl apply -f sc-csi-rbd.yaml
    kubectl get sc
  4. :上述ceph-rbd-k8s如果并没有带(default)标记,证明它不是默认的SC,您需要执行如下命令将它设置成默认:

    kubectl patch storageclass ceph-rbd-k8s \
        -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
    kubectl get sc
  5. 最后创建PVC验证当前SC是否可用:

    # 文件:pvc-csi-block.yaml
    # 名称:test-block-pvc
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
        name: test-block-pvc     #pvc名称
    spec:
        accessModes:
            - ReadWriteOnce     #访问模式
        volumeMode: Block     #数据卷类型
        resources:
            requests:
                storage: 1Gi           #存储空间
        storageClassName: ceph-rbd-k8s   #后端storageclass名称

    注意成功的标记是状态必须是Bound,如果状态不是这个值,证明有问题,可使用如下命令查看日志:

    describe pvc test-block-pvc

2.7. imageFeatures

    上述SC中配置了一个imageFeatures的属性,对Ceph而言,该属性的值如下:

类型特性

layering

image的克隆操作,可对image创建快照并保护,然后从快照克隆新的image,父子image之间使用COW技术,共享对象数据。

striping v2

条带化对象数据,类似raid 0,可改善顺序读写场景较多情况下的性能。

exclusive lock

保护image数据一致性,对image做修改时,需要持有此锁。这个可以看做是一个分布式锁,在开启的时候,确保只有一个客户端在访问image,否则锁的竞争会导致io急剧下降。 主要应用场景是qemu live-migration。

object map

此特性依赖于exclusive lock。因为image的对象分配是thin-provisioning,此特性开启的时候,会记录image所有对象的一个位图,用以标记对象是否真的存在,在一些场景下可以加速io。

fast diff

此特性依赖于object map和exlcusive lock。快速比较image的snapshot之间的差异。

deep-flatten

layering特性使得克隆image的时候,父子image之间采用COW,他们之间的对象文件存在依赖关系,flatten操作的目的是解除父子image的依赖关系,但是子image的快照并没有解除依赖,deep-flatten特性使得快照的依赖也解除。

journaling

依赖于exclusive lock。将image的所有修改操作进行日志化,并且复制到另外一个集群(mirror),可以做到块存储的异地灾备。这个特性在部署的时候需要新部署一个daemon进程,目前还在试验阶段,不过这个特性很重要,可以做跨集群/机房容灾。

Exclusive Lock

从上面可以看出,很多特性都依赖于exclusive lock,重点介绍一下。

exclusive lock

是分布式锁,实现的时候默认是客户端在第一次写的时候获取锁,并且在收到其他客户端的锁请求时自动释放锁。这个特性在jewel默认开启后,本身没什么问题, 客户端可以自动获取和释放锁,在客户端crash后也能够正确处理。


3. CephFS 部署

    前文讲述了Ceph中Block存储块的部署教程,并且借用CSI架构在K8S中初始化了将要使用的SC(Storage Class),本章节基于CephFS搭建另一个SC,搭建之前先看搭建完成后的总体拓扑图(注意ConfigMap共享):

    CephFS部署的文件地址从https://raw.githubusercontent.com/ceph/ceph-csi/master/deploy/cephfs/kubernetes/下载,将前边命令中的下载地址稍做修改即可,而前文中已经创建了CephFS:

    所以跳过前边所有内容的apply,直接进入第二章节的第五步。

    查看运行情况:

3.1. K8S: Secret

  1. 使用下边命令查看账号信息admink8s:

    auth get-key client.k8s     # k8s
    auth get-key client.admin   # admin
  2. 根据两个密钥创建一个新的Secret

    # 文件:secret-csi-cephfs.yaml
    # 名称:csi-cephfs-secret
    apiVersion: v1
    kind: Secret
    metadata:
        name: csi-cephfs-secret
        namespace: default
    stringData:
        userID: k8s
        userKey: xxx
        adminID: admin
        adminKey: xxx

3.2.K8S: StorageClass

  1. 先获取集群ID

    ceph -s|grep id|awk -F "[ ]+" '{print $3}'
  2. 编辑配置文件创建SC

    # 文件:sc-csi-cephfs.yaml
    # 名称:ceph-fs-k8s
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
        name: ceph-fs-k8s
    provisioner: cephfs.csi.ceph.com
    parameters:
        clusterID: 79ed3cf4-6a04-11ed-8db2-fa163ec820ba
        fsName: k8s.fs
        csi.storage.k8s.io/provisioner-secret-name: csi-cephfs-secret
        csi.storage.k8s.io/provisioner-secret-namespace: default
        csi.storage.k8s.io/controller-expand-secret-name: csi-cephfs-secret
        csi.storage.k8s.io/controller-expand-secret-namespace: default
        csi.storage.k8s.io/node-stage-secret-name: csi-cephfs-secret
        csi.storage.k8s.io/node-stage-secret-namespace: default
    reclaimPolicy: Delete
    allowVolumeExpansion: true
    mountOptions:
        - discard
  3. 将Yaml文件内容配置到K8S

    kubectl apply -f sc-csi-cephfs.yaml
    kubectl get sc
  4. 最后创建PVC验证当前SC是否可用:

    # 文件:pvc-csi-fs.yaml
    # 名称:test-fs-pvc
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
        name: test-fs-pvc     #pvc名称
    spec:
        accessModes:
            - ReadWriteOnce     #访问模式
        resources:
            requests:
                storage: 1Gi           #存储空间
        storageClassName: ceph-fs-k8s   #后端storageclass名称
  5. 最后切换默认SC:

    kubectl patch storageclass ceph-fs-k8s \
        -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
    kubectl get sc

4. 问题

4.1. FailedScheduling

Warning  FailedScheduling  2m16s  default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) didn't match pod anti-affinity rules. preemption: 0/3 nodes are available: 1 Preemption is not helpful for scheduling, 2 node(s) didn't match pod anti-affinity rules.

    这个问题的主要原因是K8S的Master节点默认是不参与调度的,且在Master节点上有一个污点NoSchedule(表示K8S不会将Pod调度到具有污点的Node上),若想让master节点参与调度,则需要先删除污点,允许k8s将Pod调度到该节点Node上,再添加master为nodes角色。

  1. 先查看node以及污点

    kubectl get nodes
    kubectl describe node k8s-master |grep Taints
  2. 删除污点

    kubectl taint nodes --all node-role.kubernetes.io/control-plane-
  3. 再查看Pods运行情况:

    kubectl get pods --all-namespaces -o wide

Last updated