张煜,15年加入腾讯并从事腾讯广告维护工作。20年开始引导腾讯广告技术团队接入公司的TKEx-teg,从业务的日常痛点并结合腾讯云原生特性来完善腾讯广告自有的容器化解决方案
腾讯广告承载了整个腾讯的广告流量,并且接入了外部联盟的请求,在所有流量日益增大的场景下,流量突增后如何快速调配资源甚至自动调度,都成为了广告团队所需要考虑的问题。尤其是今年整体广告架构(投放、播放)的条带化容灾优化,对于按需分配资源、按区域分配资源等功能都有着更强的依赖。
在广告内部,播放流系统承载了整个广告播出的功能,这里的稳定性直接决定了整个腾讯广告的收入,以下为架构图:
业务特点:
在20年腾讯广告已经在大规模上云,主要使用的是 AMD 的 SA2 cvm 云主机,并且已经完成了对网络、公司公共组件、广告自有组件等兼容和调试。在此基础上,基于 CVM 的 Node 云原生也开始进行调优和业务使用,弹性伸缩、Docker 化改造,大量使用各种 PAAS 服务,充分发挥云的高级功能。
以下为广告使用的 TKE 架构:
困难一:通用性
(1)面对广告84个技术团队,如何实现所有业务的适配
(2)镜像管理:基础环境对于业务团队的透明化
(3)腾讯广告容器配置规范
困难二:CPU密集型检索
(1)广告订单数量:百万级
(2)绑核:各个应用之间的 CPU 绑核隔离
(3)关核:关闭超线程
困难三:有状态服务升级中的高可用
(1)广告资源在容器升级过程中的持续可用
(2)在迭代、销毁重建过程中的持续高可用
广告运维侧提供了一套覆盖大部分应用场景的基础镜像,其中以 XXXXXXX-base:latest 为基础,这里集成了原先广告在物理机上面的各个环境配置、基础 agent、业务 agent 等。
并且基于这个基础镜像,提供了多个业务环境镜像,镜像列表如下:
mirrors.XXXXX.com/XXXXX/XXXXXXX-base:latest
mirrors.XXXXX.com/XXXXX/XXXXXXX-nodejs:latest
mirrors.XXXXX.com/XXXXX/XXXXXXX-konajdk:latest
mirrors.XXXXX.com/XXXXX/XXXXXXX-python:3
mirrors.XXXXX.com/XXXXX/XXXXXXX-python:2
mirrors.XXXXX.com/XXXXX/XXXXXXX-tnginx:latest
具体镜像使用情况如下:
在广告的基础镜像中,由于权限集设置未使用到 systemd,所以使用启动脚本作为1号 PID,并且在基础镜像中内置了一份通用的腾讯通用 Agent & 广告独有 Agent 的启动脚本,在业务镜像启动过程中,可以在各自的启动脚本中选择是否调用。
原先大量使用了其他平台的 CD 部分,但现在使用 TKE 后,其他平台已经无法使用。而 TKEx-teg 上的持续化集成部分对于自动化流水线实现较弱,需手动参与,所以在广告内部引入的 CI/CD 方案是腾讯内部的持续化集成和持续化部署方案:蓝盾。
这里全程实现流水线发布,除了审核外无需人工参与,减少人为因素的问题影响。
stage1:主要使用手动触发、git 自动触发、定时触发、远程触发
stage2 & stage3:持续化集成,拉取 git 后进行自定义的编译
蓝盾提供了默认的CI镜像进行编译,不进行二进制编译的可以选择默认(例如 php、java、nodejs等),而后台业务腾讯广告内部大量使用 blade,通常使用 mirrors.XXXXXX.com/XXXXXX/tlinux2.2-XXXXXX-landun-ci:latest 作为构建镜像,此镜像由腾讯广告效能团队提供,内部集成了腾讯广告在持续化集成过程中的各种环境和配置。
编译完成后通过镜像插件,依赖 git 库中的 dockerfile 进行镜像 build,然后推送至仓库中,同时保留一份在织云中。
stage4:线上灰度 set 发布,用于观察灰度流量下的数据表现。通过集群名、ns 名、workload 名来对某个工作负载进行镜像 tag 的迭代,并且使用一份 TKEx-teg 内部的 token 进行认证。
stage5:确认 stage4 没问题后,开始线上的全量,每次都经过审核确认。
stage6 & stage7:数据统计。
另外有一个蓝盾中机器人群通知的功能,可以自定义把需要告知的流程信息,推送到某个企业微信群中,以便大家进行确认并审核。
广告内部的母机都是用的腾讯云星星海 AMD(SA2),这里是90核超线程 cpu+192G 内存,磁盘使用的是高速云硬盘3T,在日常使用中这样的配置这个机型是腾讯云现阶段能提供的最大机型(已经开始测试SA3,最高机型配置会更大)。
如果是已经上线的 workload,则可以通过修改 yaml 来增加目录的挂载:
- mountPath: /data/log/adid_service
name: adid-log
volumes:
- emptyDir: {}
name: adid-log
所以在广告的 TKE 条带化使用过程中,我们会去通过 label 的方式来指定机房选择,腾讯云对各个机房的 CVM 都默认打了 label,可以直接调用。
存量的 workload 也可以修改 yaml 来进行强制的调度。
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- "370004"
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: k8s-app
operator: In
values:
- proxy-tj
topologyKey: kubernetes.io/hostname
weight: 100
在容器化的使用过程中,有两种方式可以面对业务流量的突增。
注:但这里的超卖并不是万能的,他有两个问题也非常明显:
大部分的业务都是cpu的性能为瓶颈,所以通用方式可以针对cpu的request使用率来设置扩容
广告对于每个流量都存在一个站点集的概念,每个站点集分了不同的 set,为了区分开各个流量之间的影响和不同的耗时要求。在20年我们对每个模块都拆出了一个 set 进行了 CVM 的上云,在此基础上,21年我们针对核心模块 sunfish 进行了容器化的上云。这个模块的特点就是 CPU 高度密集型的检索,所以他无法使用超线程(超线程的调度会导致耗时增加),并且内部的程序都进行了绑核处理(减少多进程之间的 CPU 调度)。
这里是广告最大的一个特性,而且也是 TKE 和 CVM/物理机的最大区别。
在 CVM/物理机的场景中,虚拟化技术是可以从 /proc/cpuinfo 中获取到正确的 cpu 单核信息,所以在原先的业务绑核过程中,都是从 /proc/cpuinfo 中获取 cpu 的核数和信息,进行每个程序的绑核操作。
但在容器中 cpu 信息产生了很大的偏差,原因是 /proc/cpuinfo 是根据容器自身的核数进行的排序,但这个顺序并不是该容器在母机上的真实cpu序列,真实的 cpu 序列需要从 /sys/fs/cgroup/cpuset/cpuset.cpus 中获取,例如下图两个举例:
/proc/cpuinfo 中的 CPU 序号展示(虚假):
/sys/fs/cgroup/cpuset/cpuset.cpus 中的 CPU 序号展示(真实):
从上面两张图中可以看到,/proc/cpuinfo 中只是按照分配到的 cpu 核数进行了一个排序,但并不是真正对应母机的核数序列,这样在绑核的过程中,如果绑定了15号核,其实是对母机的15号核进行绑定,但母机的第15个CPU并不是分配给了该容器。
所以需要从 /sys/fs/cgroup/cpuset/cpuset.cpus 中获取在母机中真正对应的 cpu 序列,才能实现绑核,如上图2。并且可以通过在启动脚本中加入下面的命令,可以实现对 cpu 真实核数的格式转换,方便绑定。
cpuset_cpus=$(cat /sys/fs/cgroup/cpuset/cpuset.cpus)
cpu_info=$(echo ${cpuset_cpus} | tr "," "\n")
for cpu_core in ${cpu_info};do
echo ${cpu_core} | grep "-" > /dev/null 2>&1
if [ $? -eq 0 ];then
first_cpu=$(echo ${cpu_core} | awk -F"-" '{print $1}')
last_cpu=$(echo ${cpu_core} | awk -F"-" '{print $2}')
cpu_modify=$(seq -s "," ${first_cpu} ${last_cpu})
cpuset_cpus=$(echo ${cpuset_cpus} | sed "s/${first_cpu}-${last_cpu}/${cpu_modify}/g")
fi
done
echo "export cpuset_cpus=${cpuset_cpus}" >> /etc/profile
source /etc/profile 调用环境变量,转换后的格式如下:
注意:绑核依赖 qos 配置(也就是 request 和 limit 必须设置成一致)
超线程在大部分场景下都是打开的,但在计算密集型的场景下需要关闭,此处的解决方法是在申请CVM的时候就选择关闭超线程。
然后对关核的母机做污点并打上 label,让普通的拉取不会拉到关核母机,在需要分配关核资源的时候,在 yaml 中打开容忍和设置 label,就可以获取到相应的关核资源。
yunti 资源申请时的关核配置:
无状态容器的升级最为简单,业务端口的可用即为容器的可用。
但有状态业务的启动较为复杂,需要在启动脚本中完成状态的前期准备工作。在广告这里主要涉及在广告订单资源的推送和加载。
容器的升级较于物理机最大的区别就在于容器会销毁原有的容器,然后从新的镜像中拉起新的容器提供服务,原有容器的磁盘、进程、资源都会被销毁。
但广告这里的广告订单资源都是百万级别,文件如果在每次升级都需要重新拉取,会直接导致启动过慢,所以我们在容器中都加入了临时挂在目录。
<img src="https://main.qcloudimg.com/raw/a4579ab4826d06e19e688e283ed2fee3.png" style="zoom:67%;" />
<img src="https://main.qcloudimg.com/raw/6865d1b811ed0d3060083b65d22a5ee6.png" style="zoom:67%;" />
这样的挂载方式,可以让容器在升级过程中保留上述目录下的文件,不需要重新拉取。但 emptyDir 只能在升级场景下保留,销毁重建仍旧会销毁后重新拉取,以下为存量服务直接修改 yaml 的方法:
volumeMounts:
- mountPath: /data/example/
name: example-bf
volumes:
- emptyDir: {}
name: example-bf
在业务迭代的过程中,其实有两个问题会导致业务提供了有损服务。
所以我们这里的一个最主要的思路就是:
这里我们引入业务的两个升级的概念:
后置脚本
1 )探针就绪
需要在workload创建的时候,选择针对端口进行做就绪探测,这样在业务端口启动后才会投入到关联好的负载均衡里。
<img src="https://main.qcloudimg.com/raw/15cdd81315a26cab80a6fb26eefe8700.png" style="zoom:67%;" />
也可以在存量的workload中修改yaml
readinessProbe:
failureThreshold: 1
periodSeconds: 3
successThreshold: 1
tcpSocket:
port: 8080
timeoutSeconds: 2
出现类似的unhealty,就是在容器启动后,等待业务端口的可用的过程
2 )后置脚本
后置脚本的核心功能就是在从关联的负载均衡中剔除后,到销毁容器之间,可以执行一系列业务自定义的动作。
执行顺序是:提交销毁重建/升级/缩容的操作 → 剔除北极星/L5/CLB/service → 执行后置脚本 → 销毁容器
最简单的一个功能就是在上游使用L5调用的时候,在剔除L5后sleep 60s,可以让上游更新到该pods剔除后再进行销毁操作。
lifecycle:
preStop:
exec:
command:
- sleep
- "60"
lifecycle:
preStop:
exec:
command:
- /data/scripts/stop.sh
长期的使用经验来看,主要问题在L5这方面,如果是大流量的服务,那sleep 60s 内就行,如果是请求量较小的,希望一个报错都没的,需要 sleep 90s。
这里在相同配置下,对比了普通机器 CVM 和 TKE 容器之间的 CPU 和耗时,可以看到基本没太大差异,耗时也无变化。
CVM:
TKE:
不同于其他从底层介绍云原生的分享,本文主要从业务角度,来介绍云原生在大型线上服务中的优势和使用方法,并结合腾讯广告自有的特点及策略,来实现腾讯广告在高并发、自动化等场景下的容器化实践。
对于业务团队来说,任何的工作都是从质量、效率以及成本出发,而云原生则是能从这三个方面都有所提升,希望未来能有更多来自于我们腾讯广告的分享。
容器服务(Tencent Kubernetes Engine,TKE)是腾讯云提供的基于 Kubernetes,一站式云原生 PaaS 服务平台。为用户提供集成了容器集群调度、Helm 应用编排、Docker 镜像管理、Istio服务治理、自动化DevOps以及全套监控运维体系的企业级服务。
【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!
data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,可以...
解决方法如下: 第一种 使用iframe,但是目前使用iframe的人已经越来越少了,而...
1.HTML5的内容类型 内容类型 描述 内嵌 向文档中添加其他类型的内容,例如audio...
John Au-Yeung 来源:medium 译者:前端小智 有梦想,有干货,微信搜索 【大迁世...
注释1:上图整个大背景是这个网页的全部尺寸,中间的小框才是浏览器中的可见尺寸...
简介: 企业上云多账号架构中,如何做到从上到下管理的同时,处理好员工的权限边...
复制代码 代码如下: !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional...
content属性一般用于::before、::after伪元素中,用于呈现伪元素的内容。平时con...
先点赞再看,养成好习惯 前言 这两天在另一个社区看到了一个关于 Tomcat 的提问...
Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以...