案例 | 騰訊廣告 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. 廣告資源在容器升級過程中的持續可用

容器的升級較於物理機最大的區別就在於容器會銷毀原有的容器,然后從新的鏡像中拉起新的容器提供服務,原有容器的磁盤、進程、資源都會被銷毀。

但廣告這里的廣告訂單資源都是百萬級別,文件如果在每次升級都需要重新拉取,會直接導致啟動過慢,所以我們在容器中都加入了臨時掛在目錄。

這樣的掛載方式,可以讓容器在升級過程中保留上述目錄下的文件,不需要重新拉取。但 emptyDir 只能在升級場景下保留,銷毀重建仍舊會銷毀后重新拉取,以下為存量服務直接修改 yaml 的方法:

              volumeMounts:
        - mountPath: /data/example/
          name: example-bf
      volumes:
      - emptyDir: {}
        name: example-bf

2. 升級過程中的業務高可用

在業務迭代的過程中,其實有兩個問題會導致業務提供了有損服務。

  • 如果針對 workload 關聯了負載均衡,這里容器在執行啟動腳本的第一句話就會變成 running 可用狀態,這時候會幫大家投入到關聯過的負載均衡中,但這時候業務進程並未就緒,尤其是有狀態服務必須得完成前置的狀態后才可以啟動。這時候業務就會由於加入了不可用的服務導致業務報錯。

  • 在升級的過程中,除了 deployment 模式的升級外,其他都是先銷毀原有的容器,再拉取新的容器服務。這時候就產生了一個問題就是,我們在升級的時候是先從關聯過的負載均衡里剔除,然后立馬進入到銷毀階段。如果上游是L5調用那其實並不能快速同步到該 pods 已經剔除,會繼續往下游已經銷毀的容器中發送請求,這時候整個業務就會報錯。

所以我們這里的一個最主要的思路就是:

  • 如何把業務的狀態,和容器狀態進行綁定。
  • 在升級/銷毀重建的過程中,是否可以做一個后置腳本,在銷毀之前我們可以做一些邏輯處理,最簡單的就是sleep一段時間。

這里我們引入業務的兩個升級的概念:

  • 探針就緒

  • 后置腳本

    1 )探針就緒
    需要在workload創建的時候,選擇針對端口進行做就緒探測,這樣在業務端口啟動后才會投入到關聯好的負載均衡里。

    也可以在存量的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以及全套監控運維體系的企業級服務。
【騰訊雲原生】雲說新品、雲研新術、雲游新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM