K8S 如何使用具有配额限制的 ceph 共享存储

前言


首先,你得有个 Ceph 集群,而这个集群具体怎么搭建,这里不展开说,有很多文章讲怎么搭建,至于其中方便点的部署方式,可以尝试使用 ceph-deploy。这里主要说的是,K8S 怎么使用 Ceph 存储,但经过一番探索,发现,K8S 支持 Ceph 共享存储这个事儿虽然不是很复杂,但问题是,想让 K8S 支持带配额能力的 Ceph 还是有那么一点麻烦的。

K8S 在使用共享存储时,其a支持很多 backend,比如 GlusterFS、Ceph,这2个比较典型。之所以选择用 Ceph ,主要有几个方面:

①:Ceph 目前更主流,更多主流大厂在使用,市场更好一些。
②:整体上看,性能上可能 Ceph 更好一些(这个可能仁者见仁智者见智,性能调优之后也不好说,而且他俩的适用场景本身就有一些区别,比如 GlusterFS 更适合存储大文件,但也有 GluterFS 对小文件存储的优化方式)

其实具体用哪个存储,还是要考后边的团队,对什么存储比较熟,更有掌控力。

另外,有些文章可能提到 CephFS(Ceph 文件存储)还不能用到生产环境,而且文章还是18年中旬,但很多英文文章都在17年提及,CephFS 早就 Production Ready 了。在生产环境使用应该问题不大,不过具体能不能在生产环境使用,可能还需要摸索一番。

K8S 如何使用共享存储


那么,K8S 在对分布式存储的支持上,主要有2种方式来做:

①:静态方式:PVC + PV
②:动态方式:PVC + StorageClass

静态方式:PV + PVC

PV 其实就是持久化Volume,它是资源的定义,比如说,存储空间多大、存储类型、存储后端等,它侧重于资源本身。

PVC 是一个声明,声明自己需要多少资源,声明需要多少资源,以及访问模式等,它侧重于“需求”。

在 K8S 中,我们可以为 POD 设置 ResourceRequest,比如 CPU、内存等,然后 K8S 为其匹配合适的 Node 节点,调度过去。同理,K8S 的 PVC 也是 ResourceRequest,只是 K8S 为其匹配的目标是 PV 而已。有一点不同,K8S 为 PVC 匹配得到 PV 后,会做一个绑定,这就意味着,一个 PVC,绑定一个 PV 后,PV 的状态就会变为 Bound(束缚),两者绑定后,不会再与其他资源匹配。

使用 PV 和 PVC 模式,需要注意的是 PV。PVC很简单,但 PV 有点麻烦,其中涉及很多内容,以一个 PV 示例来说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: v1
kind: PersistentVolume
metadata:
finalizers:
- kubernetes.io/pv-protection
name: pvc-21f06e44-fdfe-11e8-be0a-801844e392e8
spec:
accessModes:
- ReadWriteMany
capacity:
storage: 500Mi
cephfs:
monitors:
- 172.18.12.235:6789
- 172.18.12.236:6789
- 172.18.12.237:6789
path: /volumes/kubernetes/kubernetes/kubernetes-dynamic-pvc-21f5369d-fdfe-11e8-91a2-82b342c34790
secretRef:
name: ceph-kubernetes-dynamic-user-21f536d3-fdfe-11e8-91a2-82b342c34790-secret
namespace: cephfs
user: kubernetes-dynamic-user-21f536d3-fdfe-11e8-91a2-82b342c34790
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: pvc-1
namespace: cephfs
resourceVersion: "19338037"
uid: 21f06e44-fdfe-11e8-be0a-801844e392e8
mountOptions:
- --client-quota
persistentVolumeReclaimPolicy: Delete
storageClassName: cephfs

PVC 示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
finalizers:
- kubernetes.io/pvc-protection
name: pvc-1
namespace: cephfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 500Mi
volumeName: pvc-21f06e44-fdfe-11e8-be0a-801844e392e8
status:
accessModes:
- ReadWriteMany
capacity:
storage: 500Mi
phase: Bound

其中的 PV 里的 path 内容,需要我们提前去分布式存储系统上创建好。包括配额,也需要先做好才行。

这就有点麻烦了,我们每次创建一个 PV 之前,需要先在分布式存储系统,创建好path。但频繁这么去做,成本是有点大。所以,K8S 也有一个动态方式。

注意:PVC 有命名空间、PV 没有命名空间。

动态方式:PVC + StorageClass

动态的方式,需要借助 PVC + StorageClass ,PVC 不用说了,主要说一下 StorageClass。先来看一个 StorageClass 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cephfs
mountOptions:
- --client-quota
parameters:
adminId: admin
adminSecretName: ceph-secret-admin
adminSecretNamespace: cephfs
claimRoot: /volumes/kubernetes
monitors: 172.18.12.235:6789,172.18.12.236:6789,172.18.12.237:6789
provisioner: ceph.com/cephfs
reclaimPolicy: Delete

StorageClass 定义了后端存储系统的:类型、 servers、根目录、回收策略等。

我们在创建了 PVC(持久化Volume资源声明)后,K8S 会根据 PVC 和 和 StorageClass,动态创建 PV,然后动态自行去分布式存储系统进行创建目录操作,然后进行挂载,最终映射到 POD 内。用动态方式的好处是,我们只关心资源需求 PVC 就可以了,非常方便。

但是:目前 K8S所有版本(截止到 v1.12.3),对动态方式的支持,还不全面。仅支持一些分布式存储,比如:

Volume Plugin Internal Provisioner Config Example
CephFS - -
Glusterfs ✔️ Glusterfs

注意:Ceph 和 CephFS 不一样,Ceph 是整个服务,而 CephFS 仅仅是 Ceph 的文件存储而已。Ceph 除了文件存储 CephFS 外,还支持 对象存储、块存储。

从上面来看,K8S 根本不支持 CephFS 的 动态管理文件存储支持,所以,要么,我们需要寻找一种使其支持的方式,要么,转投 GlusterFS。我们要做的,就是寻找方式,动态管理 CephFS 的文件存储。

让K8S支持动态管理CephFS(并支持配额)


这里要提一下 K8S 能够支持动态存储管理的简单原理了。

要做到动态存储管理,就意味着2点:

①:用户只需要创建 PVC 资源。
②:K8S 需要监听 PVC 资源的变化,动态创建 PV。
③:用户需要在 POD 中使用 PVC 资源。
④:K8S 需要能够在宿主机创建 Volume。

安装 cephfs-provisioner

其实,在 Kubernetes incubator (K8S 孵化器组)中,专门有一个外部存储支持的项目:https://github.com/kubernetes-incubator/external-storage/ 。这个项目中,有关于 Ceph 动态存储管理的方式,换句话说,这不是官方原装的,所以,我们需要安装它。

在 K8S 中,安装这个项目比较简单,这个项目中,也给出了 deploy 示例。总的来说,需要单独创建 ServiceAccount、Role、ClusterRole、RoleBinding、ClusterRoleBinding 等资源。

其中 ClusterRole 主要用来获取 K8S 监听和操作 PVC、PV 资源的权限、Role 是获取 Secret 资源的权限,因为 K8S 要操作 Ceph 集群,就需要认证信息,而认证信息就是放到 Secret 资源中的。下面是具体的创建内容:

①:首先是 Secret 资源,这个 CephFS 集群的访问权限(文件名如:ceph-secret-admin.yaml ),这个数据,需要你先去 CephFS 集群,通过下面方式获取:

1
ceph auth get-key client.admin | base64

然后,将上面的结果,放到下面的 key 的内容中:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: ceph-secret-admin
namespace: cephfs
type: "kubernetes.io/rbd"
data:
key: QVFDbmhBNWNJSmUvSUxxs32323WC9LZUJJR3EzdnpkOTkrVmc9PQ==

②:创建 ClusterRole 资源,clusterrolebinding.yaml

1
2
3
4
5
6
7
8
9
10
11
12
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: cephfs-provisioner
subjects:
- kind: ServiceAccount
name: cephfs-provisioner
namespace: cephfs
roleRef:
kind: ClusterRole
name: cephfs-provisioner
apiGroup: rbac.authorization.k8s.io

③:创建 ClusterRolebinding 资源,clusterrolebinding.yaml

1
2
3
4
5
6
7
8
9
10
11
12
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: cephfs-provisioner
subjects:
- kind: ServiceAccount
name: cephfs-provisioner
namespace: cephfs
roleRef:
kind: ClusterRole
name: cephfs-provisioner
apiGroup: rbac.authorization.k8s.io

④:创建 Role 资源,role.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cephfs-provisioner
namespace: cephfs
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "get", "delete"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]

⑤:创建 Rolebinding 资源,rolebinding.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cephfs-provisioner
namespace: cephfs
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cephfs-provisioner
subjects:
- kind: ServiceAccount
name: cephfs-provisioner

⑥:创建 ServiceAccount 资源,serviceaccount.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cephfs-provisioner
namespace: cephfs
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cephfs-provisioner
subjects:
- kind: ServiceAccount
name: cephfs-provisioner
[root@node008037 dynimcpv]# cat serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: cephfs-provisioner
namespace: cephfs

⑦:创建 StorageClass 资源,storageClass.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: cephfs
namespace: cephfs
provisioner: ceph.com/cephfs
mountOptions:
- "--client-quota"
parameters:
monitors: 172.18.12.235:6789,172.18.12.236:6789,172.18.12.237:6789
adminId: admin
adminSecretName: ceph-secret-admin
adminSecretNamespace: cephfs
claimRoot: /volumes/kubernetes

⑧:创建 cephfs-provisioner 的 Deployment 资源,cephfs-provisioner-deployment.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: cephfs-provisioner
namespace: cephfs
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: cephfs-provisioner
spec:
containers:
- name: cephfs-provisioner
image: "quay.io/external_storage/cephfs-provisioner:latest"
env:
- name: PROVISIONER_NAME
value: ceph.com/cephfs
command:
- "/usr/local/bin/cephfs-provisioner"
args:
- "-id=cephfs-provisioner-1"
- "-enable-quota=true"
serviceAccount: cephfs-provisioner

好了,执行下面的命令,进行资源创建:

1
2
3
4
5
// 创建 Namespace
kubectl create ns cephfs

// 依次执行下面命令,创建如上资源
kubectl apply -f 文件名

如果如上操作后,cephfs-provisioner 就已经安装好了。上面的文件比较多,其中有几个需要注意的地方:

cephfs-provisioner Deployment 的 args 参数中,有一个 -enable-quota=true ,这个不写的话,是无法做到磁盘配额的。

创建完成后,在 cephfs 命令空间下,会有 cephfs-provisioner 的实例:

1
2
3
[root@nodex test]# kubectl get pods -n cephfs
NAME READY STATUS RESTARTS AGE
cephfs-provisioner-6f77cd58b4-75hll 1/1 Running 0 16h

测试CephFS动态管理

上面准备工作 cephfs-provisioner 已经安装好,下面就需要测试了,我们测试,其实仅需要创建2个资源,一个是 PVC、一个是 Deployment 的 POD 资源。

PVC 资源:pvc.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-1
namespace: cephfs
annotations:
volume.beta.kubernetes.io/storage-class: "cephfs"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 500Mi

Deployment 资源:deploy.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-use-rbd
namespace: cephfs
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.13
imagePullPolicy: IfNotPresent
name: nginx-use-rbd
volumeMounts:
- mountPath: /test-pvc
name: cephfs-pvc
volumes:
- name: cephfs-pvc
persistentVolumeClaim:
claimName: pvc-1

然后,分别用 kubectl apply -f 文件名 创建即可。

问题处理


创建后,我们需要观察在 cephfs 命名空间下,有没有自动创建出来 pv。

1
kubectl get pv -n cephfs -w

如果自动创建,且后期自动完成了匹配,且 nginx POD 创建后变为 Running ,则表示一切正常。但往往事实并不如人意。我们可能遇到下面几个问题:

①:PV 没有自动创建出来。
②:我们使用了共享存储的 nginx 示例 POD 一直是 ContainerCreating 状态。

PV 无法自动创建问题

如果是 PV 没自动创建出来 ,说明 cephfs-provisioner 有问题,它没能创建好相关的资源,我们主要看它的日志信息

1
kubectl logs -n cephfs cephfs-provisioner-6f77cd58b4-75hll

通常来说,PV 没创建出来最大的可能就是出在 cephfs-provisioner-6f77cd58b4-75hll 这个 POD 上,其实这个 POD 做的工作主要有几个:

①:连接 CephFS 集群
②:创建 Volume
③:设置配额属性,setxattr
④:创建 PV

而这个 cephfs-provisioner 本身操作 CephFS 集群的方式,其实是通过 Golang,调用的 Python 脚本来做的这个事情。如果观察其 log ,出错在 setxattr 部分,则最大的可能性,就是版本问题。

cephfs-provisioner 处理方式如下:

官方的镜像,在我们之前的 cephfs-provisioner-deployment.yaml 中,其实它使用的 Ceph 的版本,默认是 mimic ,可以从这里看:https://github.com/kubernetes-incubator/external-storage/blob/master/ceph/cephfs/Dockerfile ,而我们的 Ceph 版本,是 hammer,而官方并没有提供 hammer 版本的 cephfs-provisioner,所以,我们只能自己来构建。构建步骤如下:

  1. 克隆 https://github.com/kubernetes-incubator/external-storage 项目。
  2. 进入 ceph/cephfs 目录下
  3. 修改 Dockerfile ,将 CEPH_VERSION 里的 mimic ,改为 hammer 。
  4. 修改 Makefile,将 REGISTRY 里的地址,改为私有镜像仓库地址,目的是推送镜像使用。
  5. 执行 make && make push 。

完成后,更改 deploy.yaml 里 image 为新镜像地址,然后执行 kubectl apply -f deploy.yaml

完成后,观测 cephfs-provisioner POD 正常启动,同时看到 PV 自动被创建出来了。

POD持续ContainerCreating状态处理

这种问题,很有可能是 POD 使用了 PVC 形式的 Volume,而这个 Volume 却迟迟无法创建出来,所以,这时候,可以通过 kubectl describe 来看具体原因

1
kubectl describe pod -n cephfs nginx-use-rbd-dcfb58dc6-8f7gj
1
2
3
[root@node008037 glusterfs]# kubectl get pods -n test -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-rbd-tttttt-77bc5d46b7-5w294 0/1 ContainerCreating 0 9m <none> node012044 <none>

如果通过结果能看到相关 Volume 信息,且在不断输出重试内容,则大概能说明是 Volume 无法创建,进而无法挂载到容器内部导致的

1
2
3
4
5
6
7
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 9m default-scheduler Successfully assigned test/nginx-rbd-tttttt-77bc5d46b7-5w294 to node012044
Normal SuccessfulAttachVolume 9m attachdetach-controller AttachVolume.Attach succeeded for volume "pvc-c7c9dcce-0298-11e9-be0a-801844e392e8"
Warning FailedMount 1m (x11 over 7m) kubelet, node012044 MountVolume.WaitForAttach failed for volume "pvc-c7c9dcce-0298-11e9-be0a-801844e392e8" : fail to check rbd image status with: (executable file not found in $PATH), rbd output: ()
Warning FailedMount 34s (x4 over 7m) kubelet, node012044 Unable to mount volumes for pod "nginx-rbd-tttttt-77bc5d46b7-5w294_test(4c970495-0299-11e9-be0a-801844e392e8)": timeout expired waiting for volumes to attach or mount for pod "test"/"nginx-rbd-tttttt-77bc5d46b7-5w294". list of unmounted volumes=[cephfs-pvc]. list of unattached volumes=[cephfs-pvc default-token-89ltk]

这种情况其实是宿主机缺少 ceph 的 client 工具,可以通过下面的方式解决:

1
2
3
// centos 系统
yum -y install ceph-common
yum -y install ceph-fuse

安装结束后,k8s 会自动尝试重新挂载。

参考


Donate comment here