作者 | 韩堂、柘远、沉醉
来源 | 阿里巴巴云原生公众号
台湾作家林清玄在接受记者采访的时候,如此评价自己 30 多年写作生涯:“第一个十年我才华横溢,‘贼光闪现’,令周边黯然失色;第二个十年,我终于‘宝光现形’,不再去抢风头,反而与身边的美丽相得益彰;进入第三个十年,繁华落尽见真醇,我进入了‘醇光初现’的阶段,真正体味到了境界之美”。
长夜有穷,真水无香。领略过了 K8s“身在江湖”的那种惊心动魄以及它那生态系统的繁花似锦,该是回过头来体味高可用体系境界之美的时候了。毕竟仅能经得起敲打还是不能独步武林的!
在 K8s 高可用领域有一个问题被大家所熟知,那就是 K8s 单集群规模带来的 SLO 问题,如何持续保障?今天就以单集群的规模增长带来的高可用挑战来作为引子来给大家一个体感。
ASI 单集群规模支撑超过社区的 5000 台,这是个非常有意思且具备极大挑战的事情,对需要进行 K8s 生产化的同学,甚至具备 K8s 生产化经验的同学来说,一定会是个感兴趣的话题。回看 ASI 单集群规模从 100 到 10000 的发展之路,伴随着业务的增长和创新带来的每一次集群规模增长,都在逐步使我们的压力和挑战发生质变。
ASI:Alibaba Serverless infrastructure,阿里巴巴针对云原生应用设计的统一基础设施,ASI 是阿里公共云服务 ACK 的阿里集团企业版。
大家知道 K8s 社区只能够支撑五千个节点,当超过这个规模时,会出现各种性能瓶颈问题,比如:
以电商场景为例,100 节点增长到 4 千节点的时候,我们提前针对 ASI apiserver 的客户端和服务端做了大量的性能优化,从 apiserver 客户端的角度优先访问本地 cache,在客户端去做负载均衡;apiserver 服务端主要做了 watch 优化和 cache 索引优化;在 etcd 内核上利用并发读提升单 etcd 集群读处理能力,基于 hashmap 的 freelist 管理新算法提高 etcd 存储上限,基于 raft learner 技术来提高多备能力等等。
从 4 千节点增长到 8 千节点,我们又做了 qps 限流管理和容量管理优化、etcd 单资源对象存储拆分、组件规范全生命周期落地通过客户端的规范约束降低对 apiserver 的压力和以及穿透到 etcd 的压力等等。
终于迎来 8 千节点增长到上万节点的时刻,我们开始如火如荼地开展 etcdcompact 算法优化;etcd 单节点多 multiboltdb 的架构优化,apiserver 的服务端数据压缩,通过组件治理降低 etcd 写放大等;同时开始打造常态化的压测服务能力,持续回答 ASI 的 SLO。
这些例子在高可用挑战中司空见惯,列出的能力也只是其中一小部分,你也许很难看到能力之间的关联和底层的演进逻辑。当然,更多的能力建设沉淀到了我们的系统和机制当中。本篇文章会作为一个开始,以综述的形式分享我们在建设 ASI 全局高可用体系中的几个关键部分,再往后会有针对性地对进行技术点和演进路线的详解。如果大家有什么问题或者希望了解的部分,欢迎在评论区留言。
高可用是个比较复杂的命题,任何日常的变化例如服务升级、硬件更新、数据迁移、流量突增等都可能造成服务 SLO 受损,甚至不可用。
ASI 作为容器平台,并非孤立存在,而是与云底层及公共服务形成完备的生态系统。要解决 ASI 的高可用问题,需要纵观全局,找出每层的最优解,最终串联组成最优的整体解决方案。涉及到的层面包括:
特别是在 ASI 这个场景下,要支撑的业务集群数量庞大,涉及到的研发运维人员众多,功能发布频繁的迭代开发模式以及业务种类繁多带来的运行时的复杂多变,相比其他容器平台来看,ASI 高可用面临更多的挑战,其难度不言而喻。
如下图所示,现阶段高可用能力建设整体策略以 1-5-10(故障 1 分种发现、5 分钟定位、10 分钟止损)为目标牵引,注重将能力沉淀到系统或机制中,让 SRE/Dev 能够无差别的 oncall。
尽量避免发生问题、尽快发现、定位及恢复问题,是实现目标的关键,为此我们将 ASI 全局高可用体系的实现分三大部分展开:一是基础能力建设;二是应急体系建设;三是通过常态化压测以及故障演练等完成上述能力的保鲜和持续演进。
通过 3 个部分的轮转驱动,实现一个 ASI 全局高可用体系的构建,其顶层是 SLO 体系和 1-5-10 应急体系。在应急体系和数据驱动的体系背后,我们建设了大量高可用基础能力,包括防御体系、高可用架构升级、故障自愈体系、以及持续改进机制。与此同时,我们建立了多个基础性平台为高全用体系提供配套能力,如常态化故障演练平台、全链路仿真压测平台、告警平台、预案中心等等。
在建设全局高可用能力之前,我们的系统在迅速发展和变化下不断出现事故和险情,需要隔三差五去应急,导致让问题追身的局面,并且追身后没高效应对的手段,面临着几个严峻的挑战:
针对这些问题,并且总结出以下几个核心原因:
针对这些解决的问题,我们做了高可用基础能力的顶层设计,这些基础能力建设整体主要分为几个部分:
下面针对我们的一些痛点进行几个关键能力建设的描述。
这里面从两个大的角度可以去提高集群架构的可用性,除了在单集群进行架构优化以及性能突破外,还要通过多集群这样的横向扩展能力去支撑更大的规模。
核心方案就是通过对 apiserver 进行分组,通过不同的优先级策略进行对待,从而对服务进行差异化 SLO 保障。
目前张北中心的一个机房就几万节点,如果不解决多集群的管理问题,带来的问题如下:
容灾层面:把核心交易应用的中心单元部署在一个集群的风险是很大的,最坏情况下集群不可用导致整个应用服务不可用。
性能层面:对于业务来说,如因核心应用在某一时点使用时极其敏感而设定各种单机最大限制、CPU 互斥独占保证,如果都部署在一个集群的话,会因为集群节点规模限制,导致应用发生堆叠,造成 cpu 热点,性能不满足要求;对于 ASI 管控 Master 来说,单集群无限制扩大,性能总会出现瓶颈,总有一天会无法支撑。
运维层面:当某个应用扩容发现没资源,SRE 还得考虑节点加到哪个集群,额外加大了 SRE 集群管理的工作。
因此 ASI 需要达成统一的多集群管理解决方案,帮助上层各个 Pass、SRE、应用研发等提供更好的多集群管理能力,通过它来屏蔽多集群的差异、方便的进行多方资源共享。
ASI 选择基于社区联邦 v2 版本来开发满足我们的需求。
在一个大规模的 K8s 集群中性能会遇到哪些问题呢?
首先是查询相关问题。在大集群中最重要的就是如何最大程度地减少 expensive request。对百万级别的对象数量来说,按标签、namespace 查询 Pod,获取所有 Node 等场景时,很容易造成 etcd 和 kube-apiserver OOM 和丢包,乃至雪崩等问题发生。
其次是写入相关问题。etcd 适用场景是读多写少,大量写请求可能会导致 db size 持续增长、写性能达到瓶颈被限速、影响读性能。如大量的离线作业需要频繁的创建和删除 pod,通过 ASI 链路对 pod 对象的写放大,最终对 etcd 的写压力会放大几十倍之大。
最后是大资源对象相关问题。etcd 适合存储较小的 key-value 数据,在大 value 下,性能急速下降。
ASI 的性能优化可以从 apiserver 客户端、apiserver 服务端、etcd 存储 3 个方面来进行优化。
在 K8s 中,kube-apiserver 作为统一入口,所有的控制器/client 都围绕 kube-apiserver 在工作,尽管我们 SRE 通过组件的全生命周期进行规范约束卡点改进,比如通过在组件的启用和集群准入阶段进行了卡点审批,通过各个组件 owner 的全面配合改造后,大量的低级错误得到防范,但还是有部分控制器或部分行为并不可控。
除了基础设施层面的故障外,业务流量的变化,是造成 K8s 非常不稳定的因素,突发的 pod 创建和删除,如果不加以限制,很容易把 apiserver 打挂。
另外非法的操作或代码 Bug 有可能造成业务 pod 影响,如不合法的 pod 删除。
结合所有风险进行分层设计,逐层进行风险防控。
社区早期采用的限流方式主要通过 inflight 控制读写总体并发量,我们当时在 apf 没有出来之前就意识到限流能力的不足,没有能力去对请求来源做限流。而 apf 通过 User 来做限流(或者说要先经过 authn filter)存在一些不足,一方面因为Authn 并不是廉价的,另外一方面它只是将 API Server 的能力按配置来做分配,并不是一种限流方案和应急预案。我们需要紧急提供一种限流能力,以应对紧急情况,自研了 ua limiter 限流能力,并基于 ua limiter 简单的配置方式实现了一套限流管理能力,能够很方便在几百个集群当中进行默认限流管理,以及完成应急限流预案。
下面是我们自研的 ua limiter 限流方案和其他限流方案的详细对比:
ua limiter、APF、sentinel 在限流上的侧重点是不一样的:
考虑我们现阶段的需求和场景,发现 ua limiter 落地最为合适,因为我们通过 user agent 的不同,来对于组件进行限流。当然后续进行更加精细的限流,还是可以考虑结合使用 APF 等方案进一步加强。
限流策略如何管理,数百套集群,每套集群规模都不太一样,集群节点数、pod 数都是不同的,内部组件有近百个,每个组件请求的资源平均有 4 种,对不同资源又有平均 3 个不同的动作,如果每个都做限流,那么规则将会爆炸式增长,即便做收敛后维护成本也非常的高。因此我们抓最核心的:核心资源 pod\node、核心动作(创建、删除、大查询);最大规模的:daemonset 组件、PV/PVC 资源。并结合线上实际流量分析,梳理出二十条左右的通用限流策略,并将其纳入到集群交付流程中实现闭环。
当新的组件接入,我们也会对其做限流设计,如果比较特殊的,则绑定规则并在集群准入和部署环节自动下发策略,如果出现大量的限流情况,也会触发报警,由 SRE 和研发去跟进优化和解决。
所有 pod 相关的操作都会对接 Kube Defender 统一风控中心,进行秒级别、分钟级、小时级、天级别的流控。该全局风控限流组件,实行中心端部署,维护各场景下的接口调用限流功能。
defender 是站在整个 K8s 集群的视角,针对用户发起的或者系统自动发起的有风险的操作进行防护(流控、熔断、校验)和审计的风控系统。之所以做 defender,主要从以下几个方面考虑:
defender 的框架图如下所示:
在只有几个 core 集群的场景下,依靠专家经验管理容量完全可以轻松搞定,但随着容器业务的快速发展,覆盖泛交易、中间件、新生态、新计算以及售卖区等业务在接入 ASI,短短几年时间就发展了几百个集群,再发展几年数以千计万计?如此多的集群依靠传统的人肉资源管理方式难以胜任,人力成本越来越高,特别是面临诸如以下问题,极易造成资源使用率低下,机器资源的严重浪费,最终造成部分集群容量不足导致线上风险。
在 ASI 中,组件变化是常态,组件容量如何自适应这种变化也是一个非常大的挑战。而日常的运维及诊断须要有精准的容量数据来作为备容支撑。
因此我们决定通过数据化指导组件申请合理的(成本低,安全)容器资源。通过数据化提供日常运维所需要的容量相关数据,完成备容,在生产水位异常时,完成应急扩容。
目前我们完成了水位监控、全量风险播报、预调度、profile 性能数据定时抓取、进而通过组件规范中推进 CPU 内存以及 CPU 内存比例优化。正在做的包括自动化的规格建议,节点资源补充建议,以及自动化导入节点,结合 chatops 正在打造钉群“一键备容”闭环。另外还在结合全链路压测服务数据,得出各个组件的基线对比,通过风险决策,进行发布卡点,确保组件上线安全。同时未来会结合线上真实的变更,来持续回答真实环境的 SLO 表现,精准预测容量。
高可用基础能力的建设可以为 ASI 提供强有力的抗风险保障,从而在各种风险隐患出现时,尽可能保证我们服务的可用性。但是在风险出现后,如何快速介入消灭隐患,或者在高可用能力无法覆盖的故障出现后,进行有序止损,就变成了一个非常具有技术深度和横向复杂度的工程难题,也让 ASI 的应急能力建设成为我们非常重要的投入方向。
在建设应急体系之初,我们的系统由于迅速的发展和变化,不断出现的事故和险情,明显的暴露出当时我们面临的几个严重的问题:
针对这些问题,我们也进行了充分的脑暴和探讨,并且总结出以下几个核心原因:
针对这些亟待解决的问题,我们也做了应急能力的顶层设计,架构图如下:
应急能力建设整体可以分为几个部分:
针对顶层设计中的每个子模块,我们都已经做出了一些阶段性的工作和成果。
为了解决无法早于客户发现问题的难题,我们的工作最重要的目标就是要做到:让一切问题都无处遁形,被系统主动发现。
所以这就像是一场持久战,我们要做的,就是通过各种可能的手段去覆盖一个又一个新的问题,攻占一个又一个城池。
在这个目标的驱使下,我们也总结出一套非常行之有效的“战略思想”,即**「1+1 思想」**。它的核心观点在于,任何发现问题的手段,都可能因为对外部的依赖或者自身稳定性的缺陷,导致偶发的失效,所以必须有能够作为互备的链路来进行容错。
在这个核心思想的指导下,我们团队建设了两大核心能力,即黑盒/白盒报警双通道,这两个通道的各有各的特点:
黑盒通道对应的具体产品叫做 kubeprobe,是由我们团队脱胎于社区 kuberhealthy 项目的思想上进行更多的优化和改造形成的新产品,并且也成为我们判断集群是否出现严重风险的重要利器。
白盒通道的建设相对更为复杂,它需要建设在完备的可观测数据的基础之上,才能够真正发挥它的功力。所以为此我们首先从 metrics、日志、事件 3 个维度分别基于 SLS 建设 3 种数据通道,将所有可观测数据统一到 SLS 上管理。另外我们也建设了告警中心,负责完成对当前上百套集群的告警规则的批量管理,下发能力,最终构造了出了一个数据完备,问题覆盖广泛的白盒告警系统。最近还在进一步将我们的告警能力向 SLS 告警 2.0 迁移,实现更加丰富的告警功能。
随着线上问题排查经验的不断丰富,我们发现有很多问题会比较频繁地出现。它们的排查方法和恢复手段基本已经比较固化。即便某个问题背后的原因可能有多种,但是随着线上排查经验的丰富,基本都可以慢慢迭代出对这个问题的排查路线图。如下图所示,是针对 etcd 集群不健康的告警设计的排查路线:
如果把这些相对比较确认的排查经验固化到系统中,在问题出现后可以自动触发形成决策,势必可以大幅减少我们对线上问题的处理耗时。所以在这个方面,我们也开始了一些相关能力的建设。
从黑盒通道方面,kubeprobe 构建了一套自闭环的根因定位系统,将问题排查的专家经验下沉进系统中,实现了快速和自动的问题定位功能。通过普通的根因分析树以及对失败巡检探测事件/日志的机器学习分类算法(持续开发投入中),为每一个 KubeProbe 的探测失败 Case 做根因定位,并通过 KubeProbe 内统一实现的问题严重性评估系统(目前这里的规则仍比较简单),为告警的严重性做评估,从而判断应该如何做后续的处理适宜,比如是否自愈,是否电话告警等等。
从白盒通道方面,我们通过底层的 pipeline 引擎的编排能力,结合已经建设的数据平台中的多维度数据,实现了一个通用的根因诊断中心,将通过各种可观测数据从而排查问题根因的过程通过 yaml 编排的方式固化到系统中,形成一个根因诊断任务,并且在触发任务后形成一个问题的诊断结论。并且每种结论也会绑定对应的恢复手段,比如调用预案、自愈等等。
两种通道都通过钉钉机器人等手段实现类似 chatops 的效果,提升 oncall 人员的处理问题速度。
为了能够提升运行时故障的止损恢复速度,我们也把恢复止损能力的建设放在第一优先级,这个方面我们的核心准则是两个:
所以在这两个准则的驱使下,我们做了两个方面的工作:
针对缺乏跟进能力的问题,我们提出了 BugFix SLO 机制。正如名字所描述的那样,我们认为每个发现的问题都是一个要被修复的 “Bug”,并且针对这种 Bug 我们做了一下工作:
每两周,通过过去一段时间收集的新的问题,我们会产出一份稳定性周报,进行问题解决程度的通晒以及重点问题的同步。另外也会在每两周进行一次全员拉会对齐,对每个新问题的负责人确定,优先级确认进行对齐。
由于稳定性风险是相对低频发生的,所以对稳定性能力的最好的保鲜手段就是演练,所以在这个基础之上我们设计或者参与了两种演练方案,分别是:
常态化故障演练机制的核心目的在于以更频繁的频率对 ASI 系统相关的故障场景以及针对这个故障的恢复能力进行持续验收,从而既发现某些组件的在稳定性方面的缺陷,也可以验收各种恢复手段预案的有效性。
所以为了能够尽可能提升演练频率,我们:
鉴于常态化演练的演练频率如此之高,我们通常在一个专用的集群中进行持续的后台演练场景触发,以降低因为演练带来的稳定性风险。
常态化故障演练即便做的再频繁,我们也不能完全保证在生产集群真的出现同样的问题,我们是否能够以同样的方式进行应对;也没有办法真正确认,这个故障的影响范围是否与我们预期的范围一致;这些问题最根本的原因还是在于我们在常态化故障演练中的集群一般是没有生产流量的测试集群。
所以在生产环境进行故障模拟才能够更加真实的反应线上的实况,从而提升我们对恢复手段的正确性的信心。在落地方面,我们通过积极参与到云原生团队组织的季度生产突袭活动,将我们一些相对复杂或者比较重要的演练场景实现了在生产环境的二次验收,与此同时也对我们的发现速度,响应速度也进行了侧面评估,不仅发现了一些新的问题,也为我们如何在测试集群设计更符合线上真实情况的场景带来了很多参考输入。
本篇仅作为开篇从整体上介绍了 ASI 全局高可用体系建设上一些探索工作以及背后的思考,后续团队会针对具体的领域比如 ASI 应急体系建设,ASI 预防体系建设,故障诊断与恢复、全链路精细化 SLO 建设和运营、ASI 单集群规模的性能瓶颈突破上等多个方面进行深入的解读,敬请期待。
ASI 作为云原生的引领实施者,它的高可用,它的稳定性影响着甚至决定着阿里集团和云产品的业务的发展。ASI SRE 团队长期招人,技术挑战和机会都在,感兴趣的同学欢迎来撩:[email protected],[email protected]。
数字时代,如何更好地利用云的能力?什么是新型、便捷的开发模式?如何让开发者更高效地构建应用?科技赋能社会,技术推动变革,拓展开发者的能量边界,一切,因云而不同。点击立即报名活动,2021 阿里云开发者大会将会带给你答案。