乐趣区

关于腾讯云:案例-腾讯广告-AMS-的容器化之路

作者

张煜,15 年退出腾讯并从事腾讯广告保护工作。20 年开始疏导腾讯广告技术团队接入公司的 TKEx-teg,从业务的日常痛点并联合腾讯云原生个性来欠缺腾讯广告自有的容器化解决方案

我的项目背景

腾讯广告承载了整个腾讯的广告流量,并且接入了内部联盟的申请,在所有流量日益增大的场景下,流量突增后如何疾速调配资源甚至主动调度,都成为了广告团队所须要思考的问题。尤其是往年整体广告架构(投放、播放)的条带化容灾优化,对于按需分配资源、按区域分配资源等性能都有着更强的依赖。
在广告外部,播放流零碎承载了整个广告播出的性能,这里的稳定性间接决定了整个腾讯广告的支出,以下为架构图:

业务特点:

  • 申请量大,日均申请量近千亿级别,机器量占了 AMS 自有机器量的 60% 以上,整体性能即便是大量的高低稳定都会波及大量机器的变动。
  • 链路拓扑简单及性能压力极大,整个播放链路波及 40+ 的细分模块。在 100~200 毫秒(不同流量要求有差别)的极短时间内,须要拜访所有模块,计算出一个最好的广告。
  • 计算密集型,大量的应用了绑核和关核能力,来面对上百万个广告订单检索的压力。

上云计划选型

在 20 年腾讯广告曾经在大规模上云,次要应用的是 AMD 的 SA2 cvm 云主机,并且曾经实现了对网络、公司公共组件、广告自有组件等兼容和调试。在此基础上,基于 CVM 的 Node 云原生也开始进行调优和业务应用,弹性伸缩、Docker 化革新,大量应用各种 PAAS 服务,充分发挥云的高级性能。
以下为广告应用的 TKE 架构:

  • 后期资源筹备(左上):从腾讯外部云官网平台申请 CVM 和 CLB 等资源,并且同时云官网申请 master,node,pods 所须要的 subnet 网段(subnet 是辨别区域的,例如深圳光明,须要留神 node 和 pods 在区域上的网段调配,须要统一)。CVM 和 CLB 都导入至 TKEx-teg 中,在抉择 FIP 模式的时候,产生的 PODS 从调配好的 subnet 中获取本人的 EIP。
  • 仓库及镜像的应用(右上):广告运维侧提供根底镜像(mirrors.XXXXX.com/XXXXX/XXXXXXX-base:latest),业务在 from 根底镜像的同时,拉取 git 后通过蓝盾进行镜像 build,实现业务镜像的构建。
  • 容器的应用模式(下半局部):通过 TKEx-teg 平台,拉取业务镜像进行容器的启动,再通过 clb、北极星等服务进行对外应用。

容器化历程(艰难及应答)

艰难一:通用性
(1)面对广告 84 个技术团队,如何实现所有业务的适配
(2)镜像治理:根底环境对于业务团队的透明化
(3)腾讯广告容器配置标准
艰难二:CPU 密集型检索
(1)广告订单数量:百万级
(2)绑核:各个利用之间的 CPU 绑核隔离
(3)关核:敞开超线程
艰难三:有状态服务降级中的高可用
(1)广告资源在容器降级过程中的继续可用
(2)在迭代、销毁重建过程中的继续高可用

通用性

1. 广告根底镜像介绍

广告运维侧提供了一套笼罩大部分利用场景的根底镜像,其中以 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 的启动脚本,在业务镜像启动过程中,能够在各自的启动脚本中抉择是否调用。

2. 容器化 CI/CD

原先大量应用了其余平台的 CD 局部,但当初应用 TKE 后,其余平台曾经无奈应用。而 TKEx-teg 上的继续化集成局部对于自动化流水线实现较弱,需手动参加,所以在广告外部引入的 CI/CD 计划是腾讯外部的继续化集成和继续化部署计划:蓝盾。

这里全程实现流水线公布,除了审核外无需人工参加,缩小人为因素的问题影响。

stage1:次要应用手动触发、git 主动触发、定时触发、近程触发

  • 手动触发:容易了解,须要手动点击后开始流水线。
  • 主动触发:当 git 产生 merge 后,可主动触发该流水,实用于业务的麻利迭代。
  • 定时触发:定时每天某个工夫点开始整个流水线的触发,实用于 oteam 协同开发的大型模块,预约多少工夫内迭代一次,所有参加的人员来确认本次迭代的批改点。
  • 近程触发:依赖内部其余平台的应用,例如广告的评审机制在本人的平台上(Leflow),能够在整个公布评审完结后,近程触发整个流水线的执行。

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:数据统计。

另外有一个蓝盾中机器人群告诉的性能,能够自定义把须要告知的流程信息,推送到某个企业微信群中,以便大家进行确认并审核。

3. 腾讯广告容器配置标准

广告外部的母机都是用的腾讯云星星海 AMD(SA2),这里是 90 核超线程 cpu+192G 内存,磁盘应用的是高速云硬盘 3T,在日常应用中这样的配置这个机型是腾讯云现阶段能提供的最大机型(曾经开始测试 SA3,最高机型配置会更大)。

  • 所以业务在应用的时候,不倡议 pods 核数太大(例如超过 32 核),因为 TKE 亲和性的默认设置会尽量在闲暇的母机中拉取各个容器,这样在应用到中后期(例如集群曾经应用了 2 /3)导致的碎片化问题,会导致超过 32 核的 pods 简直无奈扩容。所以倡议用户在拉取容器的时候如果能够横向扩容,都是把原有的高核服务拆分成更多的低核 pods(单 pods 核数减半,整体 pods 数两倍)。
  • 在创立 workload 的时候,对日志目录进行 emptyDir 长期目录的挂载,这样能够保障在降级过程中该目录不会失落数据,不便后续的问题排查。(销毁重建仍旧会删除该目录下的所有文件)

如果是曾经上线的 workload,则能够通过批改 yaml 来减少目录的挂载:

        - mountPath: /data/log/adid_service
          name: adid-log
      volumes:
      - emptyDir: {}
        name: adid-log
  • 在腾讯广告外部,大量应用了条带化性能,也就是服务不在受限于上海、深圳、天津这样的范畴。条带化能够做到更细化的辨别,例如上海 - 南汇这样以机房为单位的部署,这样能够实现容灾的实现(大部分的网络故障都是以机房为单位,这样能够疾速切到另一路)。也能够实现条带化部署后的耗时缩小,例如同上海的两个机房,因为间隔的起因自带 3ms 的耗时,在大包传输的过程中,跨机房部署的耗时问题会被放大,在广告外部有呈现 10ms 的 gap 呈现。

所以在广告的 TKE 条带化应用过程中,咱们会去通过 label 的形式来指定机房抉择,腾讯云对各个机房的 CVM 都默认打了 label,能够间接调用。

存量的 workload 也能够批改 yaml 来进行强制的调度。

      spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: failure-domain.beta.kubernetes.io/zone
                operator: In
                values:
                - "370004"
  • 广告外部后盾都是倡议应用 4~16 核的容器配置,前端平台大多应用 1 核,这样能够保障在集群使用率偏高的场景下,也能够进行紧急扩容。并且如果心愿亲和性强制隔离各个 pods,也能够应用如下配置进行隔离(values 为具体的 workload 名称):
         affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: k8s-app
                  operator: In
                  values:
                  - proxy-tj
              topologyKey: kubernetes.io/hostname
            weight: 100

4. HPA 设置

在容器化的应用过程中,有两种形式能够面对业务流量的突增。

  • 设置容器的 request 和 limit,request 资源能够了解为确定 100% 能够调配到业务的,而 Limit 则是超卖的资源,是在 buffer 池中共享的资源局部。这样的益处在于每个业务能够配置平时失常应用的 request 资源,当产生流量突增时由 limit 局部的资源来承当超过 request 之后的性能问题。

注:但这里的超卖并不是万能的,他有两个问题也非常明显:

  • 在以后 node 残余应用的资源如果小于 limit 设定的值,会产生 PODS 主动迁徙到其余 node。2)如果须要应用到绑核性能,是须要 qos 的性能,这里强制 request 和 limit 必须设置一样的配置。

  • 设置主动扩容,这里能够依据大家各自的性能瓶颈来设置阈值,最初来实现主动扩容的性能。

大部分的业务都是 cpu 的性能为瓶颈,所以通用形式能够针对 cpu 的 request 使用率来设置扩容

百万广告订单检索

1. 广告外围检索模块

广告对于每个流量都存在一个站点集的概念,每个站点集分了不同的 set,为了辨别开各个流量之间的影响和不同的耗时要求。在 20 年咱们对每个模块都拆出了一个 set 进行了 CVM 的上云,在此基础上,21 年咱们针对外围模块 sunfish 进行了容器化的上云。这个模块的特点就是 CPU 高度密集型的检索,所以他无奈应用超线程(超线程的调度会导致耗时减少),并且外部的程序都进行了绑核解决(缩小多过程之间的 CPU 调度)。

2. 容器绑核

这里是广告最大的一个个性,而且也是 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 必须设置成统一)

3. 敞开超线程

超线程在大部分场景下都是关上的,但在计算密集型的场景下须要敞开,此处的解决办法是在申请 CVM 的时候就抉择敞开超线程。

而后对关核的母机做污点并打上 label,让一般的拉取不会拉到关核母机,在须要调配关核资源的时候,在 yaml 中关上容忍和设置 label,就能够获取到相应的关核资源。

yunti 资源申请时的关核配置:

有状态服务降级中的高可用

无状态容器的降级最为简略,业务端口的可用即为容器的可用。

但有状态业务的启动较为简单,须要在启动脚本中实现状态的后期筹备工作。在广告这里次要波及在广告订单资源的推送和加载。

1. 广告资源在容器降级过程中的继续可用

容器的降级较于物理机最大的区别就在于容器会销毁原有的容器,而后从新的镜像中拉起新的容器提供服务,原有容器的磁盘、过程、资源都会被销毁。

但广告这里的广告订单资源都是百万级别,文件如果在每次降级都须要从新拉取,会间接导致启动过慢,所以咱们在容器中都退出了长期挂在目录。

<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

2. 降级过程中的业务高可用

在业务迭代的过程中,其实有两个问题会导致业务提供了有损服务。

  • 如果针对 workload 关联了负载平衡,这里容器在执行启动脚本的第一句话就会变成 running 可用状态,这时候会帮大家投入到关联过的负载平衡中,但这时候业务过程并未就绪,尤其是有状态服务必须得实现前置的状态后才能够启动。这时候业务就会因为退出了不可用的服务导致业务报错。
  • 在降级的过程中,除了 deployment 模式的降级外,其余都是先销毁原有的容器,再拉取新的容器服务。这时候就产生了一个问题就是,咱们在降级的时候是先从关联过的负载平衡里剔除,而后立马进入到销毁阶段。如果上游是 L5 调用那其实并不能疾速同步到该 pods 曾经剔除,会持续往上游曾经销毁的容器中发送申请,这时候整个业务就会报错。

所以咱们这里的一个最次要的思路就是:

  • 如何把业务的状态,和容器状态进行绑定。
  • 在降级 / 销毁重建的过程中,是否能够做一个后置脚本,在销毁之前咱们能够做一些逻辑解决,最简略的就是 sleep 一段时间。

这里咱们引入业务的两个降级的概念:

  • 探针就绪
  • 后置脚本

    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 的使用率和耗时比照

这里在雷同配置下,比照了一般机器 CVM 和 TKE 容器之间的 CPU 和耗时,能够看到根本没太大差别,耗时也无变动。

CVM:


TKE:

整体收益

结语

不同于其余从底层介绍云原生的分享,本文次要从业务角度,来介绍云原生在大型线上服务中的劣势和应用办法,并联合腾讯广告自有的特点及策略,来实现腾讯广告在高并发、自动化等场景下的容器化实际。
对于业务团队来说,任何的工作都是从品质、效率以及老本登程,而云原生则是能从这三个方面都有所晋升,心愿将来能有更多来自于咱们腾讯广告的分享。


容器服务(Tencent Kubernetes Engine,TKE)是腾讯云提供的基于 Kubernetes,一站式云原生 PaaS 服务平台。为用户提供集成了容器集群调度、Helm 利用编排、Docker 镜像治理、Istio 服务治理、自动化 DevOps 以及全套监控运维体系的企业级服务。
【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!

退出移动版