共计 8699 个字符,预计需要花费 22 分钟才能阅读完成。
腾讯会议,一款联合国都 Pick 的线上会议解决方案,提供完美会议品质和灵活协作空间,广泛应用在政府、医疗、教育、企业等各个行业。大家从文章 8 天扩容 100 万核,腾讯会议是如何做到的?都知道腾讯会议背后的计算资源已过百万核,如此体量的业务,如何通过云原生技术提升研发和运维效率,是一个非常有价值的课题。这里我将为大家揭秘腾讯自研上云容器平台 TKEx 在支持腾讯会议全量云原生化上云背后的技术。
TKEx 平台是以腾讯云容器服务(Tencent Kubernetes Engine, TKE)为底座,服务于腾讯自研业务的容器平台。腾讯自研业务类型众多、规模超大,云原生上云面临的挑战可想而知。TKEx 平台在腾讯自研业务上云的过程中沉淀的最佳实践和解决方案,我们将会在 TKE 中提供给客户。
腾讯会议业务特性
在 Kubernetes 中,我们习惯把应用分为无状态和有状态两类,有状态应用主要指实例标识、网络、存储的有状态。腾讯会议的一些服务有如下特性:
使用 IPC 共享内存,里面存放的有状态数据从 MB 到 GB 大小不等。
- 升级时 IPC 数据不能丢失;
- 升级时只能允许 ms 级的抖动,用户无感知;
- 部分服务最多的实例数过万,要求高效完成一次版本升级;
- 全球多地域部署,要求部署高效;
- 部分服务要求每个实例都分配 EIP;
这对 Kubernetes 管理这种有状态服务提出了更高能力和性能要求。TKEx 平台抽象出业务特性背后的产品需求,在灰度发布、多集群工作负载管理、计算资源管理运营、Node 稳定性等方面进行了增强和优化,沉淀出了通用的音视频业务容器编排能力。
StatefulSetPlus 强大的灰度发布能力
StatefulSetPlus 是我们 2018 年研发并投入生产的首批 Operator 之一,核心特性包括:
- 兼容 StatefulSet 所有特性,比如按序滚动更新。
支持分批灰度更新和回滚,单批次内的 Pods 可并发更新、可串行更新。
- 支持各个批次手动待升级勾选 Pods。
- 支持用户配置各个批次待升级 Pods 的比例进行灰度。
- 支持分批回滚和一键失败回滚。
- 发布过程可暂停。
- 支持单个 StatefulSetPlus 对象管理上万个 Pods。
- 支持 ConfigMap 的分批灰度发布。
- 对接了 TKE IPAMD,实现了 Pod 固定 IP。
- 支持 HPA 和原地 VPA。
- 升级过程中的扩容使用 LastGoodVersion。
- 支持 Node 核心状态自检,Node 异常时 Pod 能自动漂移。
- 支持容器原地升级。
- 支持升级失败 Pods 的容忍率控制,大规模升级过程中升级失败 Pods 占比小于 x% 时可继续升级。
这里主要介绍为腾讯会议上 TKE 新增的两个发布能力增强:大规模自动分批灰度发布和 ConfigMap 分批灰度发布。
支持单个 StatefulSetPlus 上万 Pods 的自动分批发布能力
TKEx 平台在原来 StatefulSetPlus 手动分批发布能力基础上,这次又开发了自动分批发布的特性,解决像腾讯会议这种大体量业务灰度发布的痛点。用户只要在发布时配置各个批次更新副本的百分比,比如第一批 40%,第二批 60%。StatefulSetPlus-Operator 会根据 Readiness 探针完成情况,自动进行下一批次的更新,其原理如下。
StatefulSetPlus 核心 Field 说明如下:
batchDeployConfig:
- batchNum:分几批升级
- batchAuto:是否自动分批发布,true 表示自动分批发布
- batchIntervalMinutes:两次分批发布之间的间隔分钟数
- podsNumToUpdate:各批次发布的 pod 数量,如果不设置则将 pod 平均到每批次发布
StatefulSetPlus 对发布过程进行了精细化的监控,提供 staus.batchDeployStatus
查询发布详细状态,这使得通过 CI Pipeline 发布变得更显示和可控。
batchDeployStatus:
- action:当前操作,
Next
表示进行下一批发布,WaitToConfirm
表示等待确认该批次发布是否成功,Completed
表示所有批次均已确认发布成功。 - batchDeadlineTime:本批次发布的 Deadline,如果超过该时间,本批次的 Pod 仍然未 Running & Ready,那么本批次发布失败,进入自动回滚流
- batchOrder:当前批次
- batchOrdinal:本批次发布 pod 的 Index 的起点
- batchReplicas:本批次发布的 pod 的数量
- currentDeployComplete:本批次发布是否完成
- currentOrderSuccessPer:成功升级的 pod 所占百分比
- currentOrderProgress:本批次发布是否成功
- currentRollbackProgress:本批次回滚是否成功
- generalStatus:本次发布全局状态
- action:当前操作,
可在 annotations 加上 platform.tkex/pause-auto-batchDeploy: "true"
来暂停自动分批发布和失败自动回滚。
在 TKEx 平台上,通过如下操作流程即可轻松完成自动分批发布。
腾讯会议最大的模块需要支持上万个 Pods 的灰度发布,这是前所未有的挑战。这一次,我们对 StatefulSetPlus-Operator 进行了优化,性能得到大幅提升。对于一万个 pod 的 StatefulSetPlus,分 5 批自动升级,单批次 2000 个 pod,不挂载 cbs 盘的场景,表现如下:
- 非原地升级方式:单批次升级处理耗时 40-45 秒,单批次升级从发起升级到升级完成耗时三分半钟,升级过程中单次同步 StatefulSetPlus status 耗时 10 秒左右。
- 原地升级方式:单批次升级处理耗时 30 秒左右,单批次升级从发起升级到升级完成耗时一分十秒左右,升级过程中单次同步 StatefulSetPlus status 耗时 10 秒左右。
- 正常情况下(非升级过程),同步 StatefulSetPlus status 毫秒级。
支持 ConfigMap 的分批灰度发布和版本管理
Kubernetes 原生的 ConfigMap 更新是一次性全量更新到容器内的对应的配置文件,所以通过原生的方式更新配置文件是极其危险的事情。Kubernetes 1.18 支持了 Immutable ConfigMap/Secret,可以保护关键配置被误改导致业务受影响。业务对容器环境下配置文件的发布同样有着分批灰度发布的极高诉求。
于是我们给 StatefulSetPlus 赋予了分批发布配置文件的能力,提升了云原生场景下配置文件发布的安全性,原理如下:
方案概述:
- 用户修改 ConfigMap 后提交,后台自动创建一个新的 ConfigMap,其中 ConfigMap Name 后缀是 data 内容的 hash 值,防止同样的 data 内容创建出多个 ConfigMap,然后在 Lable 中添加没有 data hash 值的真正的 ConfigMap 名字,另外在 lable 中添加 version,或者允许业务自定义一些 lable 以便标识 ConfigMap 的版本。
- Kubernetes 对 Pod 的修改只支持更新栏位
spec.containers[*].image, spec.containers[*].resources(if inplace resources update feature enabled),spec.initContainers[*].image, spec.activeDeadlineSeconds or spec.tolerations(only additions to existing tolerations)
,因此需要修改 kube-apiserver 代码,使得允许 update/patch volumes。 - 通过 StatefulSetPlus 的分批灰度发布能力,逐个批次的对 Pods 引用的 ConfigMap 进行修改,由 kubelet volumemanager 自动 reload configmap,因此 ConfigMap 的更新不需要重建 Pods。
为防止 ConfigMap 累积过多,影响 etcd 集群的性能,我们在自研组件
TKEx-GC-Controller
增加 ConfigMap 的回收逻辑,只保留最近 10 个版本的 ConfigMap。
用户只要在更新 Workload 页面,选择手动分批或者自动分批更新,在数据卷选项重新选择新版本的 ConfigMap 即可。可以在更新业务镜像的同时也更新 ConfigMap 配置文件,或者只更新 ConfigMap 配置文件。
ConfigMap 配置文件更新,需要容器内业务进程能 watch 到配置文件的变更进行重启加载或者热加载。然而有些业务当前并没有这个能力,因此 TKEx 在 ConfigMap 发布的入口提供配置文件更新后的 ProUpdate Hook,比如业务进程的冷 / 热重启命令。
如何保证有状态服务的升级只有 ms 级抖动
拒绝胖容器模式(把容器当虚拟机用)是 TKEx 平台的原则,如何使用镜像发布并且提供像进程重启一样的 ms 级业务抖动,这是腾讯会议容器化上云最有挑战性的需求之一。TKEx 平台在灰度发布能力上已经做了长期的技术沉淀,上万个业务模块在使用,但当前能力仍无法满足这一需求,镜像预加载 + 容器原地升级的方案,仍与这目标差距甚远。
经过多个方案的设计、分析、测试对比,考虑通用性、云原生、发布效率多个因素,最终使用如下方案:
Pod 里面有 3 个关键容器,它们的职责分别如下:
- biz-sidecar: Sidercar 容器职责很简单,检测 Pod 是否在升级中。通过 Readyness Probe 比较 EmptyDir Volume 中的业务发布版本文件 version1 和 version2 的内容是否相等,相等则 Ready,否则 notReady。
- biz-container:容器启动脚本会将环境变量(预注入)里的一个版本号写到 versionX 文件中,然后开始循环等文件锁,如果成功获取文件锁则启动业务进程。文件锁是防止 Pod 内同时运行多个版本的业务 Container 的关键,用文件锁来做不同版本容器的互斥。
- biz-pause:启动脚本会将环境变量里的一个版本号写到 versionX 文件里,然后就进入无限 sleep 状态。这个容器是备用容器,当业务升级时,它就会通过原地升级的方式切换到 biz-container 的角色。
升级流程概述
以业务容器镜像从版本 V1 升级到版本 V2 为例,升级流程描述如下:
- 用户第一次部署业务,如上最左边的 Pod, 一共有 3 个容器。biz-sidecar,biz-container(配置环境变量版本号为 1)以及 biz-pause(配置环境变量版本号为 1)。所有 2 个容器启动之后会分别将 version1, version2 文件的内容更新为 1,biz-sidecar 此时为 Ready。
- 更新 Pod 之前的 biz-pause 容器为业务 V2 版本的镜像同时环境变量版本号为 2,等该容器原地升级之后把 version2 文件的内容更新为 2 之后开始等文件锁。此时 biz-sidecar 探针转为 notReady 状态。
- StatefulSet-Operator Watch 到 biz-sidecar 为 notReady 之后再将之前的 v1 版本的业务镜像替换成 biz-pause 镜像同时环境变量版本号为 2。等 pause 镜像容器原地重启之后会将之前 v1 业务镜像占的文件锁释放,同时 version1 内容更新为 2。此时 sidecar 探针为 Ready, 整个升级结束。
需要说明以下两点:
- 原生 Kubernetes apiserver 只允许修改 Pod 的 image 等 field,不支持修改 resource 以及环境变量等,所以该方案需要改 K8s apiserver 的相关代码。
- 另外为了保证 Pod Level Resource 以及 Pod QoS 不变,StatefulSetPlus-Operator 在升级时需要对容器状态变更过程中进行 Container Resource 调整。
多地域部署和升级,变得更简单
在多地域服务管理上,我们主要解决两个诉求:
- 同一个服务需要部署在很多的地域,提供就近访问或者多地容灾,如何进行服务在多个集群的快速复制;
- 部署在多个地域的同一个服务,如何进行快速的同步升级;
TKEx 提供了便捷的多地域多集群业务部署和业务同步升级能力。
- 支持一次性部署到多个地域多个集群。
- 支持部署在多个集群的 Workload 同步升级。
平台资源管理能力增强
TKEx 平台的集群资源是所有服务共享的,各种服务混部在集群和节点中。各个产品都有自己的资源预算,平台接受各个产品的预算,然后根据自动生成对应的资源配额,以此控制各个产品在整个平台上的 Quota。产品部署后,涉及到成本核算,平台会根据真实使用的资源量,以小时为时间计量粒度,跟踪统计每个业务产品下面各个 Workload 的资源使用情况。
DynamicQuota-Operator
Kubernetes 原生用 ResourceQuota 来做资源限制,但是它与我们的期望相比存在如下问题:
- ResourceQuota 是基于 Namespace 的,无法做到产品基本的限制。
- ResourceQuota 是基于集群内的限制,无法做到平台级的,无法进行多集群联动 Balance。
- 只有限制能力,无法保障业务有足够的资源可以使用。
基于我们对于业务产品的管理需求及期望,TKEx 的配额管理系统须满足如下特性:
- 使用简单,用户无需关心底层细节,比如配额如何在各个集群间分布及调配都由系统来自动完成。
- 分配给产品的配额,必须保障产品始终有这么多资源可以使用。
- 满足平台在离线混合部署场景诉求,配额要有限制离线任务配额的能力。
- 为了避免某一个产品占用配额而不使用导致平台资源浪费,要有在产品间配额借和还的能力。
我们设计了一个 DynamicQuota CRD,用来管理集群中各个业务产品的 Quota,实现以上能力。
- Quota Rebalance Worker: 该 Worker 会定期根据产品在各集群的配额使用情况,动态的将产品配额在各集群间调配。比如有个产品的服务因为配置了弹性扩缩容,当产品在某个集群因为扩容导致配额用完但是在其他的集群还有比较多的配额,这时 Worker 就会将配额从空闲集群调配到该集群。
- DynamicQuota Operator: 负责维护自定义 CRD DynamicQuota 的状态,同时会收集各产品在集群中的使用情况暴露给 Prometheus。
- DynamicQuota ValidatingWebhook: 截获集群中所有向 kube-apiserver 的 pod 创建请求,并阻止那些超配额的产品 Pod 创建请求。
- OfflineTask QueueManager: 负责从离线作业队列(ActiveQ)中根据作业优先级进行消费,并判断各个集群的离线作业资源占比是否超过水位线,以达到控制所有离线作业资源占比的目的,防止离线作业消耗过多的集群资源。
- pod-resource-compressor 和 VPA 组件,根据集群和节点实际负载、资源分配情况,对离线作业进行资源压缩和原地升降配,以保护在线任务的资源使用。在离线混部时,我们还在内核层面对 Cpu 调度进行了优化,以达到离线任务快速避让,以保证在线任务的服务质量。
预算转移自动生成产品 Quota
产品完成预算归属到 TKEx 平台后,平台将自动为产品增加对应的产品配额,自动修改 DynamicQuota。用户可以在 TKEx 监控面板中查看归属产品的资源配额。
业务核算自动化和可视化
TKEx 会以 核 * 时
为业务使用资源的计量粒度进行成本核算,用户可以在 TKEx 监控面板中查看具体的各个 Kubernetes Workload 的详细资源使用情况。
提升自愈能力
随着集群规模和节点部署密度越来越高,集群平均负载超过 50%,高峰期很多节点的负载甚至 80% 以上,一些稳定性问题开始显现,为此 TKEx 针对节点稳定性做了如下优化:
- 主动探测 dockerd 的可用性,异常时主动重启 dockerd,防止 dockerd hung 住导致 Node 上 Pods 自动销毁重建,
- 主动监控 kubelet 的可用性,异常时主动重启 kubelet,防止 kubelet hung 住导致 Pods 大量漂移重建。
- 因为 Kubernetes 在 pids.max, file-max 等内核参数隔离机制不完善,在 kubernetes 1.14 中虽然支持了对 Pods 内 Pids numbers 的限制,但实际落地时很难为业务指定默认的 pids limit。集群中仍会出现因为节点 Pids 和 file-max 耗尽导致同一节点上其他业务容器受影响的问题。对此,我们在
Node-Problem-Detector
(简称 NPD)组件中增加了节点 pids,file-max 的监控,达到相关资源使用水位线时,会自动检测消耗 pids 和 file-max 最多的 Container 并上报,主动触发告警并对 Container 进行原地重启。
以上几项能力都在 NPD 组件中实现,负责监控节点的工作状态,包括内核死锁、OOM 频率、系统线程数压力、系统文件描述符压力等指标,做节点驱逐、节点打 Taint 等动作,并通过 Node Condition 或者 Event 的形式上报给 Apiserver。
当前 NPD 组件会在节点中增加如下特定的 Conditions:
Condition Type | 默认值 | 描述 |
---|---|---|
ReadonlyFilesystem | False | 文件系统是否只读 |
FDPressure | False | 查看主机的文件描述符数量是否达到最大值的 80% |
PIDPressure | False | 查看主机是否已经消耗了 90% 以上的 pids |
FrequentKubeletRestart | False | Kubelet 是否在 20Min 内重启超过 5 次 |
CorruptDockerOverlay2 | False | DockerImage 是否存在问题 |
KubeletProblem | False | Kubelet service 是否 Running |
KernelDeadlock | False | 内核是否存在死锁 |
FrequentDockerRestart | False | Docker 是否在 20Min 内重启超过 5 次 |
FrequentContainerdRestart | False | Containerd 是否在 20Min 内重启超过 5 次 |
DockerdProblem | False | Docker service 是否 Running(若节点运行时为 Containerd,则一直为 False) |
ContainerdProblem | False | Containerd service 是否 Running(若节点运行时为 Docker,则一直为 False |
ThreadPressure | False | 系统目前线程数是否达到最大值的 90% |
NetworkUnavailable | False | NTP service 是否 Running |
有些事件是不适合在 NDP DaemonSet 做分布式检测的,我们就放在 TKEx Node Controller 中去做中心式检测,由其生成 Event 并发送到 Apiserver。比如:
- 快速检测 Node 网络问题,而不依赖于 5min 延时的 NodeLost Condition,这个问题 NDP 检测到也无法把事件发送到 Apiserver。
- Node Cpu 持续高负载导致业务服务质量下降,会有 TKEx Node Controller 做检测,并将 cpu 负载 Top N 的 Pods 通过 Event 发送到 Apiserver,由 TKEx-descheduler 来决定驱逐哪些 Pods。做驱逐决策时,需要考虑 Pods 所属 Workload 是否是单副本的,Pods 是否能容忍 Pods 漂移重建等。
TKEx-descheduler 则负责 ListWatch NPD 和 TKEx Node Controller 发送的 Events,做出对应的行为决策,比如对 Pod 内某个问题 Container 进行原地重启、问题 Pod 的驱逐等。
容器网络增强和调度能优化
容器网络支持 EIP
TKEx 之前提供的 VPC+ENI
的 Underlay 网络方案,使得容器网络和 CVM 网络、IDC 网络在同一网络平面,并且支持容器固定 IP,极大地方便自研业务上云。这次 TEKx 平台的容器网络能力再升级,支持在使用 HostNetwork
和VPC+ENI
容器网络方案上,再为 Pod 分配 EIP(弹性公网 IP)的能力。
调度优化
当后端集群资源池耗尽,会有大量的待调度的 pending pods,此时使用任何类型的 Workload 进行镜像更新时都会出现资源抢占导致升级失败的情况。
为了解决这个问题,提升业务升级的稳定性,我们优化了 Kubernetes Scheduler Cache 的逻辑,给 StatefulSet/StatefulSetPlus 升级时提供了资源预抢占的调度能力,很好的保证了在不新增资源的情况下 StatefulSet/StatefulSetPlus 能正常升级成功,不会被调度队列中的 Pendnig Pod 抢占资源。
后面团队会单独输出一篇技术文章对此进行详细分析,感兴趣的同学请关注 腾讯云原生 公众号,加小助手 TKEplatform,拉你进腾讯云容器技术交流群。
总结
本文总结了腾讯会议在 TKE 容器化部署时用到的平台相关特性,包括业务镜像自动分批灰度发布、ConfigMap 分批灰度发布、Pod 内 A / B 容器 ms 级切换发布、多集群发布管理、基于 DynamicQuota 的产品配额管理、探测节点和集群稳定性问题以提升自愈能力等。腾讯自研业务在 TKE 上沉淀的优秀组件和方案,后面会在公网 TKE 产品中提供给公网客户,也在计划开源,敬请期待。