前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >将云原生进行到底:腾讯百万级别容器云平台实践揭秘

将云原生进行到底:腾讯百万级别容器云平台实践揭秘

作者头像
腾讯云原生
发布2023-01-10 16:40:22
8390
发布2023-01-10 16:40:22
举报

林沐,腾讯云高级工程师,负责腾讯自研业务上云平台的建设和有状态服务容器化标准的制定,专注于大规模服务场景云原生实践的推广。

导读|基于 K8s 的云原生容器化已经在腾讯内部海量业务中大范围落地实践。业务从传统的虚拟机部署形态无缝切换到容器部署形态,运行在 K8s 上的应用从无状态服务扩展到有状态服务,这个过程经历了哪些改造?同时,K8s 如何经受住业务形态复杂多样、模块数量庞大的考验?遇到哪些新的挑战?如何优化?效果怎么样?腾讯云高级工程师林沐将为你解答。

在线业务资源容器化部署的问题与优化方案

腾讯平台的业务基本都属于在线业务。这些业务以前在虚拟机部署时,是通过物理机操办的方式生产出很多虚拟机,对于业务来说是不感知的。当业务发现虚拟机负载较低时,可将多个在线业务混部来提高资源利用率。

这种资源管理方式到容器化部署时发生了一些变化,主要有四方面的内容。

容器交付。每个 Pod 在交付的同时需要声明规格大小,规格大小要改变时 Pod 必须销毁重建,无法通过混部来新增业务。节点均衡。K8s 每个节点上部署多个 Pod,每个节点上的 Pod 类型、数量也都不相同,要保证节点均衡是一个挑战。K8s 的云原生特性,也就是弹性,是否能够符合在线业务在生产环境中的需求?集群池化。K8s 是按集群维度管理,而平台有上万个业务,这么多业务如何映射到不同的集群实现条带化管理?

针对上述问题,腾讯采取的优化手段是:

第一,资源利用率提升——动态压缩和超卖。我们面临一个痛点是用户配置的容器规模不合理,普遍偏大,这样节点装箱率和负载比较低。所以第一个优化方式就是 Pod 资源动态压缩,Pod 请求双核处理器 4G 内存,在调度时压缩成单核 4G 内存。因为 CPU 属于可压缩资源,内存属于不可压缩资源。这里修改的只是 Request 大小,并不修改 Limit,所以不影响容器实际能使用的上限值。这样能提高节点的装箱率。接下来是 Node 资源动态超卖,根据负载情况超卖更多 CPU 核心。

第二,节点负载均衡——动态调度和重调度。资源压缩超卖能提高节点的装箱率和负载使用率,但 Pod 是共享Node 的,压缩和超卖会加剧它们之间的干扰。于是我们开发了动态调度器,当每一个 Pod 调度时,能够感知存量 Node 当前的实时负载情况,从而对增量 Pod 在 Node 当中均衡处理,掉到一个低负载的节点上。存量 Pod 在节点上也有可能发生高负载,这时我们在节点上部署 Pod-Problem-Detecor、NodeProblem-Detecor,检测出哪个 Pod 会导致节点高负载,哪些 Pod 属于敏感 Pod,通过事件上报告诉API Server,让调度器将异常 Pod、敏感 Pod 重新调度到空闲节点。

第三,K8s 业务弹性伸缩——协同弹性。在线业务最关心业务稳定性。有时业务负载超出预期时,因为最大负载数配置过低,导致业务雪崩。所以我们对 HPA 进行优化,加入 HPAPlus-Controller,除了支持弹性最大副本策略之外,还能够支持业务自定义配置进行伸缩。第二个是 VPAPlus-Controller,可以 Pod 突发高负载进行快速扩容,对有状态的服务也可以进行无感知扩缩容。

第四,集群资源管理——动态配额和资源腾挪。从平台的角度,K8s 集群也是一个重要的维护对象。平台通过动态 Operator 的方式控制业务对集群的可见性以及配额大小,使得各个集群的业务是分布均匀的。集群本身也有规模大小,有节点伸缩,叫做 HNA。HNA 能够根据集群负载情况自动补充资源或释放资源。生产环境中一种情况是,有时候突发活动,在公共资源池里没有特定资源,需要从其他系统里腾挪资源。所以我们开发了弹性资源计划 Operator,它会给每个节点、每个集群下发任务,要求每个集群释放一些Node 出来。这批节点的数量要尽可能符合业务的数量要求,同时要对存量业务的负载质量不产生影响。我们的方式是通过动态规划的方式解决问题,从而在业务做活动,或者紧急情况下,能够使集群之间的资源也能够流转。

容器化对动态路由同步的挑战与解决方案

每一个 Pod 在销毁重建的时候会动态添加或提取路由。一般来说,生产环节中的路由是第三方系统负责,当 Pod 正常的时候系统给它转发流量,或者做名词解析,当它摘除时就从名词服务里剔除。

但我们的平台在生产环节中会遇到一些特殊情况。第一个情况就是容器化之后容器的变更更加频繁。第二个变化在于业务规模非常庞大,单个负载的 Pod 可能成千上万。第三是业务层面的变化,云原生的方式是一个集群对一个路由入口,但在生产环节又是第三方路由系统,允许云上云下混合部署,跨集群多路由服务共享路由。动态路由是容器化的关键路径,是要解决的核心问题。

在微观层面,业务对容器运行阶段有特殊需求,包括容器分级、路由和进程的运行状态一致、大批量探针失败时要实现路由熔断。

生产环节中路由系统是非常多的,每个路由系统会对应一种控制组件。所以我们需要路由同步 Controller的统一框架。这个框架理论上是一个旁路 Controller,因此存在不可靠的问题。例如在 Pod 下线前,销毁的时候不保证已经剔除路由;又比如在滚动更新时,可能上一批还没有添加路由,下一批就开始销毁重建。由于有些业务又比较敏感,必须要求绝对保证线下和滚动的时候路由的正确性,于是我们利用了 K8s 云原生的删除保护、滚动更新机制来实现这一需求。当业务在销毁之前先剔除路由,业务在滚动更新的时候先保

证上一批添加。通过这种方式将路由融入到 Pod 生命周期里,来实现业务的可靠性。

对于运行阶段,例如容器异常自动重启,或者 Pod 其中一个容器通过原地生成的方式启动,这些场景就会绕过前面提到的滚动更新和删除保护。所以还要在运行阶段保证业务之间的快速同步。业务大批量变更又会产生大量事件,导致 Controller 积压问题。

对此我们第一个优化方式是使用 Service 粒度事件合并,将事件数量成数量级减少来提高速度。第二个是双队列模型。K8s 的 Controller 里有定时历史对账机制,会将所有的 Pod 对象全部入队列。我们需要将实时和定时的事件分开,这样既能够解决定时对账,又能解决实时处理需求。

这里面有一个细节问题,两个不同队列可能在同一个时刻会有同一个事件要处理,这就需要相互感知的能力避免这种情况发生。

下一个 Controller 框架的核心点在于支持共享路由。但云原生的 K8s 机制里是一个集群对应一个路由入口,所以我们在 Controller 框架里增加一个路由同步记录,也是按照 Service 的粒度去记录的。如果业务系统产生脏数据,例如触发一个剔除操作,但是路由系统返回成功了,实际上没有剔除,那么下一次它去同步处理这个事件的时候发现它没有被剔除,那么还是会再重新剔除一遍。也就是说路由操作等于期望值去 Diff当前值,而它的期望值就等于 Endpoint 和 Pod 生命周期的交集,当前值就是路由系统里面的情况加上路由记录,二者再取差积就是要做的路由操作。

旁路 Controller 作为一个组件会有异常的时候,虽然 K8s 提供 Leader 机制,但这个机制被 Controller拉起时需要预加载存量 service 数据,如果数据量非常大需要很久时间。我们的解决方式是,每一个Controller 运行的时候属于主备模式,这样当主容器挂掉的时候,备容器获得锁,之间的间隔就是整个Controller 同步中断的最长时间,之后备容器就可以快速接管路由通路服务。中断期间可能发生事件丢失问题,我们通过定时历史对账机制解决这个问题。

我们还有特殊需求,是业务为了兼容虚拟机部署的一种管理方式,主要针对容器的运行阶段以及特殊处理。这里的需求包括容器分级、流量平衡、路由熔断。这些需求对传统的 Endpoint Controller 而言是不感知的,原来只维护 Ready 和 Not Ready 的状态,没有感知更细分的状态去维护容器的角色和状态。如果这是由路由 Controller 来实现,那么对这些特殊场景来说是牵一发而动全身的,每一个组件都得同时开发,修改一遍,维护成本是很高的。所以我们提供了一种解决方案——Endpoint-Plus Controller。它将维护容器角色和状态的能力下沉到 Endpoint 来实现。它与 Endpoint 和路由同步 Controller 之间建立一种交互协议,就是 Endpoint Ready 时添加路由,Not Ready 时禁用路由,不在 Endpoint 里删除路由。这样所有组件都是统一的,而且每次业务的新需求只要修改 Endpoint 就对全部生效,这样实现了动态路由同步的桥梁作用。

一种全新的容器销毁失败自愈机制探索

最后一个话题是关于容器销毁失败自愈的。前面提到了动态调度、弹性伸缩、容灾迁移、流水线发布,这些操作都有一个前提,就是容器销毁重建时老的容器要销毁,新的容器能创建出来。但实际上在生产环境中这并不是 100% 能保证的。因为容器是共享的,多个容器在同一个节点上,卡住的时候会涉及到很多原因:

调用链很长,只要其中任何软件出现 BUG 都会卡住;管理容器对业务容器有侵入,造成卡顿;业务容器之间互相干扰;共享内核、Cgroup、Namespace,并不保证所有资源绝对完全隔离;共享节点资源,当 CPU、磁盘 IO 高负载时会影响整个节点上的所有 Pod。

K8s 发展到现在已经有了一套很完善的自愈机制。对容器异常来说,云原生 K8s 提供一个暴力解决方案就是强删机制。该机制只是删除这个数据对象的数据,并不是销毁这个容器。这样导致一个问题,如果进行强制销毁,可能老容器会残留,新容器又起来了,这时老的容器会影响节点。

所以容器销毁阶段卡住会影响容器销毁重建这个基本需求,而且它的原因是复杂多样的,在大规模系统环境中更容易出现,而已有的自愈机制是没有涵盖这种场景的,所以我们就需要提供一种全新的自愈机制。传统的解决方案是通过脚本扫描到它,对于定位到的问题,没有解决方案的需要临时隔离,已有解决方案的就要明确修复。但这并不是一个闭环方案,因为还有很多未知问题,对未知问题来说业务关心的是尽可能恢复,而对平台来说为了保证稳定性,需要尽可能知道这些根因,去收敛这类问题。

所以我们兼顾这两个需求,要缩小定位范围、缩短定位周期,提高定位效率。对定位到根因的我们要去评估它的影响面,防止增量发生。而已经有解决方案的,我们需要有全网修复能力,出现异常的时候要告警,从而实现闭环解决方案。

我们想到了是智能运维的方法,它依靠大规模训练样本,关注相关性。而故障处理一般是小样本量,强调专业和因果性,所以它并不很适合这种场景。但在智能运维的决策树模型里有些概念可以拿来参考,譬如基尼系数、信息熵、剪枝等。

最后简单介绍一下决策树模型的实现。

第一步需要建立模型,是关于信息熵的权衡,要平衡自愈机制和定位效率。我们在选择信息熵的时候是自顶向下的推导路径,从节点异常再到容器调用链异常,再到具体系统日志。

第二步是特征选取,基尼系数越小特征越明确,所以我们选择共同特征作为一个特征值。同时选择一些已知问题或者根因比较明确的作为叶子节点。

最后一步是模型优化,例如剪枝优化,通过后剪枝的方式解决过拟合现象。同时简化模型。通过这种方式,

当容器发生销毁失败时,能够触发自愈路径。同时,对于新增问题,我们可以缩短问题范围,提高定位效率。

通过以上三步,最终我们探索出了这样一种全新的容器销毁失败自愈机制。期望本文思路对你有帮助~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-01-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯云原生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档