首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

eBay Kubernetes集群的存储实践

如今,eBay已在内部广泛使用Kubernetes作为容器管理的平台,并自研了AZ和联邦级别的控制平面,用以负责50多个集群的创建、部署、监控、修复等工作,并且规模在不断扩大。

我们的生产集群上,针对各种应用场景,大量使用了本地存储网络存储,并通过原生的PV/PVC来使用。其中本地存储分为静态分区类型基于lvm的动态类型,支持ssd, hdd, nvme等介质。网络块存储使用ceph RBD和ISCSI,共享文件存储使用cephfs和nfs。

一、本地存储

01 静态分区

我们最早于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(点击可查看大图)

02 LVM动态存储

对于上述静态存储的方案,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使用。

图2(点击可查看大图)

二、网络存储

01 块存储

对于网络块存储,我们使用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/390892)kernel RBD支持的RBD feature非常有限,很多后端存储的特性无法使用。 3)当节点map了RBD device并被container使用,节点重启会一直hang住,原因是network shutdown先于RBD umount,导致kernel在cleanup_mnt()的时候kRBD连接ceph集群失败,进程处于D状态。我们改变systemd的配置ShutdownWatchdogSec为1分钟,来避免这个问题。

除了kernel RBD模块,Ceph也支持块存储的用户态librbd实现:rbd-nbd。Kubernetes也支持使用rbd-nbd。

如图3所示,我们对kRBD和rbd-nbd做了对比:

图3(点击可查看大图)

如上,rbd-nbd在使用上有16个device的限制,同时会耗费更多的cpu资源,综合考虑我们的使用需求,决定继续使用kRBD。

图4为三类块存储的性能比较:

图4(点击可查看大图)

02 文件存储

我们主要使用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

2)kubelet du hang

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,导致连接出现问题时客户端无法重连。

图5(点击可查看大图)

3)性能

kernel mount性能远高于fuse性能,经过调试,发现启用了fuse_big_write后,在大块读写的场景下,fuse性能几乎和kernel差不多。

三、应用场景

01 本地数据备份还原

本地存储相比网络存储,具有成本低,性能高的优点,但是如果节点失效,将会导致数据丢失,可靠性比网络存储低。

为了保证数据可靠性,应用实现了自己的备份还原机制。使用本地PV存储数据,同时挂载RBD类型的PV,增量传输数据至远端备份集群。同时远端会根据事先定义规则,周期性地在这些RBD盘上打snapshot(快照),在还原的时候,选定特定snapshot,provision出对应PV,并挂载到节点上,恢复到本地PV。

02 盘加密

对于安全要求级别高的应用,如支付业务,我们使用了kata安全容器方案,同时对kata container的存储进行加密。如图6所示,我们使用了kernel dm-crypt对盘进行加密,并将生成的key对称加密存入eBay的密钥管理服务中,最后给container使用的是解密后的盘,在pod生命周期结束后,会关闭加密盘,防止数据泄漏。

图6(点击可查看大图)

四、磁盘监控

对于本地存储来说,节点坏盘,丢盘等错误,都会影响到线上应用,需要实时有效的监控手段。我们基于社区的node-problem-detector项目,往其中增加了硬盘监控(disk monitor)的功能。

(详情可见:https://github.com/Kubernetes/node-problem-detector)

主要监控手段有三类:

1)smart工具检测每块盘的健康状况。 2)系统日志中是否有坏盘信息。根据已有的模式(pattern)对日志进行匹配,如图7所示。 3)丢盘检测,对比实际检测到的盘符和节点flavor定义的盘符。

图7(点击可查看大图)

以上检测结果以metrics(指标) 的形式被prometheus收集,同时也更新到自定义crd computenode的状态中,由专门的remediation controller(修复控制器)接管,如满足预定义的节点失效策略,将会进入后续修复流程。

对于有问题的盘,monitor会对相应PV标记taint,scheduler里会防止绑定到该类PV,同时对于已绑定的PV,会给绑定到的PVC发event,通知应用。

五、管理部署

以上提到了几类组件,local-volume-provisioner,local-volume-dynamic-provisioner,cinder-provisioner,node-problem-detector等,我们开发了gitops + salt的方案对其进行统一管理

首先把每个组件作为一个salt state,定义对应的salt state文件和pillar,写入git repo,对于key等敏感信息则存放在secret中。这些manifest文件通过AZ控制面同步到各个集群并执行。我们将所有的组件视为addon,salt会生成最终的yaml定义文件,交由kube addon manager进行apply。在需要更新的时候,只需更新相应的salt文件和pillar值即可。

六、后续工作

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技术荟(ID:eBayTechRecruiting)

原文链接

https://mp.weixin.qq.com/s/VeyR4dSkH_bOH7YmbwSE2w

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/2NV2wqX8CKrAt0kpcgbz
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券