作者
卓晓光,腾讯广告高级开发工程师,负责新闻视频广告整体后台架构设计,有十余年高性能高可用海量后台服务开发和实践经验。目前正带领团队完成云原生技术栈的全面转型。
吴文祺,腾讯广告开发工程师,负责新闻视频广告流量变现相关后台开发工作,熟悉云原生架构在生产实践中的应用,拥有多年高性能高可用后台服务开发经验。目前正推动团队积极拥抱云原生。
陈宏钊,腾讯广告高级开发工程师,负责新闻视频广告流量变现相关后台开发工作,擅长架构优化升级,有丰富的海量后台服务实践经验。目前专注于流量场景化方向的广告系统探索。
一、引言
新闻视频广告团队主要负责腾讯新闻、腾讯视频、腾讯微视等媒体广告流量的广告变现提收工作,在媒体流量日益复杂,广告变现效率逐步提升的背景下,团队需要负责开发并维护的后台服务数量增长迅猛,服务维护成本与日俱增,传统的基于物理机的部署以及运维方案已经成为了制约团队人效进一步提高的瓶颈。
自2019年开始,公司鼓励各研发团队将服务迁移上云,并对服务进行云原生改造。为了响应公司的号召,在充分调研了服务上云的收益后,我们决定将团队维护的业务分批上云,提升后台服务的部署及运维效率。
二、业务上云“三步走”
图2-1 新闻视频广告后台整体架构图
新闻视频广告主要承接来自腾讯新闻、腾讯视频、片多多以及腾讯微视的广告流量接入并负责提升流量的变现效率,具体包括流量接入、形态优化、ecpm 优化等等。如上图所示,由于接入的业务需求复杂多样,而且彼此之间有显著差异,比如内容广告、激励广告等,导致新闻视频广告的后台服务具有如下特点:
- 请求量大,线上服务需要承接来自流量方的海量请求,服务性能及稳定性要求高;
- 服务数量众多,不同服务之间负载差异大,同一服务不同时间段负载变化大,人力运维成本高;
- 依赖的外部组件多,服务发布与运维的流程都与传统的物理机模式深度绑定,历史包袱重,平滑上云挑战大。
针对以上这些问题,我们在服务迁移上云的过程中,定制了”三步走“计划:
- 搭建上云依赖的基础组件并规范上云流程;
- 离线服务快速上云;
- 海量在线服务平滑上云;
三、第一步——搭建基础组件&规范上云流程
为了提高服务上云效率,方便各个服务负责人自行将服务迁移上云,我们首先搭建了云服务基础组件,并输出了服务上云的规范,主要包括容器化 CI/CD 和服务平滑上云两部分。
3.1 容器化 CI/CD 配置
想要上云,首先要考虑的问题就是如何将服务在云平台上部署。我们对物理机和云平台的部署情况做对比。
物理机 | 云平台 | |
---|---|---|
调度单位 | 服务器 | Pod |
单位数量 | 少 | 多 |
数量变化 | 相对固定,审批流程通过后才能增加或减小单位数量 | 弹性较大,动态缩扩容随负载随时变化 |
表3-1 物理机和云平台的部署情况对比
可以看到,物理机和云平台面临的情况完全不同。在物理机时代,我们采用织云部署服务,在织云平台手工配置服务器目标,管理二进制和部署,而转向云平台后,大量 Pod 动态变化组成的集群,与假定管理对象都是固定 ip 的服务器的织云并不契合。因此,我们转向采用蓝盾实现自动化集成与部署。
蓝盾将集成与发布流程整合并统一管理,代码合入即自动触发编译、回归、审核和发布流程,无需额外人工。为了兼容已有的物理机服务的发布流程,保证混合部署服务的版本一致,二进制包仍然使用织云管理,由蓝盾流水线编译产物后推送至织云。蓝盾流水线从织云拉取二进制后和运维提供的包含必要 agent 的基础镜像打包,将环境与二进制标准化,模版化,以镜像的方式作为最终产物发布,拉起即用,便于 pod 快速部署与扩容,减少了手工标准化服务器的工作量。最终,我们达成了如下目标:
- 自动化部署,节约人力;
- 兼容物理机发布流程,便于混合部署;
- 模版化环境,方便快速扩容。
图3-1 蓝盾实现 CI/CD 流水线
3.2 后台服务平滑上云
3.2.1 基础组件平滑上云
新闻视频广告后台服务依赖北极星、智研等基础组件提供负载均衡、指标上报等基础功能,然而在将这些基础组件迁移上云后,我们发现它们并不能如期提供能力,影响了服务正常运行。经过深入排查分析,我们发现,这些组件不能正常工作的原因主要包括以下2点:
- 容器的 ip 不属于 idc 网段,这些基础组件在容器中的 agent 与它们的 server 无法连通;
- 容器 ip 会随着容器的升级和迁移而发生变化,组件注册的 ip 频繁失效。
而这2个问题都是由于 TKE 平台为容器配置默认的 Overlay 网络策略导致的。为了解决上述的问题,TKE 平台方建议我们采用“浮动 ip”和“删除或缩容 APP 时回收”等高级网络策略。浮动 ip 策略不同于 overlay,分配由运维指定的 idc 网段的 ip,其他 idc 机器上的服务可以直接访问;“删除或缩容 APP 时回收”策略将 ip 与对应的 pod 绑定,即使 pod 更新与迁移,ip 也不会发生改变。
我们在新增工作负载时,在高级设置中配置浮动 ip 与删除或缩容 APP 时回收的策略,保证增量负载的组件工作正常;同时修改已有负载的 yaml 配置,添加如下图的配置项,将存量负载的配置对齐增量负载。双管齐下,最终解决了网络策略对基础组件的影响,促进基础组件与云平台的协同工作。
图3-2 TKE 平台新增负载策略选择
图3-3 TKE 平台存量配置修改
3.2.2 流量平滑迁移
将服务成功部署上云后,接下来就需要让云上集群承接外部流量,发挥职能。新闻视频流量当前都路由至物理机,若直接全量切换访问云服务,会面临如下问题:
- 云上集群服务未经过流量考验,可能导致未知的问题在线上暴露;
- 新集群未充分预热,可以承受的 QPS 较低。
可以想见,这样激进的操作极其危险,容易引发事故甚至集群崩溃,因此我们需要将流量平滑的切至云平台,留下充裕的时间应对上云过程中出现的问题。
针对平滑切换的目标,我们提出了两个上云指导方针:
- 小步慢跑。分多阶段迁移流量,每一次仅将少量的流量切换至云平台,切换后,观察系统监控以及业务指标监控无异常后,再进行下一次的流量迁移。
- 灰度验证。对标物理机的发布操作,挪出部分资源搭建灰度集群,每一次流量切换前,先在灰度集群上面实验,灰度集群验证完成后,再在正式集群上执行对应的操作。
最终,我们在全量切换前提前发现问题并解决,保证线上服务的稳定,实现了流量平滑顺畅的迁移。
四、第二步——150+离线服务的快速上云
新闻视频广告的离线后台服务数量众多,服务总数150+;功能也很复杂多样,既有视频特征抽取等高计算量的服务,也有仅提供通知能力的低负载的服务。在上云过程中,如何为纷杂繁多的离线服务,设计与之适应的资源选取、优化与调度方案?对于这个问题,我们总结出了一套符合新闻视频广告离线业务特点的上云方案。
4.1 离线服务资源分配方案
4.1.1 资源分配模板设计
为了提升 CPU 和内存的利用率,我们希望对不同种类的服务分配合适的资源,保证资源充分利用。在上云过程中,我们总结出一套分配资源的方法。离线服务的单个 pod,CPU 限制在0.25核-16核之间,内存最小不能低于512M,在满足下限的同时,最好与核数保持1:2的比例。遵循这套方法有什么好处呢?下面是我们总结的原因:
- 若 CPU 核数大于32,可能导致 TKE 平台在调度时,找不到空闲的核数足以满足要求的节点,导致 Pod 扩容失败;
- 若 CPU 核数大于32,可能导致 TKE 平台的资源碎片化,降低集群的整体资源利用率;
- 如果容器分配的 CPU 低于0.25核,或者内存低于512M,会导致北极星等公共组件的 agent 无法正常运行,容器无法正常拉起。
在实际的使用中,我们结合上云的实战经验,总结出不同类型服务的推荐配置表格如下所示。
CPU核数 | 内存大小 | |
离线定时轮询服务 | 0.5核 | 1G |
离线运营类服务 | 2核 | 4G |
离线计算类服务 | 8核 | 16G |
表4-1 不同类型服务的推荐配置
4.1.2 基础镜像简化
按照推荐配置表格的设置,我们将离线服务部署在 TKE 平台上。一开始,服务能够平稳的运行,然而,随着时间的推移,公共镜像中的 agent 越来越多,agent 占用的资源越来越大,对于通知服务等分配资源较少的服务,agent 的资源占用提升后,甚至超过服务本身使用的资源,导致 OOM 或容器无法拉起等异常表现。
我们既想要优化不断增长的agent数量带来的资源消耗提升,又想要享受公共镜像的更新,有没有两全其美的办法呢?答案是肯定的。我们在 CI/CD 流水线构建业务镜像时,通过 RUN 命令新增删除多余 agent 的流程。在删除 Agent 流程的内部,我们配置标记服务依赖的必要镜像,反选其他无用的 agent,执行删除。由于RUN命令新增了一层不可变文件层,不影响该层以前的公共镜像文件层,公共镜像更新 agent 时,也会作用到业务镜像。通过这种方式,我们既节省了 agent 使用的资源,保证低资源分配的服务正常运行,又享受了公共镜像自动更新 agent 带来的便利。
图4-1 镜像瘦身示意图
4.2 离线服务 HPA 配置方案
很多离线服务的负载并不是稳定不变的,白天与夜晚的负载往往会有较大差异,如果为这些服务分配恒定的资源,可能会导致服务负载高峰期资源过载,负载低谷期资源闲置。因此,我们希望分配给容器的资源,能够随着服务负载的变化而变化。为此,我们选择使用HPA组件实现弹性缩扩容。
4.2.1 HPA配置模板设计
想要使用 HPA,首先需要选取触发是否应该缩扩容的指标。选取衡量指标的核心思想,是尽量选取变化最大的指标,避免变化较小的指标限制 Pod 数量的变化,导致其他负载指标的变化超出资源限制。对于离线任务而言,一般来说,CPU 使用率更容易随着任务的开启和结束发生变化,而内存一般存储相对固定的上下文,比较稳定,选取 CPU 使用率作为判定标准比较合理。
图4-2 选取 CPU 利用率作为 HPA 的衡量指标
其次,我们要指定 Pod 数量的上限和下限。上限可以避免不正确的配置造成大量 Pod 创建,空耗集群资源,影响集群稳定性。而下限有两个作用,第一,确保 Pod 数量符合 HPA 组件的最小副本不为零的限制,避免组件采集不到 metrics,无法获取缩扩容调整所依赖的指标后,失去调节副本数量的功能,从而使集群陷入 Pod 数量恒定为0,无 pod 可供服务的情况;第二,若少量 Pod 因故障陷入无法服务的状态,保证一定数量的 Pod 可以减小故障对服务的冲击。
图4-3 设置 HPA 调整实例数量的上下限
最后,我们需要根据具体的负载变化曲线,决定弹性缩扩容的策略。
- 如果负载曲线在每一天每一个时段下都高度一致,我们可以考虑使用 CronHPA 组件,人工指定不同时段的 Pod 数量,定期调度。
- 如果负载曲线每天都在发生变化,不论趋势还是数值,我们都可以使用 HPA 配置,设置 CPU 使用率参考值,要求集群在利用率超过或低于指定值时进行 Pod 数量的调整,将 Pod 的 CPU 使用率维持在容忍范围内。
- 如果负载曲线每天发生变化,但是存在定时发生的尖峰,为了避免集群自行调整的速度慢于负载增长的速度,导致集群被压垮,我们可以结合 CronHPA 与 HPA 使用,平时将控制权交给 HPA,在尖峰到来前使用 CronHPA 提前扩容,既保证尖峰不会冲垮集群,又能享受集群自动调整 Pod 数量带来的便利。
图4-5 结合 CronHPA 与 HPA 共同使用
由此,我们完成了 HPA 的设置,通过多样的策略,实现了集群分配资源随着服务负载变化。
4.2.2 IP 白名单自动变更
服务 Pod 数量动态变化,会导致部分服务运行的环境中,IP 地址经常改变。而广告业务服务访问下游接口时,大多需要通过静态的 IP 白名单校验。静态的 IP 白名单不再适合云原生环境下部署的服务,我们希望推动下游的 IP 白名单支持动态添加容器 IP,拥抱云原生。为此,我们根据下游权限的敏感等级,使用不同的处理方式完成改造。
- 对于敏感等级较低的接口,我们推动接口作者提供 IP 自动上报的接口,为每一位用户下发凭证,服务启动前使用调用接口,上报当前的 IP 地址加入白名单。例如北极星为我们提供了上报脚本,我们仅需要在容器的启动脚本内调用上报脚本即可完成上报。由于服务运行期间,IP 将不再变更,因此仅需要上报 Pod 拉起时的 IP,即可保障服务的稳定运行。
- 对于敏感等级高的接口,作者不信赖来自用户的操作,担心用户的错误操作击穿白名单保护,例如 CDB 团队就担心广告隐私数据泄漏。我们推动这些团队与 TKE 平台方合作,开放授权给 TKE 平台,用户申请鉴权凭证后托管在 TKE 平台上,由 TKE 平台拉起容器的时候负责上报新的 IP。
图4-6 TKE 平台配置授权
最终,我们实现了下游接口对容器缩扩容的感知,自动化更新白名单,保障服务在弹性缩扩容生效的情况下正常工作。
五、第三步——海量在线服务的平滑上云
新闻视频广告系统承载百亿级别的流量,后台在线服务的QPS最高可以达到几十万的级别,在迁移上云后同样需要保证服务的高性能、高可用以及可扩展等。为了满足这个要求,我们与运维、TKE平台方共同合作,解决上云过程中所遇到的问题,保障海量在线服务顺利平滑上云。
5.1 计算密集型服务延时毛刺优化
广告系统中,在线服务大多属于计算密集型,需要较高的性能保证计算按时完成。如果相关的计算逻辑在不同的 CPU 核间频繁调度,会引发 cache miss 频率的提升,程序性能降低,请求时延经常出现毛刺。因此,部署在物理机器上的服务大量使用绑核能力,手工指定服务运行的 CPU,提升局部性,提升程序性能。
然而上云后,平台对 CPU 资源进行虚拟化管理,服务在容器内通过“/proc/cpuinfo”获取到的是分配隔离和重排序后的虚拟 CPU 列表,与节点实际的 CPU 列表大相径庭。使用虚拟的 CPU 列表进行绑核操作,不仅可能绑定到未分配的 CPU,性能不符合预期,甚至会绑定到不存在的 CPU,引发程序错误。
图5-1 96核机器节点上,某容器内虚拟化的 CPU 列表
因此,需要找到一种在云平台上获取实际 CPU 列表的办法。我们考虑到云平台通过 cgroup 管理并隔离资源,可以尝试在 cgroup 中寻找获取实际 CPU 列表的接口。于是我们查找资料发现,cgroup 提供“/sys/fs/cgroup/cpuset/cpuset.cpus”子系统,可以获取到虚拟 CPU 列表在物理列表上的实际映射范围,符合我们的要求。我们修改绑核功能中获取 CPU 列表的代码,将读取 proc 子系统的部分改为读取 cgroup 子系统,从而成功实现云上服务的绑核功能。
图5-2 cgroup 实际分配的 CPU 列表
5.2 在线服务的高可用性保障
在线服务的生命周期分为三个阶段,服务启动,准备就绪,服务销毁。服务只有处于准备就绪阶段才对外可用。而集群的可用性,取决于加入负载均衡的服务中准备就绪的比例。因此,要想提高服务的可用性,可以从两个方向努力:
- 降低服务启动的时长,提升准备就绪状态在服务生命周期的占比。
- 降低访问服务的失败率,保证加载和销毁阶段的服务不加入负载均衡。
5.2.1 降低服务启动时长
想要服务的启动时长,需要分析服务启动过程中,有哪些耗时占比高的操作。经过分析,我们发现,广告服务在服务启动的过程中,其中有一个步骤是通过文件同步服务 byteflood,订阅同步广告、素材、广告主等数据的文件到容器本地。这个步骤非常耗时,占上下文加载阶段的耗时大头。有没有什么办法能够降低这个步骤的耗时呢?
深入探索后,我们找到了优化的空间。原来,byteflood 每次都需要拉取全量的数据文件。为什么不能增量拉取呢?因为 byteflood 组件在容器中同步到本地的文件,存储在容器的可变层,一旦容器由于升级或者迁移触发重建,则数据文件全部丢失,必须重新拉取全量文件。看来,只需要持久化存储数据文件,我们就可以避免文件丢失,采用增量拉取的方式更新数据,从而降低数据订阅步骤的耗时,缩短上下文的加载时长,提高服务的可用性。
我们采用挂载外部数据卷(volume)的方式存储数据文件。外部数据卷独立于容器文件系统,容器重建不会影响外部数据卷中的文件,保证了数据文件的持久化。
图5-3 使用挂载点持久化订阅数据文件
然而,使用 volume 挂载后,我们遇到了路径不一致的新问题。由于TKE平台规范限制,挂载的路径和物理机上不一致,为了保持云服务和物理机的服务配置一致,我们想要通过软链将配置路径指向挂载路径。但是挂载的行为发生在容器拉起时,而服务进程启动前,即需要保证配置路径包含数据文件。如果需要在pod启动后手工维护软链,不仅生效时间可能在服务进程执行读取操作后导致服务读不到数据,而且生成的软链同样面临容器重建后被丢弃的问题。为此,我们将容器的entrypoint,即容器启动时调用的命令,替换为自行实现的启动脚本,在脚本内加入生成软链的语句,服务启动语句放在软链的后面。容器启动即执行软链操作,无需手工处理,重建也能保证再次执行,解决了配置路径的问题。
图5-4 软链指向实际挂载路径,对齐配置路径
最终,我们成功的外挂了数据文件,将服务启动时长从5分钟降低到10秒钟,效果显著。
5.2.2 降低访问服务的失败率
容器内服务的状态若处于加载中或者已销毁,将无法处理请求,如果这些无法处理请求的容器的 IP 处于负载均衡的列表中,就会降低集群可用性。想要降低请求访问服务的失败率,必须保证负载均衡关联的服务均处于准备就绪的状态,而保证负载均衡关联的服务均处于准备就绪的状态,关键在于将负载均衡的关联状态纳入服务的生命周期管理,服务脱离加载状态前,不允许容器加入负载均衡,服务如果需要变更至销毁状态,需要在变更前将容器地址从负载均衡服务中剔除。
平台侧已经将公司的负载均衡服务——北极星——纳入容器的生命周期,不将 Not Ready 的容器加入北极星,容器销毁时将容器地址从北极星剔除。然而,由于容器的生命周期不同于服务生命周期。容器进入 Ready 状态时,服务可能仍在加载上下文,被加入北极星却无法提供服务;容器销毁时,平台发起剔除的请求,但是北极星组件内部状态并非即时更新,可能在容器销毁后,依然转发流量到已销毁的容器。需要找到合适的方法,使北极星感知服务生命的周期,避免流量转发至加载或销毁状态的服务。
想要禁止加载状态的服务加入负载均衡,可以借助平台方提供的就绪检查功能。就绪检查通过定期检查业务的 tcp 端口状态,判断服务是否已经加载完成,若未加载完成,则将 Pod 状态设置为 Unhealthy,同时禁止上游的北极星将流量转发到这个容器,直到加载完成。通过就绪检查,我们保证在服务加载完成前,不会有请求发送至服务所在的 Pod。
图5-5 配置就绪检查
想要保证服务变更至销毁状态前将服务地址踢出负载均衡,同样需要借助 TKE 平台的功能——后置脚本。平台侧允许业务方指定一段脚本,该脚本将在容器销毁前执行。我们在该脚本内执行等待一段时间的操作,保证上游的负载均衡器更新状态后,再销毁容器。通过后置脚本,我们保证容器在销毁前,负载均衡不再向该容器转发任何请求。
图5-6 后置脚本示例
5.3 高并发服务连接失败优化
新闻视频流量大部分在线服务需要承载海量请求,同时处理大量的并发,例如品效合一后台服务。在这些服务迁移到 TKE 平台的过程中,随着流量的逐步增长,系统失败量显著增加,特别的,一些使用短连接的服务出现了大量连接失败的情况。为此,我们和运维以及 TKE 平台方的同学共同合作,排查并解决了问题,顺利推进新闻视频团队业务在线服务全部上云,同时也增加了大家将海量服务迁移上云的信心。
经过排查,我们发现错误率的提升主要由两点导致。
- 内核的流量统计功能长期占用 CPU 导致网络处理延迟。 kubernetes 通过 ipvs 模块管理 NAT 规则控制流量转发容器,而 ipvs 默认开启流量统计功能,他向内核注册计时器触发统计操作。计时器触发时,通过自旋锁占用 CPU,遍历规则统计。一旦节点上 Pod 数量较多,规则数量多,则计时器长期占用 CPU,影响服务进程处理回包。
- 连接数超出 NAT 连接追踪表的上限导致新连接被丢弃。 kubernetes 通过 nf_conntrack 模块记录连接信息,而 nf_conntrack 的实际实现为固定大小的哈希表。一旦表被填满,新增的连接会被丢弃。
图5-7 服务日志展示的连接表容量已满的错误
针对第一点,我们希望关闭内核模块 ipvs 的流量统计功能。然而集群的 tlinux 版本与外网版有区别,缺少流量统计功能的开关。平台方推动新增集群 tlinux 补丁同步机制,将外网版本的补丁应用在集群节点上,新增流量统计开关的内核参数。我们配置关闭统计功能后,错误数量下降显著。
图5-8 安装补丁并关闭统计功能后的效果
针对第二点,我们希望增大连接追踪表的大小,避免连接丢弃的问题。平台方及时响应,调整内核参数,保证追踪表大小大于当前连接数。修改配置后,服务日志不再打印“table full”,错误数量也大为降低。
图5-9 提升 tke 流量权重后错误数飙升
图5-10 调整哈希表大小后错误数几乎跌零
在 TKE 平台方的帮助下,我们共同解决了业务上云过程中存在的高并发环境下连接失败问题,成功的将新闻视频流量的在线服务都迁移至 TKE 平台。
六、成果展示
图6-1 上云成果
比较项 | 上云前 | 上云后 |
---|---|---|
资源分配 | 人工申请,弹性低 | 灵活调整 |
资源管理 | 人工 | 平台自动分配 |
资源利用 | 经常面临浪费 | 低谷缩容,高峰扩容,充分利用 |
关注点 | 包含机器和服务 | 专注服务 |
表6-1 上云前后情况对比
新闻视频广告团队积极拥抱云原生,在 TKE 平台以及运维团队的协作与支持下,推动150+后台服务全部上云,上云负载累计核心达到6000+,大大提升了运维效率和资源利用率。资源利用率最高提升至原来的10倍,运维效率提升超过50%,上云过程中,针对服务的特点定制化改造适配云原生,沉淀了针对海量服务、离线服务等多套上云实践方案,有效提高了服务上云效率。
关于我们
更多关于云原生的案例和知识,可关注同名【腾讯云原生】公众号~
福利:
①公众号后台回复【手册】,可获得《腾讯云原生路线图手册》&《腾讯云原生最佳实践》~
②公众号后台回复【系列】,可获得《15个系列100+篇超实用云原生原创干货合集》,包含Kubernetes 降本增效、K8s 性能优化实践、最佳实践等系列。
③公众号后台回复【白皮书】,可获得《腾讯云容器安全白皮书》&《降本之源-云原生成本管理白皮书v1.0》
④公众号后台回复【光速入门】,可获得腾讯云专家5万字精华教程,光速入门Prometheus和Grafana。
【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。