干货 | eBay Kubernetes集群的存储实践
导读
Kubernetes作为eBay内部广泛使用的容器管理平台,承担着巨大的存储功能。本文将从本地存储、网络存储、应用场景、磁盘监控、管理部署和后续工作这几方面介绍eBay Kubernetes集群的存储实践。
如今,eBay已在内部广泛使用Kubernetes作为容器管理的平台,并自研了AZ和联邦级别的控制平面,用以负责50多个集群的创建、部署、监控、修复等工作,并且规模在不断扩大。
我们的生产集群上,针对各种应用场景,大量使用了本地存储和网络存储,并通过原生的PV/PVC来使用。其中本地存储分为静态分区类型和基于lvm的动态类型,支持ssd, hdd, nvme等介质。网络块存储使用ceph RBD和ISCSI,共享文件存储使用cephfs和nfs。
一、本地存储
我们最早于2016年开始做localvolume(本地卷),当时社区还没有本地的永久存储方案,为了支持内部的NoSQL应用使用PV(Persistent Volume),开发了第一版的localvolume方案:
首先,在节点创建的时候,provision系统根据节点池flavor定义对数据盘做分区和格式化,并将盘的信息写入系统配置文件。
同时,我们在集群内部署了daemonset localvolume-provisioner,当节点加入集群后,provisioner会从配置文件中读取配置信息并生成相应的PV,其中包含相应的path,type等信息。这样,每个PV对象也就对应着节点上的一个分区。
除此之外,我们改进了scheduler,将本地 PV/PVC的绑定(binding)延迟到scheduler里进行。这也对应现在社区的volumeScheduling feature。
现在cgroup v1不能很好地支持buffer io的限流和隔离,对于一些io敏感的应用来说,需要尽可能防止这些“noisy neighbors”干扰。同时对于disk io load很高的应用,应尽可能平均每块盘的负担,延长磁盘寿命。
因此,我们增加了PVC的反亲和性(anti affinity)调度特性,在满足节点调度的同时,会尽可能调度到符合反亲和性规则的盘上。
具体做法是,在PV中打上标签表明属于哪个节点的哪块盘,在PVC中指定反亲和性规则,如下图一所示。scheduler里增加相应的预选功能,保证声明了同类型的反亲和性的PVC,不会绑定到处在同一块盘的PV上,并在最终调度成功后,完成选定PV/PVC的绑定。
图1(点击可查看大图)
对于上述静态存储的方案,PV大小是固定的,我们同时希望volume空间能够更灵活地按需申请,即动态分配存储。
类似地,我们在节点flavor里定义一个vg作为存储池,节点创建的时候,provision系统会根据flavor做好分区和vg的创建。同时集群内部署了daemonset local-volume-dynamic-provisioner,实现CSI的相应功能。
在CSI 0.4版本中,该daemonset由CSI的四个基本组件组成,即:csi-attacher, csi-provisioner, csi-registrar以及csi-driver。其中csi-attacher, csi-provisioner和csi-registrar为社区的sidecar controller。csi-driver是根据存储后端自己实现CSI接口, 目前支持xfs和ext4两种主流文件系统,也支持直接挂载裸盘供用户使用。
为了支持scheduler能够感知到集群的存储拓扑,我们在csi-registrar中把从csi-driver拿到的拓扑信息同步到Kubernetes节点中存储,供scheduler预选时提取,避免了在kubelet中改动代码。
如图2所示,pod创建后,scheduler将根据vg剩余空间选择节点、local-volume-dynamic-provisioner来申请相应大小的lvm logical volume,并创建对应的PV,挂载给pod使用。
二、网络存储
对于网络块存储,我们使用ceph RBD和ISCSI作为存储后端,其中ISCSI为远端SSD,RBD为远端HDD,通过openstack的cinder组件统一管理。
网络存储卷的管理主要包括provision/deletion/attach/detach等,在provision/deletion的时候,相比于localvolume(本地卷)需要以daemonset的方式部署,网络存储只需要一个中心化的provisioner。
我们利用了社区的cinder provisioner方案(详情可见:https://github.com/kubernetes/cloud-provider-openstack),并加以相应的定制,比如支持利用已有快照卷(snapshot volume)来创建PV,secret统一管理等。
Provisioner的基本思路是:
watch PVC创建请求
→ 调用cinder api创建相应类型和大小的卷,获得卷id
→ 调用cinder的initialize_connection api,获取后端存储卷的具体连接信息和认证信息,映射为对应类型的PV对象
→ 往apiserver发请求创建PV
→ PV controller负责完成PVC和PV的绑定。
Delete为逆过程。
Attach由volume plugin或csi来实现,直接建立每个节点到后端的连接,如RBD map, ISCSI会话连接,并在本地映射为块设备。这个过程是分立到每个节点上的操作,无法在controller manager里实现中心化的attach/detach。因此放到kubelet或csi daemonset来做,而controller manager主要实现逻辑上的accessmode的检查和volume接口的伪操作,通过节点的状态与kubelet实现协同管理。
Detach为逆过程。
在使用RBD的过程中,我们也遇到过一些问题:
1)RBD map hang:
RBD map进程hang,然而设备已经map到本地并显示为/dev/rbdX。经分析,发现是RBD client端的代码在执行完attach操作后,会进入顺序等待udevd event的loop,分别为"subsystem=rbd" add event和"subsystem=block" add event。而udevd并不保证遵循kernel uevent的顺序,因此如果"subsystem=block" event先于 "subsystem=rbd" event, RBD client将一直等待下去。通过人为触发add event(udevadm trigger --type=devices --action=add),就可能顺利退出。这个问题后来在社区得到解决,我们反向移植(backport)到所有的生产集群上。
(详情可见:https://tracker.ceph.com/issues/39089)
2)kernel RBD支持的RBD feature非常有限,很多后端存储的特性无法使用。3)当节点map了RBD device并被container使用,节点重启会一直hang住,原因是network shutdown先于RBD umount,导致kernel在cleanup_mnt()的时候kRBD连接ceph集群失败,进程处于D状态。我们改变systemd的配置ShutdownWatchdogSec为1分钟,来避免这个问题。如上,rbd-nbd在使用上有16个device的限制,同时会耗费更多的cpu资源,综合考虑我们的使用需求,决定继续使用kRBD。
图4为三类块存储的性能比较:
我们主要使用cephfs作为存储后端,cephfs可以使用kernel mount,也可以使用cephfs-fuse mount,类似于前述kRBD和librbd的区别。前者工作在内核态,后者工作在用户态。
经过实际对比,发现性能上fuse mount远不如kernel mount,而另一方面,fuse能更好地支持后端的feature,便于管理。目前社区cephfs plugin里默认使用ceph fuse,为了满足部分应用的读写性能要求,我们提供了pod annotation(注解)选项,应用可自行选择使用哪类mount方式,默认为fuse mount。
下面介绍一下在使用ceph fuse的过程中遇到的一些问题(ceph mimic version 13.2.5, kernel 4.15.0-43)
1)ceph fuse internal type overflow导致mount目录不可访问
ceph fuse设置挂载目录dentry的attr_timeout为0,应用每次访问时kernel都会重新验证该dentry cache是否可用,而每次lookup会对其对应inode的reference count + 1。
经过分析,发现在kernel fuse driver里count是uint_64类型,而ceph-fuse里是int32类型。当反复访问同一路径时,ref count一直增加,如果节点内存足够大,kernel没能及时触发释放 dentry缓存,会导致ceph-fuse里ref count值溢出。针对该问题,临时的解决办法是周期性释放缓存(drop cache),这样每次会生成新的dentry,重新开始计数。同时我们存储的同事也往ceph社区提交补丁,将ceph-fuse中该值改为uint_64类型,同kernel 匹配起来。(详情可见:https://tracker.ceph.com/issues/40775 )
kubelet会周期性通过du来统计emptydir volume使用情况,我们发现在部分节点上存在大量du进程hang,并且随着时间推移数量越来越多,一方面使系统load增高,另一方面耗尽pid资源,最终导致节点不响应。
经分析,du会读取到cephfs路径,而cephfs不可达是导致du hang的根本原因,主要由以下两类问题导致:
a. 网络问题导致mds连接断开。如图5所示,通过ceph admin socket,可以看到存在失效链接(stale connection),原因是client端没有主动去重连,导致所有访问mount路径的操作hang在等待fuse answer上,在节点启用了client_reconnect_stale选项后,得到解决。b. mds连接卡在opening状态,同样导致du hang。原因是服务端打开了mds_session_blacklist_on_evict,导致连接出现问题时客户端无法重连。3)性能
kernel mount性能远高于fuse性能,经过调试,发现启用了fuse_big_write后,在大块读写的场景下,fuse性能几乎和kernel差不多。
三、应用场景
本地存储相比网络存储,具有成本低,性能高的优点,但是如果节点失效,将会导致数据丢失,可靠性比网络存储低。
为了保证数据可靠性,应用实现了自己的备份还原机制。使用本地PV存储数据,同时挂载RBD类型的PV,增量传输数据至远端备份集群。同时远端会根据事先定义规则,周期性地在这些RBD盘上打snapshot(快照),在还原的时候,选定特定snapshot,provision出对应PV,并挂载到节点上,恢复到本地PV。
对于安全要求级别高的应用,如支付业务,我们使用了kata安全容器方案,同时对kata container的存储进行加密。如图6所示,我们使用了kernel dm-crypt对盘进行加密,并将生成的key对称加密存入eBay的密钥管理服务中,最后给container使用的是解密后的盘,在pod生命周期结束后,会关闭加密盘,防止数据泄漏。
四、磁盘监控
对于本地存储来说,节点坏盘,丢盘等错误,都会影响到线上应用,需要实时有效的监控手段。我们基于社区的node-problem-detector项目,往其中增加了硬盘监控(disk monitor)的功能。
(详情可见:https://github.com/Kubernetes/node-problem-detector)主要监控手段有三类:
1)smart工具检测每块盘的健康状况。
2)系统日志中是否有坏盘信息。根据已有的模式(pattern)对日志进行匹配,如图7所示。3)丢盘检测,对比实际检测到的盘符和节点flavor定义的盘符。以上检测结果以metrics(指标)的形式被prometheus收集,同时也更新到自定义crd computenode的状态中,由专门的remediation controller(修复控制器)接管,如满足预定义的节点失效策略,将会进入后续修复流程。
对于有问题的盘,monitor会对相应PV标记taint,scheduler里会防止绑定到该类PV,同时对于已绑定的PV,会给绑定到的PVC发event,通知应用。
五、管理部署
六、后续工作
1)对于网络存储,将后端控制面由cinder切换到SDS,届时将会对接新的SDS api,实现新的dynamic provision controller和csi插件;
2)实现Kubernetes平台上的volume snapshot(卷快照)功能;3)将in-tree 的volume插件全部迁移到CSI,并将CSI升级到最新版本,方便部署和升级;4)引入cgroup v2, 以实现blkio qos控制;5)实现本地存储的自动扩容能力。您可能还感兴趣:
实战 | eBay PB级日志系统的存储方案实践SRE重案调查组 第四集 | JVM元数据区的内存泄漏之谜SRE重案调查组 第三集 | 探秘HTTP异步请求的“潘多拉魔盒”SRE重案调查组 第二集 | 挖掘应用处理变慢的“真相”
SRE重案调查组 第一集 | 高延迟问题的罪魁祸首System.gc()
分享 | 基于kubernetes的VM解决方案探讨
eBay2020校招现已正式启动!详情点击下方链接:
↓点击阅读原文,直接开启网申之旅!