万级规模 k8s 集群 etcd 高可用建设之路

IT168网站
k8s的早期拥护者在推广k8s时,宣称其比OpenStack的优势之一是k8s没有使用消息队列,其延迟比OpenStack低。这其实是一个误解,无论是etcd提供的watch接口,还是k8s client包中的informer机制,无不表明k8s是把etcd当做了消息队列,k8s消息的载体很多,譬如k8s event。

蚂蚁集团运维着可能是全球最大的k8s集群:k8s官方以5k node作为k8s规模化的顶峰,而蚂蚁集团事实上运维着规模达到10k node规模的k8s集群。一个形象的比喻就是,如果官方以及跟着官方的k8s使用者能想象到的k8s的集群规模是泰山,那么蚂蚁集团在官方的解决方案之上已经实现了一个珠穆朗玛峰,引领了k8s规模化技术的提升。

这个量级的差异,不仅仅是量的差异,更是k8s管理维护的质的提升。能维护有如此巨大挑战巨量规模的k8s集群,其背后原因是蚂蚁集团付出了远大于k8s官方的优化努力。

所谓万丈高楼平地起,本文着重讨论下蚂蚁集团的在k8s的基石---etcd层面做出的高可用建设工作:只有etcd这个基石稳当了,k8s这栋高楼大厦才保持稳定性,有tidb大佬黄东旭朋友圈佐证【图片已获黄总授权】。

11111.png

面临的挑战

etcd首先是k8s集群的KV数据库。

从数据库的角度来看,k8s整体集群架构各个角色如下:

etcd集群的数据库

kube-apiserver etcd的API接口代理、数据缓存层

kubelet数据的生产者和消费者

kube-controller-manager数据的消费者和生产者

kube-scheduler数据的消费者和生产者

etcd本质是一个KV数据库,存储了k8s自身资源、用户自定义的CRD以及k8s系统的event等数据。每种数据的一致性和数据安全性要求不一致,如event数据的安全性小于k8s自身的资源数据以及CRD数据。

k8s的早期拥护者在推广k8s时,宣称其比OpenStack的优势之一是k8s没有使用消息队列,其延迟比OpenStack低。这其实是一个误解,无论是etcd提供的watch接口,还是k8s client包中的informer机制,无不表明k8s是把etcd当做了消息队列,k8s消息的载体很多,譬如k8s event。

从消息队列的角度来看,k8s整体集群架构各个角色如下:

etcd消息路由器

kube-apiserver etcd生产者消息代理和消息广播【或者成为次级消息路由器、消费者代理】

kubelet消息的生产者和消费者

kube-controller-manager消息的消费者和生产者

kube-scheduler消息的消费者和生产者

etcd是推模式的消息队列。etcd是k8s集群的KV数据库和消息路由器,充当了OpenStack集群中的MySQL和MQ两个角色,这样的实现貌似简化了集群的结构,但其实不然。在large scale规模k8s集群中,一般经验,首先都会使用一个单独etcd集群存储event数据:把KV数据和一部分MQ数据物理隔离开,实现了KV和MQ角色的部分分离。如参考文档2中提到美团“针对etcd的运营,通过拆分出独立的event集群降低主库的压力”。

当k8s集群规模扩大时,etcd承载着KV数据剧增、event消息暴增以及消息写放大的三种压力。为了证明所言非虚,特引用部分数据为佐证:

etcd KV数据量级在100万以上;

etcd event数据量在10万以上;

etcd读流量压力峰值在30万pqm以上,其中读event在10k qpm以上;

etcd写流量压力峰值在20万pqm以上,其中写event在15k qpm以上;

etcd CPU经常性飙升到900%以上;

etcd内存RSS在60 GiB以上;

etcd磁盘使用量可达100 GiB以上;

etcd自身的goroutine数量9k以上;

etcd使用的用户态线程达1.6k以上;

etcd gc单次耗时常态下可达15ms。

使用Go语言实现的etcd管理这些数据非常吃力,无论是CPU、内存、gc、goroutine数量还是线程使用量,基本上都接近go runtime管理能力极限:经常在CPU profile中观测到go runtime和gc占用资源超过50%以上。

蚂蚁的k8s集群在经历高可用项目维护之前,当集群规模突破7千节点规模时,曾出现如下性能瓶颈问题:

etcd出现大量的读写延迟,延迟甚至可达分钟级;

kube-apiserver查询pods/nodes/configmap/crd延时很高,导致etcd oom;

etcd list-all pods时长可达30分钟以上;

2020年etcd集群曾因list-all压力被打垮导致的事故就达好几起;

控制器无法及时感知数据变化,如出现watch数据延迟可达30s以上。

如果说这种状况下的etcd集群是在刀锋上跳舞,此时的整个k8s集群就是一个活火山:稍不留神就有可能背个P级故障,彼时的整个k8s master运维工作大概是整个蚂蚁集团最危险的工种之一。

高可用策略

实现一个分布式系统高可用能力的提升,大概有如下手段:

提升自身稳定性与性能;

精细管理上游流量;

保证服务下游服务SLO。

etcd经过社区与各方使用者这么多年的锤炼,其自身的稳定性足够。蚂蚁人能做到的,无非是使出周扒皮的本事,提高集群资源整体利用率,scale out和scale up两种技术手段双管齐下,尽可能的提升其性能。

etcd自身作为k8s的基石,其并无下游服务。如果说有,那也是其自身所使用的物理node环境了。下面分别从etcd集群性能提升、请求流量管理等角度阐述我们在etcd层面所做出的高可用能力提升工作。

文件系统升级

在山窝里飞出金凤凰,诚非易事。让etcd跑的更快这件事,没有什么手段比提供一个高性能的机器更短平快地见效了。

1.使用NVMe ssd

etcd自身=etcd程序+其运行环境。早期etcd服务器使用的磁盘是SATA盘,经过简单地测试发现etcd读磁盘速率非常慢,老板豪横地把机器鸟枪换炮---升级到使用了NVMe SSD的f53规格的机器:etcd使用NVMe ssd存储boltdb数据后,随机写速率可提升到70 MiB/s以上。

参考文档2中提到美团“基于高配的SSD物理机器部署可以达到日常5倍的高流量访问”,可见提升硬件性能是大厂的首选,能折腾机器千万别浪费人力。

2.使用tmpfs

NVMe ssd虽好,理论上其读写极限性能跟内存比还是差一个数量级。我们测试发现使用tmpfs【未禁止swap out】替换NVMe ssd后,etcd在读写并发的情况下性能仍然能提升20%之多。考察k8s各种数据类型的特点后,考虑到event对数据的安全性要求不高但是对实时性要求较高的特点,我们毫不犹豫的把event etcd集群运行在了tmpfs文件系统之上,将k8s整体的性能提升了一个层次。

3.磁盘文件系统

磁盘存储介质升级后,存储层面能够进一步做的事情就是研究磁盘的文件系统格式。目前etcd使用的底层文件系统是ext4格式,其block size使用的是默认的4 KiB。我们团队曾对etcd进行单纯的在单纯写并行压测时发现,把文件系统升级为xfs,且block size为16 KiB【在测试的KV size总和10 KiB条件下】时,etcd的写性能仍然有提升空间。

但在读写并发的情况下,磁盘本身的写队列几乎毫无压力,又由于etcd 3.4版本实现了并行缓存读,磁盘的读压力几乎为零,这就意味着:继续优化文件系统对etcd的性能提升空间几乎毫无帮助。自此以后单节点etcd scale up的关键就从磁盘转移到了内存:优化其内存索引读写速度。

4.磁盘透明大页

在现代操作系统的内存管理中,有huge page和transparent huge page两种技术,不过一般用户采用transparent huge page实现内存page的动态管理。在etcd运行环境,关闭transparent huge page功能,否则RT以及QPS等经常性的监控指标会经常性的出现很多毛刺,导致性能不平稳。

etcd调参

MySQL运维工程师常被人称为“调参工程师”,另一个有名的KV数据库RocksDB也不遑多让,二者可调整的参数之多到了令人发指的地方:其关键就在于针对不同存储和运行环境需要使用不同的参数,才能充分利用硬件的性能。etcd随不及之,但也不拉人后,预计以后其可调整参数也会越来越多。

etcd自身也对外暴露了很多参数调整接口。除了阿里集团k8s团队曾经做出的把freelist由list改进为map组织形式优化外,目前常规的etcd可调整参数如下:

write batch

compaction

1.write batch

像其他常规的DB一样,etcd磁盘提交数据时也采用了定时批量提交、异步写盘的方式提升吞吐,并通过内存缓存的方式平衡其延时。具体的调整参数接口如下:

batch write number批量写KV数目,默认值是10k;

batch write interval批量写事件间隔,默认值是100 ms。

etcd batch这两个默认值在大规模k8s集群下是不合适的,需要针对具体的运行环境调整之,避免导致内存使用OOM。一般地规律是,集群node数目越多,这两个值就应该成比例减小。

2.compaction

etcd自身由于支持事务和消息通知,所以采用了MVCC机制保存了一个key的多版本数据,etcd使用定时的compaction机制回收这些过时数据。etcd对外提供的压缩任务参数如下:

compaction interval压缩任务周期时长;

compaction sleep interval单次压缩批次间隔时长,默认10 ms;

compaction batch limit单次压缩批次KV数目,默认1000。

(1)压缩任务周期

k8s集群的etcd compaction可以有两种途径进行compaction:

etcd另外提供了comapct命令和API接口,k8s kube-apiserver基于这个API接口也对外提供了compact周期参数;

etcd自身会周期性地执行compaction;

etcd对外提供了自身周期性compaction参数调整接口,这个参数的取值范围是(0,1 hour];

其意义是:etcd compaction即只能打开不能关闭,如果设置的周期时长大于1 hour,则etcd会截断为1 hour。

蚂蚁k8s团队在经过测试和线下环境验证后,目前的压缩周期取值经验是:

在etcd层面把compaction周期尽可能地拉长,如取值1 hour,形同在etcd自身层面关闭compaction,把compaction interval的精细调整权交给k8s kube-apiserver;

在k8s kube-apiserver层面,根据线上集群规模取值不同的compaction interval。

之所以把etcd compaction interval精细调整权调整到kube-apiserver层面,是因为etcd是KV数据库,不方便经常性地启停进行测试,而kube-apiserver是etcd的缓存,其数据是弱状态数据,相对来说启停比较方便,方便调参。至于compaction interval的取值,一条经验是:集群node越多compaction interval取值可以适当调大。compaction本质是一次写动作,在大规模集群中频繁地执行compaction任务会影响集群读写任务的延时,集群规模越大,其延时影响越明显,在kube-apiserver请求耗时监控上表现就是有频繁出现地周期性的大毛刺。

更进一步,如果平台上运行的任务有很明显的波谷波峰特性,如每天的8:30 am~21:45 pm是业务高峰期,其他时段是业务波峰期,那么可以这样执行compaction任务:

在etcd层面设定compaction周期是1 hour;

在kube-apiserver层面设定comapction周期是30 minutes;

在etcd运维平台上启动一个周期性任务:当前时间段在业务波谷期,则启动一个10 minutes周期的compaction任务。

其本质就是把etcd compaction任务交给etcd运维平台,当发生电商大促销等全天无波谷的特殊长周期时间段时,就可以在平台上紧急关闭compaction任务,把compaction任务对正常的读写请求影响降低到最低。

(2)单次压缩

即使是单次压缩任务,etcd也是分批执行的。因为etcd使用的存储引擎boltdb的读写形式是多读一写:可以同时并行执行多个读任务,但是同时刻只能执行一个写任务。

为了防止单次compaction任务一直占用boltdb的读写锁,每次执行一批固定量【compaction batch limit】的磁盘KV压缩任务后,etcd会释放读写锁sleep一段时间【compaction sleep interval】。

在v3.5之前,compaction sleep interval固定为10 ms,在v3.5之后etcd已经把这个参数开放出来方便大规模k8s集群进行调参。类似于batch write的interval和number,单次compaction的sleep interval和batch limit也需要不同的集群规模设定不同的参数,以保证etcd平稳运行和kube-apiserver的读写RT指标平稳无毛刺。

运维平台

无论是etcd调参,还是升级其运行的文件系统,都是通过scale up的手段提升etcd的能力。还有两种scale up手段尚未使用:

通过压测或者在线获取etcd运行profile,分析etcd流程的瓶颈,然后优化代码流程提升性能;

通过其他手段降低单节点etcd数据量。

通过代码流程优化etcd性能,可以根据etcd使用方的人力情况进行之,更长期的工作应该是紧跟社区,及时获取其版本升级带来的技术红利。通过降低etcd数据规模来获取etcd性能的提升则必须依赖etcd使用方自身的能力建设了。

我们曾对etcd的单节点RT与QPS性能与KV数据量的关系进行过benchmark测试,得到的结论是:当KV数据量增加时,其RT会随之线性增加,其QPS吞吐则会指数级下降。这一步测试结果带来的启示之一即是:通过分析etcd中的数据组成、外部流量特征以及数据访问特点,尽可能地降低单etcd节点的数据规模。

目前蚂蚁的etcd运维平台具有如下数据分析功能:

longest N KV---长度最长的N个KV

top N KV---段时间内访问次数最多的N个KV

top N namespace---KV数目最多的N个namespace

verb+resoure---外部访问的动作和资源统计

连接数---每个etcd节点的长连接数目

client来源统计---每个etcd节点的外部请求来源统计

冗余数据分析---etcd集群中近期无外部访问的KV分布

根据数据分析结果,可以进行如下工作:

客户限流

负载均衡

集群拆分

冗余数据删除

业务流量精细分析

1.集群拆分

前文提到,etcd集群性能提升的一个经典手段就是把event数据独立拆分到一个独立的etcd集群,因为event数据是k8s集群一中量级比较大、流动性很强、访问量非常高的数据,拆分之后可以降低etcd的数据规模并减轻etcd单节点的外部客户端流量。

一些经验性的、常规性的etcd拆分手段有:

pod/cm

node/svc

event,lease

这些数据拆分后,大概率能显著提升k8s集群的RT与QPS,但是更进一步的数据拆分工作还是有必要的。依据数据分析平台提供的热数据【top N KV】量级以及外部客户访问【verb+resource】情况,进行精细分析后可以作为etcd集群拆分工作的依据。

2.客户数据分析

针对客户数据的分析分为longest N KV分析、top N namespace。

一个显然成立的事实是:单次读写访问的KV数据越长,则etcd响应时间越长。通过获取客户写入的longest N KV数据后,可以与平台使用方研究其对平台的使用方法是否合理,降低业务对k8s平台的访问流量压力和etcd自身的存储压力。

一般地,k8s平台每个namespace都是分配给一个业务单独使用。前面提到k8s可能因为list-all压力导致被压垮,这些数据访问大部分情况下都是namespace级别的list-all。从平台获取top N namespace后,重点监控这些数据量级比较大的业务的list-all长连接请求,在kube-apiserver层面对其采取限流措施,就可以基本上保证k8s集群不会被这些长连接请求打垮,保证集群的高可用。

3.冗余数据分析

etcd中不仅有热数据,还有冷数据。这些冷数据虽然不会带来外部流量访问压力,但是会导致etcd内存索引锁粒度的增大,进而导致每次etcd访问RT时延增加和整体QPS的下降。

近期通过分析某大规模【7k node以上】k8s集群etcd中的冗余数据,发现某业务数据在etcd中存储了大量数据,其数据量大却一周内没有访问过一次,与业务方询问后获悉:业务方把k8s集群的etcd当做其crd数据的冷备使用。与业务方沟通后把数据从etcd中迁移掉后,内存key数目立即下降20%左右,大部分etcd KV RT P99延时立即下降50%~60%之多。

4.负载均衡

k8s平台运维人员一般都有这样一条经验:etcd集群如果发生了启停,需要尽快对所有k8s kube-apiserver进行一次重启,以保证kube-apiserver与etcd之间连接数的均衡。其原因有二:

kube-apiserver在启动时可以通过随机方式保证其与etcd集群中某个节点建立连接,但etcd发生启停后,kube-apiserver与etcd之间的连接数并无规律,导致每个etcd节点承担的客户端压力不均衡;

kube-apiserver与etcd连接数均衡时,其所有读写请求有2/3概率是经过follower转发到leader,保证整体etcd集群负载的均衡,如果连接不均衡则集群性能无法评估。

通过etcd运维平台提供的每个etcd的连接负载压力,可以实时获取集群连接的均衡性,进而决定运维介入的时机,保证etcd集群整体的健康度。

其实最新的etcd v3.5版本已经提供了etcd客户端和etcd节点之间的自动负载均衡功能,但这个版本才发布没多久,目前最新版本的k8s尚未支持这个版本,可以及时跟进k8s社区对这个版本的支持进度以及时获取这一技术红利,减轻平台运维压力。

未来之路

通过一年多的包括kube-apiserver和etcd在内的k8s高可用建设,目前k8s集群已经稳定下来,一个显著的特征是半年内k8s集群没有发生过一次P级故障,但其高可用建设工作不可能停歇---作为全球k8s规模化建设领导力象限的蚂蚁集团正在挑战node量级更大规模的k8s集群,这一工作将推动etcd集群建设能力的进一步提升。

前面提到的很多etcd能力提升工作都是围绕其scale up能力提升展开的,这方面的能力还需要更深层次的加强:

etcd最新feature地及时跟进,及时把社区技术进步带来的开源价值转换为蚂蚁k8s平台上的客户价值

及时跟进阿里集团在etcd compact算法优化、etcd单节点多multiboltdb的架构优化以及kube-apiserver的服务端数据压缩等etcd优化工作【见参考文档1】,对兄弟团队的工作进行借鉴和反馈,协同作战共同提升

跟进蚂蚁自身k8s平台上etcd的性能瓶颈,提出我们自己的解决方案,在提升我们平台的技术价值的同时反哺开源

除了关注etcd单节点性能的提升,我们下一步的工作将围绕分布式etcd集群这一scale out方向展开。前面提到的etcd集群拆分工作,其本质就是通过分布式etcd集群的方式提升etcd集群整体的性能:该集群的数据划分方式是依据k8s业务层面的数据类型进行的。

该工作可以进一步拓展为:不区分KV的业务意义,从单纯的KV层面对把数据根据某种路由方式把数据写入后端多etcd子集群,实现etcd集群整体的冷热负载均衡。

分布式etcd集群的实现有两种方式:proxyless和proxy based:proxy based etcd分布式集群的请求链路是client[kube-apiserver]->proxy->etcd server,而谓的proxyless分布式etcd集群的请求链路是client[kube-apiserver]->etcd server。

proxy based etcd分布式集群的好处是可以直接基于etcd社区提供的etcd proxy进行开发,后期亦可回馈社区,实现其开源价值、技术价值和客户价值的统一。但经过测试:按照测试发现,kube-apiserver经过proxy向etcd发起读写请求后RT和QPS降低20%~25%。所以下一步的工作重点是开发proxyless etcd集群。

目前的拆分后的etcd分布式集群本质或者67%的概率是proxy based分布式集群:kube-apiserver的请求大概有三分之二的概率是经过follower转发到leader,此处的follower本质就是一个proxy。如果kube-apiserver所有请求都是与leader直连后被处理,理论上当前的k8s集群的RT和QPS就有67%*20%≈13.4%的性能收益。

proxyless etcd分布式集群的缺点是如果把proxy的路由逻辑放入kube-apiserver中,会造成kube-apiserver版本升级成本增加,但相比于至少20%【将来通过etcd集群规模扩充这个收益肯定会更大】的收益,这个仅仅影响了kube-apiserver单个组件的版本升级的成本是值得的。

除了multiple etcd clusters的思路外,数据中间件团队基于OBKV之上实现了etcd V3 API,算是另一种比较好的技术路线,颇类似于本文开头黄东旭提到的在tikv之上etcd V3 API接口层,可以称之为类etcd系统,目前相关工作也在推进中。

总之,随着我们k8s规模越来越大,蚂蚁集团etcd整体工作的重要性就日益凸显。如果说前期etcd的高可用建设之路是在泥泞小道上蹒跚前行,那么以后的etcd高可用建设之路必是康庄大道---道路越走越宽广!

THEEND

最新评论(评论仅代表用户观点)

更多
暂无评论