kubernetes笔记: Ceph RBD
by 伊布
背景
为什么k8s需要RBD呢?以前用到的volume,有 configmap 、empty dir、hostpath,configmap通常是用来向容器注入配置的,而empty dir、hostpath 也能够用来存储数据,但他们都有一个致命的问题:如果容器被删除了,数据也就跟着丢失了。这对一些无状态应用来说没什么,因为他们的数据可能早就进数据库了,但对一些有状态的应用来说(比如说mysql),就是个问题了。
Ceph RBD、Cephfs,都是解决这个问题的办法。
Ceph RBD是个什么东西呢?
我们知道,存储可以分为三类:对象存储(例如aws s3、aliyun oss)、文件存储(例如nfs、nas)、块存储。而Ceph RBD(RADOS Block Devices)即为块存储的一种,下文的Cephfs则是文件存储的一种。
下面简要走一下ceph RBD的使用。
ceph集群
因为公司有专门团队做Ceph,我只管用就行,跳过Ceph集群的安装过程。对于k8s来说,需要的Ceph信息有这些:
IP地址、端口号 管理员用户名 管理员keyring
node上准备ceph
接下来会用一些ceph的命令行,用来根ceph集群交互。
yum/apt install ceph-common ceph-fs-common -y
ceph命令行需要2个配置文件,具体配置问ceph团队就行了。
- /etc/ceph/ceph.conf
- /etc/ceph/ceph.client.admin.keyring
RBD是这样用的:用户在Ceph上创建Pool(逻辑隔离),然后在Pool中创建image(实际存储介质),之后再将image挂载到本地服务器的某个目录上。
记住下面几条命令就行了。
# rbd list #列出默认pool下的image
# rbd list -p k8s #列出pool k8s下的image
# rbd create foo -s 1024 #在默认pool中创建名为foo的image,大小为1024MB
# rbd map foo #将ceph集群的image映射到本地的块设备
/dev/rbd0
# ls -l /dev/rbd0 #是b类型
brw-rw---- 1 root disk 252, 0 May 22 20:57 /dev/rbd0
$ rbd showmapped #查看已经map的rbd image
id pool image snap device
0 rbd foo - /dev/rbd0
# mount /dev/rbd1 /mnt/bar/ #此时去mount会失败,因为image还没有格式化文件系统
mount: /dev/rbd1 is write-protected, mounting read-only
mount: wrong fs type, bad option, bad superblock on /dev/rbd1,
missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so.
# mkfs.ext4 /dev/rbd0 #格式化为ext4
...
Writing superblocks and filesystem accounting information: done
# mount /dev/rbd0 /mnt/foo/ #重新挂载
# df -h |grep foo #ok
/dev/rbd0 976M 2.6M 907M 1% /mnt/foo
如果你有自己挂载过硬盘的经验,对RBD的操作应该是很快就能熟悉了。
上面都是在默认pool中做的,我们在k8s里,当然要用自己的pool了。
# ceph osd lspools # 看看已经有那些pool
# ceph osd pool create k8s 128 #创建pg_num 为128的名为k8s的pool
# rados df
有了自己专属的 pool以后,把上面创建image的过程重新走一下吧。
# rbd create foobar -s 1024 -p k8s #在k8s pool中创建名为foobar的image,大小为1024MB
不要挂载!不要挂载!不要挂载!
RBD用作volume
前面已经创建好了image foobar,给volume用简单直接粗暴,挂就行了。
apiVersion: v1
kind: Pod
metadata:
name: rbd
spec:
containers:
- image: gcr.io/nginx
name: rbd-rw
volumeMounts:
- name: rbdpd
mountPath: /mnt/rbd
volumes:
- name: rbdpd
rbd:
monitors:
- '1.2.3.4:6789'
pool: k8s
image: foobar
fsType: ext4
readOnly: false
user: admin
keyring: /etc/ceph/ceph.client.admin.keyring
Pod启动后,可以看到文件系统由k8s做好并挂载到了容器里。我们将/etc/hosts文件拷贝到/mnt/rbd/目录去。
# kubectl exec rbd -- df -h|grep rbd
/dev/rbd6 976M 2.6M 907M 1% /mnt/rbd
# kubectl exec rbd -- cp /etc/hosts /mnt/rbd/
# kubectl exec rbd -- cat /mnt/rbd/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.244.3.249 rbd
然后将Pod删除、重新挂载foobar image。
前面Pod要求各node上都要有keyring文件,很不方便也不安全。新的Pod我使用推荐的做法:secret(虽然也安全不到哪里)
先创建一个secret。
apiVersion: v1
kind: Secret
metadata:
name: ceph-secret
type: "kubernetes.io/rbd"
data:
key: QVFCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX9PQ==
在新的Pod里Ref这个secret。
apiVersion: v1
kind: Pod
metadata:
name: rbd3
spec:
containers:
- image: gcr.io/nginx
name: rbd-rw
volumeMounts:
- name: rbdpd
mountPath: /mnt/rbd
volumes:
- name: rbdpd
rbd:
monitors:
- '1.2.3.4:6789'
pool: k8s
image: foobar
fsType: ext4
readOnly: false
user: admin
secretRef:
name: ceph-secret
再来看看前面写到image上的文件还在不在。
kubectl exec rbd3 -- cat /mnt/rbd/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.244.3.249 rbd
你会发现,哎,文件还在哎!对,这就是RBD的效果。但注意,Volume已经不存在了,虽然数据保住了,但这其实不是我们希望的持久化存储的方式。
RBD用作PV/PVC
volume是一种比较初级的使用方式,中级的方式是使用PV/PVC。
简单说说PV/PVC。集群管理员会创建多个PV(Persist Volume,持久化卷,不区分namespace),而普通用户会创建PVC(PV Claim,PV声明),k8s会从满足PVC的要求中选择一个PV来与该PVC绑定,之后再将PV对应的image挂载到容器中。
来看个例子。
先在k8s pool里创建一个名为pv的 image。
rbd image create pv -s 1024 -p k8s
再创建一个PV,使用上面创建的image pv。
apiVersion: v1
kind: PersistentVolume
metadata:
name: ceph-rbd-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
rbd:
monitors:
- '1.2.3.4:6789'
pool: k8s
image: pv
user: admin
secretRef:
name: ceph-secret
fsType: ext4
readOnly: false
persistentVolumeReclaimPolicy: Recycle
看下现在pv的状态,还是 Available。
kubectl get pv|grep rbd
ceph-rbd-pv 1Gi RWO Recycle Available
创建一个PVC,要求一块1G的存储。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ceph-rbd-pv-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
因为上面已经创建满足要求的PV了,可以看到pvc和pv的状态都已经是Bound了。
# kubectl get pvc|grep rbd
ceph-rbd-pv-claim Bound pvc-bdaf8358-5dc6-11e8-a37d-ecf4bbdeea94 1Gi RWO fast 10s
# kubectl get pv|grep ceph-rbd-pv
ceph-rbd-pv 1Gi RWO Recycle Bound 12m
RBD用作storage class
为什么前面说PV/PVC是一个“中级”解决方案呢?
很简单啊,管理员不可能没事就等着给人创建RBD image、PV,而一次创建大量PV等着人用又显得很不云计算。
storage class的出现,就是为了解决这个问题。
简单来说,storage创建需要的材料,只需要访问ceph RBD的IP/Port、用户名、keyring、pool,不需要提前创建image;当用户创建一个PVC时,k8s查找是否有符合PVC请求的storage class类型,如果有,则依次:
- 到ceph集群上创建image
- 创建一个PV,名字为pvc-xx-xxx-xxx,大小pvc请求的storage。
- 将上面的PV与PVC绑定,格式化后挂到容器中
是不是很简单?管理员只要创建好storage class就行了,后面的事情用户自己就可以搞定了。当然了,为了防止流氓用户,设置一下Resource Quota还是很有必要的。
下面走一个。
先创建一个SC。跟PV一样,SC也是集群范围的(RBD认为是fast的)。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: fast
provisioner: kubernetes.io/rbd
parameters:
monitors: 172.25.60.3:6789
adminId: admin
adminSecretName: ceph-secret
adminSecretNamespace: resource-quota
pool: k8s
userId: admin
userSecretName: ceph-secret
fsType: ext4
imageFormat: "2"
imageFeatures: "layering"
之后创建应用的时候,需要同时创建 pvc+pod,二者通过claimName关联。pvc中需要指定其storageClassName为上面创建的sc的name(即fast)。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: rbd-pvc-pod-pvc
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 8Gi
storageClassName: fast
RBD只支持 ReadWriteOnce 和 ReadOnlyAll,不支持ReadWriteAll。注意这两者的区别点是,不同nodes之间是否可以同时挂载。同一个node上,即使是ReadWriteOnce,也可以同时挂载到2个容器上的。
apiVersion: v1
kind: Pod
metadata:
labels:
test: rbd-pvc-pod
name: ceph-rbd-sc-pod2
spec:
containers:
- name: ceph-rbd-sc-nginx
image: gcr.io/nginx
volumeMounts:
- name: ceph-rbd-vol1
mountPath: /mnt/ceph-rbd-pvc/nginx
readOnly: false
volumes:
- name: ceph-rbd-vol1
persistentVolumeClaim:
claimName: rbd-pvc-pod-pvc
其实我觉得PVC的创建都可以直接省掉,可以新增一个类型,直接Pod请求就得了,类似下面StatefulSets里的volumeClaimTemplates。
如果是多副本的应用怎么办呢?可以用StatefulSet。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: gcr.io/nginx
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "fast"
resources:
requests:
storage: 8Gi
此时去看PVC,可以看到创建了3个PVC。
# kubectl get pvc|grep www
www-nginx-0 Bound pvc-05f10f64-58df-11e8-8bd4-ecf4bbdeea94 8Gi RWO fast 6d
www-nginx-1 Bound pvc-0e1768f3-58df-11e8-8bd4-ecf4bbdeea94 8Gi RWO fast 6d
www-nginx-2 Bound pvc-17347e8e-58df-11e8-8bd4-ecf4bbdeea94 8Gi RWO fast 6d
# kubectl get pv|grep www
pvc-05f10f64-58df-11e8-8bd4-ecf4bbdeea94 8Gi RWO Delete Bound dex/www-nginx-0 fast 6d
pvc-0e1768f3-58df-11e8-8bd4-ecf4bbdeea94 8Gi RWO Delete Bound dex/www-nginx-1 fast 6d
pvc-17347e8e-58df-11e8-8bd4-ecf4bbdeea94 8Gi RWO Delete Bound dex/www-nginx-2 fast 6d
但注意不要用Deployment。因为,如果Deployment的副本数是1,那么还是可以用的,跟Pod一致;但如果副本数 >1 ,此时创建deployment后会发现,只启动了1个Pod,其他Pod都在ContainerCreating状态。过一段时间describe pod可以看到,等volume等很久都没等到。
Unable to mount volumes for pod "nginx-deployment-55bd845d95-vjs58_dex(73be7605-5dcf-11e8-a37d-ecf4bbdeea94)": timeout expired waiting for volumes to attach or mount for pod "default"/"nginx-deployment-55bd845d95-vjs58". list of unmounted volumes=[nginx-rbd-v1]. list of unattached volumes=[nginx-rbd-v1 default-token-62cfx]
总结
本文介绍了kubernetes使用RBD的必要性,一些简单的RBD命令行及RBD手工操作方式,以及RBD在Volume、PV、StorageClass三种方式下的使用。
Ref
Subscribe via RSS