字節跳動下一代通用高性能 OneAgent
一、為什么需要 OneAgent?
字節跳動擁有海量的主機和微服務實例,涵蓋物理機、虛擬機、容器等多種場景,每秒產生的千億級別的可觀測性數據,在可觀測性數據采集和管道化處理等方面給技術團隊帶來了巨大挑戰:
- 字節跳動內部存在多套可觀測系統和多個數據采集組件,一臺機器上往往需要部署多個遙測采集 Agent,造成了資源的浪費。這些采集組件來自不同的團隊,有些已經年久失修無人維護,運維和排障非常困難;
- 內外一體逐漸成為集團業務開發的一個重點,很多業務使用開源 SDK,也想統一接入公司的可觀測性平臺,采集組件側存在大量的適配工作;
- 字節跳動內場多年打磨的 M.T.L 引擎也將逐步上云產品化,我們需要保證內外統一的觀測接入及產品體驗。
字節可觀測性埋點現狀分析
為了解決上述問題,技術團隊決定構建 OneAgent 組件,提供 Logs、Metrics、Trace 和 Event 采集解決方案。
OneAgent 是字節可觀測團隊提供的新一代可觀測性數據[M.T.L.E]采集和處理管道(DataPipeline),目標是讓 OneAgent 成為集團和云上的首選可觀測性基礎設施,簡化在云環境中接入可觀測性系統的復雜度,同時讓用戶可以輕松地從可觀測性數據中獲取更多價值。
OneAgent 中的 One 指的是統一和復用,我們希望它作為一款可觀測數據采集、預處理 Agent,能在系統中發揮以下作用:
- 數據采集統一:Metrics、Log、Trace、Event 能夠使用同一個 agent 實現采集
- Client、Server 端復用:可以支持部署在 Client 端采集,也可以部署在 Server 側作為 Collector Proxy
- 內外場復用:內部和商業化版本的采集標準鏈路上,能夠使用同一個 Agent 作為采集器
通過調研了社區內現有的開源解決方案,我們最終決定和 iLogtail 社區達成了共建合作,采用開源 iLogtail 作為 OneAgent 的底座,同時將通用能力直接以開源的工作方式貢獻回社區。
二、OneAgent 架構
基于開源項目的框架,字節跳動云原生可觀測團隊打造的 OneAgent 本身分為 C++ 編寫的 core 部分,和以 Go 為主的插件系統部分。
Core 是 iLogtail 的主體,主要負責 OneAgent 自身狀態的管理,包括配置的加載和監聽、流水線的啟停、保存 CheckPoint、自監控、告警等,也包含了日志文件采集相關的邏輯。Go 插件系統則對接了多種可觀測性生態,包括 Prometheus、OpenTelemetry、SkyWalking 等生態的諸多插件,大大拓寬了 iLogtail 的應用場景。
以語言為界,iLogtail 包含了 C++ 和 Go 兩種流水線,兩者之間可以通過 cgo 的方式來交互。
- C++ 流水線起源于日志文件采集,在 iLogtail 1.0 階段是固定的一條流水線,不支持編排,自由度較低。后來在 iLogtail 2.0 階段放開了 C++ 流水線做了插件化改造,現在已經支持編排能力,目前的 C++ 流水線處在高速迭代發展階段,不過總體插件生態仍然弱于 Go pipeline,可以通過調用 Go 插件增強處理數據的能力。
- Go 的 pipeline 的生態更加豐富,對接了多種開源協議。Go Pipeline 既能獨立運行,也能借由 Core 拉起。在 iLogtail 1.0 階段,Go 流水線只能調用 C++ 寫的一些 Processor 來加速處理數據。隨著 iLogtail 2.0 將兩條流水線的打通,數據只要進入任意一條流水線,都能在兩條流水線中自由流動。
云原生可觀測團隊目前使用 OneAgent 的 Go 流水線來采集和處理 Metrics 和 Traces 數據,使用 C++ 流水線搭配 Go Flusher 插件做 Logs 文件采集。
三、基于業務場景的開源改造
在實際開發過程中,由于字節跳動擁有龐大的業務規模和復雜的業務場景,單純的開源版本并不能完全滿足現實需要。為此,我們對核心的數據模型、流水線、編排調度、構建體系都做了深度改造。
原生的 Event 數據模型
社區版本的 pipeline 是以日志作為底座的,使用 SLS-PB 數據模型。雖然也可以處理 Metrics、Traces 數據,但是都得先轉化成日志,這往往會造成性能額外開銷及兼容性問題。以下述 metric 為例,metric 的字段使用特殊約定的 key 存儲,其中 metrics 的多個 labels 以固定格式拼接到一個字段中,value 則通過 string 格式存儲。
{
"__name__":"net_out_pkt",
"__labels__":"cluster#$#ilogtail-test-cluster|hostname#$#master-1-1.c-ca9717110efa1b40|hostname#$#test-1|interface#$#eth0|ip#$#10.1.37.31",
"__time_nano__":"1680079323040664058",
"__value__":"32.764761658490045",
"__time__":"1680079323"
}
這帶來一些問題:
- 對 label 進行操作就會引入反序列化開銷;
- value 運算前需要做轉換;
- value 不支持多值,傳輸效率低;
- 對一些社區比較流行的數據協議,例如 Prometheus,OpenTelemetry 等的兼容性不夠好。
為了解決上述問題,我們開發了基于新數據模型的 2.0 pipeline。2.0 Pipeline 中流動的每一條數據定義為 PipelineEvent,Metric、Trace、Log、Bytes,Profiling 等都是派生自 PipelineEvent 的具體事件,大幅提升了靈活性:
type PipelineEvent interface {
GetName() string
SetName(string)
GetTags() Tags
GetType() EventType
GetTimestamp() uint64
GetObservedTimestamp() uint64
SetObservedTimestamp(uint64)
}
PipelineEvent 不但可以攜帶更豐富的信息,也更適合做 data pipeline 內的計算模型,大大提升了通用處理能力。
目前,我們已經把這項改造貢獻給社區,社區也已經接受這個新的數據模型為核心處理模型,后續新的插件都將支持新的數據模型,存量的插件也在逐漸適配支持。
全新的構建方案
社區在 v1.4.0 之前要求插件必須放在項目主倉庫內,但互聯網時代,代碼是企業最核心的資產之一,受限于字節跳動內部的安全合規要求,一些插件必須放在公司內部的私有倉庫,不能貢獻到開源社區。一般面對這種情況,我們只能 fork 一個新的倉庫來增加一些內部專用的插件,但這會導致我們和社區上游漸行漸遠,既也不能享受開源項目的新特性,也不利于把最終用戶的反饋反哺社區。
針對這個問題,我們設計了一種產物構建機制,能夠合并官方倉庫和私有倉庫的插件,一起構建出包。在方案上,我們重點參考了 OpenTelemetry Collector 的做法,使用 YAML 文件渲染成最終的 Go 文件。我們對倉庫做了如下改造:
v1.4.0 之前,插件的引入都放在 plugins/all/all.go 下:
package all
import (
_ "github.com/alibaba/ilogtail/plugins/aggregator"
_ "github.com/alibaba/ilogtail/plugins/aggregator/baseagg"
...
)
v1.4.0 之后,項目主倉庫添加 plugins.yml 文件,內置插件都加到這個文件里面進行注冊,默認使用 plugins.yml 來生成內置插件的 all.go 。同時編譯腳本支持傳遞多個 *_plugins.yml , 使用 external_plugins.yml 來加載外部倉庫的插件,生成 external_all.go 文件。
plugin.yml:
plugins:
common:
- import: "github.com/alibaba/ilogtail/plugins/aggregator"
- import: "github.com/alibaba/ilogtail/plugins/aggregator/baseagg"
...
external_plugins.yml:
plugins: // 需要注冊的plugins,按適用的系統分類
common:
- gomod: code.private.org/private/custom_plugins v1.0.0 // 必須,插件module
import: code.private.org/private/custom_plugins // 可選,代碼中import的package路徑
path: ../custom_plugins // 可選,replace 本地路徑,用于調試
windows:
linux:
project:
replaces: // 可選,array,用于解決多個插件module之間依賴沖突時的問題
go_envs: // 可選,map,插件的repo是私有的時候,可以添加如GOPRIVATE環境等設置
GOPRIVATE: *.code.org
git_configs: // 可選,map,私有插件repo可能需要認證,可以通過設置git url insteadof調整
url.https://user:token@github.com/user/.insteadof: https://github.com/user/
有了這個能力之后,各個公司能更自由地開發自己場景的插件,大大降低了開發門檻,大幅提升了開源項目的共建體驗。
字節跳動的插件配置(脫敏)
插件開發
基于上述這兩個特性,我們開發一系列 OneAgent 插件運用在不同場景,很好地支撐了業務的需求:
輸入插件:
- Unix Domain Socket Input
- Prometheus Service Input V2
- OpenTelemetry Input
- HTTP Server Input V2
- Metrics TCP Server Input
- TTLogAgent Input
處理插件:
- Prometheus Metric Validator
- Telegraf Metric Filter
聚合插件:
- Metric Tag Aggregator
輸出插件:
- HTTP Flusher
- OTLP Flusher
- TTLogAgent Flusher
拓展插件:
- Metric Event Filter
- Byted Metrics Decoder
- Status Code Request Breaker
上述插件中的部分也已經貢獻給開源社區。
四、OneAgent 案例分享
本節將會介紹 3 個字節跳動使用 OneAgent 的案例。
存儲底座替換 Telegraf
字節跳動內部某業務的存儲業務主要使用 BytedMetrics 1.0 SDK 進行打點,使用 Metrics Agent(ms2)和 Telegraf 作為指標采集的 Agent。這條采集鏈路過長并且資源消耗過高,并且產品側指標存在采集延遲、指標丟點的問題。部分業務單個實例每分鐘打點可達百萬級,采集組件至多可以占用 16 核心,卻仍然受限于性能問題,無法實時地上報監控指標,從而造成了產品側指標采集延遲、丟點的問題。
ms2 是字節跳動自研的高性能指標采集 Agent,用 C++ 實現,主要用來聚合,清洗,降級數據以及投遞數據,以 Daemonset 的形式部署在各個集群,終態會和 OneAgent 融合。詳細介紹見文章:字節跳動百萬級 Metrics Agent 性能優化的探索與實踐
BytedMetrics SDK 1.0 是字節自研的第一代打點 SDK,依賴 ms2 做數據聚合。
BytedMetrics SDK 2.0 是字節自研的第二代高性能打點 SDK,具有數據聚合能力,性能領先 Prometheus SDK 2-5 倍。
Telegraf 是 InfluxData 公司開發的一款開源指標采集 Agent,用于主機指標和業務指標的采集,現在逐步被 OneAgent 替換。
為了解決上述問題,我們使用 OneAgent 作為新的指標采集 Agent,現階段已經使用 OneAgent 平替 Telegraf,降低了采集 Agent 80%的 CPU 占用,解決了業務的監控丟點和延遲問題。
OneAgent上線效果
該業務用戶的打點方式多種多樣,涵蓋了 BytedMetrics 1.0 SDK、BytedMetrics 2.0 SDK、InfluxDB SDK、Prometheus Exporter、OpenTelemetry SDK 等。過去需要部署多個 Agent 進行采集,這不僅導致了資源的浪費,還增加了運維成本。如今,OneAgent 已經能夠接收除 BytedMetrics 1.0 SDK 以外的所有數據,成功收斂了數個采集 Agent。
隨著業務逐漸接入 BytedMetrics 2.0 SDK,以及我們下一個階段對 C++流水線的改造,最終 OneAgent 將取代 ms2,成為該業務唯一的可觀測數據采集 Agent,從而進一步節省資源,這也是我們的最終目標。
日志文件采集
字節跳動內部主流是 Go 微服務,主要使用侵入式的 BytedLogs SDK 來記錄日志,SDK 通過 Unix Domain Socket 上報給本機的 TTLogAgent,可以實現無盤化采集日志。
TTLogAgent 是字節自研的日志采集 Agent,用 Go 實現,主要用來接收 Log SDK 上報的數據并發送到后端,同時也支持日志文件采集,日志降級,錯誤日志分流等功能,以 daemonset 的形式部署在各個集群,最終會被 OneAgent 替換。
Logs SDK 是字節自研的高性能日志 SDK,采用鏈式 API,性能屬于業內第一梯隊。
這也帶來一些問題:
- 如果進程發生不可恢復的錯誤,崩潰信息無法及時上報,這部分 stdout/stderr 輸出會重定向到一個文件中,當前依賴一個腳本工具上報 panic 日志。
- 隨著公司上云和內外一體策略,一些業務使用了開源組件打日志,無法接入字節跳動內部的日志平臺,需要使用文件采集方案。
TTLogAgent 文件采集能力較弱,對日志的處理通用性也不夠,不支持正則處理多行日志,無法開放給所有用戶使用。在對比 TTLogAgent 與 iLogtail 的優劣之后,我們決定復用 OneAgent 的這部分能力,將 TTLogAgent 改造成 OneAgent 的一個插件。
事件驅動的文件采集
OneAgent 采用了輪詢(polling)與事件(inotify)并存的模式進行日志采集,既借助了 inotify 的低延遲與低性能消耗的特點,也通過輪詢的方式兼顧了運行環境的全面性,可以實現毫秒級的日志采集的延遲控制,很好地應對字節跳動當前的日志壓力。
改造后 TTLogAgent 變成了 OneAgent 的一個插件:
后續我們會把 TTLogAgent 的功能逐步遷移到 OneAgent,最終下線 TTLogAgent。
對接開源生態
OneAgent 不但可以作為采集 Agent,也可以部署成服務作為 Proxy 或 Gateway。字節跳動內部部署了多個 OneAgent 服務,幫助用戶接入不同的可觀測性平臺,實現內外一體的觀測體驗。
- 有團隊部署了 OneAgent 服務作為 Prometheus Remote Write 的后端,將指標寫入公司自研時序數據庫 ByteTSD。
- 有團隊使用 InfluxDB SDK 通過 OneAgent 接入 ByteTSD 和 InfluxDB。
- 有團隊使用 OpenTemetry SDK 通過 OneAgent 寫入內場可觀測性平臺。
五、未來展望
根據后續規劃,未來我們會持續增強 OneAgent 能力,提升其穩定性和易用性。同時我們也會深度參與開源社區共建,和廣大開發者共同打造領先的可觀測數據采集器:
- 實現 OneAgent 多 pipeline 的連接,增強數據處理能力。
- 擴展增強 C++ 的 pipeline,更高性能地處理 Metrics 和 Trace 數據,在一些計算密集型場景,Go 插件的處理能力還無法和 C++ 組件對齊。
- 加快 OneAgent 運維控制面建設。
- 收斂目前字節內部的 Agent 到 OneAgent,首先考慮融合 Telegraf、ms2 及 TTLogagent。
六、引用
字節跳動百萬級 Metrics Agent 性能優化的探索與實踐:https://mp.weixin.qq.com/s/bl1HbC6ti6Pw2FGxgstfBw
節約資源、提升性能,字節跳動超大規模 Metrics 數據采集的優化之道:https://mp.weixin.qq.com/s/spHNCBWfgOCHSomLvp5aWA
OpenTelemetry Collector Connector: https://github.com/open-telemetry/opentelemetry-collector/blob/main/connector/README.md
云原生可觀測團隊
字節跳動云原生可觀測(Cloud Native-Observability)團隊提供日均數十 PB 級可觀測性數據采集、存儲和查詢分析的引擎底座,致力于為業務、業務中臺、基礎架構建設完整統一的可觀測性技術支撐能力。同時,團隊也正通過火山引擎持續對外輸出云上可觀測技術能力。