死生契阔,与子成说。执子之手,与子偕老。——《诗经》
前边三个章节我们已经搭建好了K8S、Ceph、Harbor,基础环境已经有了,接下来这个章节就让K8S和Ceph联动起来,将Ceph作为K8S的底层存储引擎让二者协同工作,不论是之后的KubeSphere还是TiDB都可以直接使用本章配置的结果来访问底层存储。
K8S和Ceph集成有三种实现方式:
StorageClass动态存储,动态创建PV、PVC
Ceph支持K8S存储有两种类型:
注意 :一般写CephFS会将Ceph和FS连写,而RBD则中间加一空白来写!
1. ceph-csi
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组件共同完成存储相关的操作。
组件 作用 Watch PVC对象,调用csi plugin创建存储,最终创建PV。
Watch VolumeAttachment对象,调用csi plugin做attach/dettach操作。
Watch PVC对象,调用csi plugin来做存储扩容。
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
默认情况下,Ceph块设备使用rbd池,使用下边命令为K8S创建一个池:
Copy # 存储池名称:k8s.store
# 在目前三节点的集群中,推荐后边不使用128 128,而是32 32两个值,否则等待更新的时间会很长
ceph osd pool create k8s.store 128 128
# 新创建的池必须在使用之前初始化,此时需要使用rbd工具初始化
rbd pool init k8s.store
您可以登录Ceph集群管理界面等待任务执行完成(截图中可以看到Applications类型 是rbd
):
为K8S创建新的Ceph账号
Copy # 账号信息
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
账号创建完成后,ceph-csi需要一个存储在K8S中的ConfigMap对象来定义Ceph集群的Ceph mon地址
Copy ceph mon dump
ceph -s | grep mon
2.2.K8S: ConfigMap
将部署配置放在~/deploy/ceph
中,clusterID需要和上边取得的id保持一致,并且需要修改mon地址,多个地址后面要使用逗号分隔。
Copy cd /root/deploy/ceph
vim config-map-csi.yaml
文件内容如下:
Copy # 文件: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
在K8S中创建该ConfigMap对象:
Copy kubectl apply -f config-map-csi.yaml
kubectl get cm ceph-csi-config
按照步骤4-5创建额外的几个ConfigMap(必须)
Copy # 文件: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
ceph-csi
需要创建ServiceAccount和RBAC用于访问集群内部信息,先下载yaml文件:
Copy # 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
将下载的两个yaml文件内容配置到K8S中:
Copy kubectl apply -f csi-provisioner-rbac.yaml
kubectl apply -f csi-nodeplugin-rbac.yaml
2.4.K8S: provisioner
下载两个yaml文件到本地:
Copy # 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
查看第一个yaml文件中的镜像,手动拉取镜像(推荐)
Copy # 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
将两个配置文件内容配置到K8S中:
Copy kubectl apply -f csi-rbdplugin.yaml
kubectl apply -f csi-rbdplugin-provisioner.yaml
查看csi插件是否已经启动,FailedScheduling
问题参考问题章节:
2.5.K8S: Secret
获取ceph
用户信息,找到client.admin
用户,复制后边的Key,然后创建一个Secret对象:
Copy # 文件:secret-csi-rbd.yaml
# 名称:csi-rbd-secret
apiVersion : v1
kind : Secret
metadata :
name : csi-rbd-secret
namespace : default
stringData :
userID : k8s
userKey : xxx
最终结果如下:
Copy kubectl get secret
kubectl get cm
2.6.K8S: StorageClass
先获取集群ID
Copy ceph -s | grep id | awk -F "[ ]+" '{print $3}'
创建新的yaml文件(StorageClass)
Copy # 文件: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
将Yaml文件内容配置到K8S
Copy kubectl apply -f sc-csi-rbd.yaml
kubectl get sc
注 :上述ceph-rbd-k8s
如果并没有带(default)
标记,证明它不是默认的SC,您需要执行如下命令将它设置成默认:
Copy kubectl patch storageclass ceph-rbd-k8s \
-p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl get sc
最后创建PVC
验证当前SC是否可用:
Copy # 文件: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,如果状态不是这个值,证明有问题,可使用如下命令查看日志:
Copy describe pvc test-block-pvc
2.7. imageFeatures
上述SC中配置了一个imageFeatures
的属性,对Ceph而言,该属性的值如下:
类型 特性 image的克隆操作,可对image创建快照并保护,然后从快照克隆新的image,父子image之间使用COW技术,共享对象数据。
条带化对象数据,类似raid 0,可改善顺序读写场景较多情况下的性能。
保护image数据一致性,对image做修改时,需要持有此锁。这个可以看做是一个分布式锁,在开启的时候,确保只有一个客户端在访问image,否则锁的竞争会导致io急剧下降。 主要应用场景是qemu live-migration。
此特性依赖于exclusive lock。因为image的对象分配是thin-provisioning,此特性开启的时候,会记录image所有对象的一个位图,用以标记对象是否真的存在,在一些场景下可以加速io。
此特性依赖于object map和exlcusive lock。快速比较image的snapshot之间的差异。
layering特性使得克隆image的时候,父子image之间采用COW,他们之间的对象文件存在依赖关系,flatten操作的目的是解除父子image的依赖关系,但是子image的快照并没有解除依赖,deep-flatten特性使得快照的依赖也解除。
依赖于exclusive lock。将image的所有修改操作进行日志化,并且复制到另外一个集群(mirror),可以做到块存储的异地灾备。这个特性在部署的时候需要新部署一个daemon进程,目前还在试验阶段,不过这个特性很重要,可以做跨集群/机房容灾。
从上面可以看出,很多特性都依赖于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
使用下边命令查看账号信息admin
和k8s
:
Copy auth get-key client.k8s # k8s
auth get-key client.admin # admin
根据两个密钥创建一个新的Secret
Copy # 文件: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
先获取集群ID
Copy ceph -s | grep id | awk -F "[ ]+" '{print $3}'
编辑配置文件创建SC
Copy # 文件: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
将Yaml文件内容配置到K8S
Copy kubectl apply -f sc-csi-cephfs.yaml
kubectl get sc
最后创建PVC
验证当前SC是否可用:
Copy # 文件: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名称
最后切换默认SC:
Copy kubectl patch storageclass ceph-fs-k8s \
-p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl get sc
4. 问题
4.1. FailedScheduling
Copy 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角色。
先查看node以及污点
Copy kubectl get nodes
kubectl describe node k8s-master | grep Taints
删除污点
Copy kubectl taint nodes --all node-role.kubernetes.io/control-plane-
再查看Pods运行情况:
Copy kubectl get pods --all-namespaces -o wide