总结并分享后续的迭代和演进计划。
可观测性建设耗时长我们认为应用开发团队花了一半的时间用于可观测性的建设。这张图里面可以看到,开发者通常需要考虑在不同的 Dev Stack 和 Infra Stack 中如何埋点、如何插码、如何传递追踪上下文、如何生成指标/追踪/日志数据并进行关联,需要考虑的问题太多太杂。除此之外开发者还有很多时间在做 Debug,而这些 Debug 之所以耗费了这么多时间,通常大部分是因为可观测性建设的欠缺导致。
可观测性建设数据关联难可观测性指标数据一般分为Tracing、Metric和Logging三类。Logging关注的元数据是type/level/time/message/...。
DeepFlow软件架构DeepFlow的架构其实非常简单,它简单到只有一个Agent和一个Server,分别是数据采集组件和数据存储查询组件。Agent是使用Rust来实现的,高性能且内存安全,它通过eBPF技术实现了对任意开发技术栈、任意基础设施的全自动应用性能指标数据采集(AutoMetrics),以及自动化的分布式链路追踪(AutoTracing),这两项是DeepFlow Agent独有的能力,能极大降低开发者建设可观测性的工作量。Server包含了4个内部模块:Controller面向采集器Agent的管理,能纳管多资源池的10万量级的Agent;Labeler面向标签数据的自动注入,提供AutoTagging的能力;Querier面向数据查询,提供统一的SQL接口;Ingester面向数据存储,提供插件化的、可替换可组合的数据库接口。它支持水平扩展,而且完全不依赖外部的消息队列或负载均衡,就能够去实现对多个Region、多个资源池中Agent的负载均摊。Server也有两个非常核心的技术,AutoTagging和SmartEncoding。通过AutoTagging我们能为Agent采集到的所有观测数据自动注入统一的资源、实例和API标签,使得我们能够消除不同数据类型之间的隔阂,增强所有数据的关联、切分、下钻能力。SmartEncoding是我们非常创新的一个高性能的标签编码机制,通过这个机制,我们既能方便的进行数据关联,又能将标签注入的存储性能提升10倍,这在我们的实际生产环境中已经进行了广泛的验证。AutoTagging通过云API、K8s apiserver自动同步30多种资源标签、100多种自定义微服务标签,来构建标准化的标签体系。
DeepFlow资源同步DeepFlow的标签体系:
同步K8s资源的数据流同步云资源同步云资源信息:通过调用云平台 API 进行资源抽象和转换,然后将相关标签信息保存至 MySQL 中,并定期更新 ClickHosue 中的字典。
同步 Legacy Host 信息:一些环境中,可能没有真正意义的云平台,或者存在一些传统主机需要监控,这就需要用Legacy Host同步方案。由于没有具体的云API,我们完全通过Agent抓取所在服务器的名称等基本信息和网卡信息,上报给Server汇总并进行资源抽象。
同步托管 K8s 信息:当 K8s 平台部署在云资源上时,要做到真正的可观测性,需要将K8s的资源和云资源关联起来,才能真正做到无缝地关联、切分和下钻。我们一方面通过获取 K8s 资源所在的 VPC,基于 VPC 内 IP 的唯一性,通过 VPC + IP 将 K8s 的容器节点与云服务器关联起来;另一方面通过将云平台的 API 调用与 K8s 独立,两者使用不同的调用频率,从而解决大规模场景下,云平台 API 慢与 K8s 资源更新快的矛盾。
同步云资源的数据流理想很丰满,现实很骨感。我们努力想实现观测数据无缝跳转,但当上百个标签呈现在眼前时,你会发现后端资源消耗飙升,性能急剧下降,整个平台别说无缝跳转了,连使用都成了问题。于是 SmartEncoding 技术诞生了。
采集时编码Controller 根据云平台和 K8s 资源抽象好标签信息进行 Int 编码后,并不会将所有的标签下发给 Agent。仅会下发最少量的标签。这样 Agent 只需要为数据追加很少的Int标签即可。在混合云场景下,为了标识资源我们可以用 VPC ID 作为基,它能和 IP 地址联合决定客户端、服务端对应的实例和服务;可以通过 gpid 解决远端进程信息标记的问题。我们主要考虑 Agent 做的工作尽量少,这样可以最大限度的降低采集器的 CPU、内存消耗,以及传输数据的带宽消耗。我们在生产环境中发现有些 K8s 的标签会非常长,key 和 value 高达上百个字节。可以想象如果我们将上百个标签注入每个请求传输到后端,消耗的带宽会非常可观。存储时编码
存储时编码同样 Controller 会向 Ingester 下发 Int 标签,但仅下发持久化存储的标签。Ingester 在收到 Agent 发过来的数据后,会进行一轮标签的扩充,将 Agent 注入的少量标签扩展为更为丰富的标签集合。但这里注意的是,我们并不存储自定义标签。标签的存储是为了方便检索和聚合,我们只需要保证每个切分粒度上都有标签存在即可。举例来讲我们存储 Region、AZ、VM、Node、Namespace、Service、POD 等固定的云或者 K8s 资源标签即可,而其他的自定义的标签一般是依附在这些标签之上的,存在一定的对应的关系。另外,自定义标签动态性高,也不适合全部存储。根据我们的经验,一般每一个请求涉及到的的固定标签在40个左右,自定义标签在60个左右。通过只存储固定的资源标签,我们能将压力进一步降低。查询时编/解码
查询时编解码DeepFlow SQL支持通过字符串查询和聚合,并且也支持自定义标签的查询和聚合。这里我们依赖 ClickHouse 的字典能力。通过编码自定义标签的 Filter 和 Group 查询请求,利用 ClickHouse 的字典转换为系统标签;同时对于 Select 请求也可以利用 ClickHouse 的字典将系统标签转为字符串或者自定义标签返回。我们再来回顾一下这三级编解码,可以发现它能为我们节省大量的资源消耗,性能提升应该十分可观。一方面采集器的CPU、内存可以降低,传输带宽可以降低,最主要的还是后端存储开销的降低。我们在谈论可观测性时经常会谈到采样、避免高基数等。ClickHouse 采用稀疏索引,很好的避免了高基数问题。我们在此之上的多级编解码又能将存储开销显著降低,而且由于查询阶段扫描的数据量变小了,所以能获得更好的查询性能。这里有一些数据可以看一下,DeepFlow 默认使用 ClickHouse 存储数据,在 SmartEncoding 的加持下,标准 Tag 的 CPU 和磁盘消耗相比 LowCard 存储或直接存储有一个数量级的优化,而由于自定义 Tag 不会随数据写入,在通常的场景下整体写入资源消耗可降低50倍。



SELECT col_1, col_2, col_3 FROM tbl_1 WHERE col_4 = y GROUP BY col_1, col_2 HAVING col_5 > 100 ORDER BY col_3 LIMIT 100 ``` 我们可以查询某个 Tag 的所有候选项: ```sql SHOW tag ${tag_name} values FROM ${table_name}
SHOW tag ${tag_name} values FROM ${table_name} WHERE display_name LIKE '*abc*'
SELECT pod FROM `vtap_flow_port.1m` WHERE pod_cluster = 'cluster1' GROUP BY pod 更多详细用法[4]查询 Universal Tag
CREATE TABLE flow_metrics.`vtap_flow_port.1m` ( `time` DateTime('Asia/Shanghai') COMMENT 'v6.1.8' CODEC(DoubleDelta), `ip4` IPv4 COMMENT 'IPv4地址', `ip6` IPv6 COMMENT 'IPV6地址', `is_ipv4` UInt8 COMMENT '是否IPV4地址. 0: 否, ip6字段有效, 1: 是, ip4字段有效', `l3_device_id` UInt32 COMMENT 'ip对应的资源ID', `l3_device_type` UInt8 COMMENT 'ip对应的资源类型', `l3_epc_id` Int32 COMMENT 'ip对应的EPC ID', `pod_cluster_id` UInt16 COMMENT 'ip对应的容器集群ID', `pod_group_id` UInt32 COMMENT 'ip对应的容器工作负载ID', `pod_id` UInt32 COMMENT 'ip对应的容器POD ID', `pod_node_id` UInt32 COMMENT 'ip对应的容器节点ID', `pod_ns_id` UInt16 COMMENT 'ip对应的容器命名空间ID' ) ENGINE = Distributed(...)
CREATE DICTIONARY flow_tag.pod_map ( `id` UInt64, `name` String, `icon_id` Int64 ) PRIMARY KEY id SOURCE(...)
SELECT dictGet(flow_tag.pod_map, 'name', toUInt64(pod_id)) AS pod FROM `vtap_flow_port.1m` WHERE pod = 'deepflow' GROUP BY pod LIMIT 1 查询 K8s label
CREATE DICTIONARY flow_tag.k8s_label_map ( `pod_id` UInt64, `key` String, `value` String, ) PRIMARY KEY pod_id, key SOURCE(...) LIFETIME(MIN 0 MAX 60) LAYOUT(COMPLEX_KEY_HASHED())
SELECT dictGet(flow_tag.k8s_label_map, 'value', (toUInt64(pod_id), 'app')) AS `label.app` FROM `vtap_flow_port.1m` WHERE `label.app` = 'xxx' LIMIT 1 查询集成数据,包括 Prometheus、Telegraf、OpenTelemetry 等数据。
CREATE TABLE ext_metrics.prometheus_web ( `time` DateTime('Asia/Shanghai') CODEC(DoubleDelta), `_tid` UInt8 COMMENT '用于区分trident不同的pipeline', `az_id` UInt16 COMMENT '可用区ID', `host_id` UInt16 COMMENT '宿主机ID', `tag_names` Array(String) COMMENT '额外的tag', `tag_values` Array(String) COMMENT '额外的tag对应的值', `metrics_float_names` Array(String) COMMENT '额外的metrics', `metrics_float_values` Array(Float64) COMMENT '额外的metrics值' )
CREATE TABLE flow_tag.ext_metrics_custom_field_local ( `time` DateTime('Asia/Shanghai') CODEC(DoubleDelta), `table` LowCardinality(String), `vpc_id` Int32, `pod_ns_id` UInt16, `field_type` LowCardinality(String) COMMENT 'value: tag, metrics', `field_name` LowCardinality(String), `field_value_type` LowCardinality(String) COMMENT 'value: string, float' ) ENGINE = ReplacingMergeTree(time)
SELECT tag_values[indexOf(tag_names, 'host')] AS `tag.host` FROM deepflow_agent_collect_sender WHERE (tag_values[indexOf(tag_names, 'host')]) = 'xxxx' LIMIT 1
全部0条评论
快来发表一下你的评论吧 !