logo
logo

一图了解如何优化 DeepFlow 存储开销

向阳,李灼锋 2023-12-27

本文介绍如何对 DeepFlow 进行配置以降低 ClickHouse 的存储开销。

0x0: 配置项总览

在介绍具体的配置项之前,我们首先看看 DeepFlow Agent 采集的主要数据类型都有哪些。从 ClickHouse 中数据库、数据表的角度来看,数据主要有如下几类:

  • flow_log.l4_flow_log:流日志。基于 cBPF 流量数据计算出的 TCP/UDP 五元组流日志,每条流日志包含包头五元组字段、标签字段、性能指标,每条流日志平均大约消耗 150 字节存储空间。
  • flow_log.l7_flow_log:调用日志。基于 cBPF 流量数据、eBPF 函数调用数据计算出的 HTTP/gRPC/MySQL 等应用协议的调用日志,每条调用日志包含调用的关键请求/响应字段、标签字段、性能指标,每条调用日志平均大约占用 70 字节存储空间,主要取决于头部字段的长度,各种应用协议存储的头部字段详见在线文档
  • flow_metrics:指标数据。基于流日志和调用日志聚合计算得到的指标数据,默认聚合生成 1m、1s 两种时间精度的指标。由于这些指标数据是聚合得到的,体量不大,一般消耗的存储空间仅仅是流日志或调用日志的 1/10 左右。
  • event.perf_event:性能事件。目前主要存储进程的文件读写事件,每个事件包含进程名、文件名、读写性能指标等字段,每个事件大约占用 80 字节。
  • profile:持续剖析。存储开启了持续剖析功能的进程的函数调用栈,默认仅 deepflow-agent 和 deepflow-server 进程开启 eBPF OnCPU Profile。

DeepFlow 有丰富的配置可以用于降低 ClickHouse 的存储开销,下图中进行了汇总。

可用于降低数据量的配置项可用于降低数据量的配置项

上图中的配置参数根据其用途可以分为四类:

  • 黑色:用于设置数据保存时长。不同类型的数据通常需要设置不同的保存时长。
  • 绿色:用于降低数据精细程度。通过修改配置,可以调整各类数据的精细程度,直接达到降低存储压力的目的。
  • 蓝色:用于关闭不关心的数据。通过修改配置,可以关闭一些自身不关心的功能,或者屏蔽一部分不关心的流量,从而达到降低存储压力的目的。
  • 红色:用于设置过载保护阈值。Agent 和 Server 为了保护自身避免过载,暴露了采集和存储数据速率的阈值配置项,避免采集和写入过量数据。

0x1: 动手之前预估效果

调整配置之前应该在 ClickHouse 中确认对应数据的储存消耗,预先评估配置调整会带来的收益。下面这些 ClickHouse 命令能够帮助你评估特定数据表的存储开销。

查看所有数据表的行数和空间消耗,确认哪些数据表占用了最多的存储空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
WITH sum(bytes_on_disk) AS size SELECT database, table, formatReadableSize(sum(data_uncompressed_bytes)) AS "压缩前总大小", formatReadableSize(sum(bytes_on_disk)) AS "压缩后总大小", sum(rows) AS "总行数", sum(data_uncompressed_bytes)/sum(rows) AS "压缩前平均每行长度", sum(bytes_on_disk)/sum(rows) AS "压缩后平均每行长度" FROM system.parts GROUP BY database, table ORDER BY size DESC;

WITH sum(bytes_on_disk) AS size
SELECT
database,
table,
formatReadableSize(sum(data_uncompressed_bytes)) AS `压缩前总大小`,
formatReadableSize(sum(bytes_on_disk)) AS `压缩后总大小`,
sum(rows) AS `总行数`,
sum(data_uncompressed_bytes) / sum(rows) AS `压缩前平均每行长度`,
sum(bytes_on_disk) / sum(rows) AS `压缩后平均每行长度`
FROM system.parts
GROUP BY
database,
table
ORDER BY size DESC

┌─database────────┬─table────────────────────────────────────┬─压缩前总大小─┬─压缩后总大小─┬────总行数─┬─压缩前平均每行长度─┬──压缩后平均每行长度─┐
│ flow_log │ l7_flow_log_local │ 16.58 GiB │ 3.15 GiB │ 47231817377.0247995540802371.69953315579623
│ flow_log │ l4_flow_log_local │ 4.84 GiB │ 1.51 GiB │ 10487780495.46614078479905154.46138525026268
│ profile │ in_process_local │ 2.24 GiB │ 349.19 MiB │ 10325238233.143725597414835.46215912892274
│ flow_metrics │ vtap_flow_edge_port.1s_local │ 1.95 GiB │ 261.58 MiB │ 5267001398.2078425654371552.07621016210174
│ flow_metrics │ vtap_flow_port.1s_local │ 1.01 GiB │ 159.70 MiB │ 3027870357.8566127343644355.30619544432225
│ flow_metrics │ vtap_app_port.1s_local │ 932.36 MiB │ 129.11 MiB │ 8225431118.8569198379999816.459093876053426
│ deepflow_system │ deepflow_system_local │ 1.55 GiB │ 117.64 MiB │ 1847824089.797162175618456.675736920832287
│ flow_metrics │ vtap_app_edge_port.1s_local │ 691.41 MiB │ 65.07 MiB │ 4128212175.619379043518116.52821560520632
│ flow_metrics │ vtap_flow_edge_port.1m_local │ 331.22 MiB │ 57.66 MiB │ 818085424.537126337727773.90728347298875
│ flow_metrics │ vtap_app_edge_port.1m_local │ 228.35 MiB │ 28.72 MiB │ 1435956166.7500146244035220.973729000052927
│ flow_metrics │ vtap_flow_port.1m_local │ 121.96 MiB │ 27.08 MiB │ 328060389.810443211607686.55356946899957
│ flow_metrics │ vtap_app_port.1m_local │ 97.90 MiB │ 16.84 MiB │ 870320117.9521589760088220.283444020590128
│ event │ perf_event_local │ 2.84 MiB │ 1.24 MiB │ 16543180.123919482560678.70126337423683
└─────────────────┴──────────────────────────────────────────┴──────────────┴──────────────┴───────────┴────────────────────┴─────────────────────┘

查询某个数据表每天的空间消耗,例如查询 l7_flow_log 每天的空间消耗,确认某个数据表每天的空间消耗,评估该类数据如何设置保存时长:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
WITH sum(bytes_on_disk) AS size SELECT SUBSTRING(partition, 1, 10) AS date, formatReadableSize(sum(data_uncompressed_bytes)) AS "压缩前总大小", formatReadableSize(sum(bytes_on_disk)) AS "压缩后总大小", sum(rows) AS "总行数", sum(data_uncompressed_bytes)/sum(rows) AS "压缩前平均每行长度", sum(bytes_on_disk)/sum(rows) AS "压缩后平均每行长度" FROM system.parts WHERE `table` = 'l7_flow_log_local' GROUP BY date ORDER BY date DESC;

WITH sum(bytes_on_disk) AS size
SELECT
substring(partition, 1, 10) AS date,
formatReadableSize(sum(data_uncompressed_bytes)) AS `压缩前总大小`,
formatReadableSize(sum(bytes_on_disk)) AS `压缩后总大小`,
sum(rows) AS `总行数`,
sum(data_uncompressed_bytes) / sum(rows) AS `压缩前平均每行长度`,
sum(bytes_on_disk) / sum(rows) AS `压缩后平均每行长度`
FROM system.parts
WHERE table = 'l7_flow_log_local'
GROUP BY date
ORDER BY date DESC

┌─date───────┬─压缩前总大小─┬─压缩后总大小─┬───总行数─┬─压缩前平均每行长度─┬─压缩后平均每行长度─┐
2023-12-273.50 GiB │ 681.83 MiB │ 10049374374.318180216996671.14405494312382
2023-12-265.13 GiB │ 1012.92 MiB │ 14397814382.6350284841851773.76948966002756
2023-12-255.73 GiB │ 1.07 GiB │ 16340447376.420530845943270.36901530294735
2023-12-242.22 GiB │ 436.62 MiB │ 6432796370.2260309513934571.17070586413746
└────────────┴──────────────┴──────────────┴──────────┴────────────────────┴────────────────────┘

根据某个字段的值,区分统计空间消耗。例如:查看 l7_flow_log 表中不同应用协议(l7_protocol)的行数,以及对应的 request_resource 字段的平均长度,评估是否关闭某种应用协议的解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
SELECT dictGet(flow_tag.int_enum_map, 'name', ('l7_protocol', toUInt64(l7_protocol))) AS "应用协议", count(0) AS "行数", sum(length(request_resource))/count(l7_protocol) AS "平均 request_resource 长度", sum(length(request_resource))/sum(if(request_resource !='', 1, 0)) AS "平均非空 request_resource 长度" FROM flow_log.l7_flow_log WHERE time>now()-86400 GROUP BY l7_protocol ORDER BY "行数" DESC;

SELECT
dictGet(flow_tag.int_enum_map, 'name', ('l7_protocol', toUInt64(l7_protocol))) AS `应用协议`,
count(0) AS `行数`,
sum(length(request_resource)) / count(l7_protocol) AS `平均 request_resource 长度`,
sum(length(request_resource)) / sum(if(request_resource != '', 1, 0)) AS `平均非空 request_resource 长度`
FROM flow_log.l7_flow_log
WHERE time > (now() - 86400)
GROUP BY l7_protocol
ORDER BY `行数` DESC

┌─应用协议───┬─────行数─┬─平均 request_resource 长度─┬─平均非空 request_resource 长度─┐
│ HTTP │ 1530752650.9493890129600351.16722749422727
│ MySQL │ 1276219767.32974729977919103.38747090527679
│ DNS │ 927139441.679012347010641.6790123470106
│ HTTP2 │ 45610750.97422647073332491.0020964209295697
│ TLS │ 342276914.61352840346514823.354457466682167
│ Redis │ 214066889.6192693121960192.19873624192489
│ gRPC │ 95747113.70939172048030723.696586596959204
│ N/A │ 791130 │ nan │
│ Custom │ 480211
│ PostgreSQL │ 112581.11281.112
│ MongoDB │ 1081.37037037037037052.3125
└────────────┴──────────┴────────────────────────────┴────────────────────────────────┘

查看 l7_flow_log 表中不同统计位置(tap_side)的行数,评估是否关闭某些统计位置处采集的调用日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SELECT tap_side, dictGet(flow_tag.string_enum_map, 'name', ('tap_side', tap_side)) AS "统计位置", count(0) AS "行数" FROM flow_log.l7_flow_log WHERE time>now()-86400 GROUP BY tap_side ORDER BY "行数" DESC;

SELECT
tap_side,
dictGet(flow_tag.string_enum_map, 'name', ('tap_side', tap_side)) AS `统计位置`,
count(0) AS `行数`
FROM flow_log.l7_flow_log
WHERE time > (now() - 86400)
GROUP BY tap_side
ORDER BY `行数` DESC

┌─tap_side─┬─统计位置───────┬─────行数─┐
│ c │ 客户端网卡 │ 15034372
│ s │ 服务端网卡 │ 9343403
│ c-p │ 客户端进程 │ 9179406
│ s-p │ 服务端进程 │ 5723075
│ rest │ 其他网卡 │ 3170534
│ s-nd │ 服务端容器节点 │ 2796958
│ c-nd │ 客户端容器节点 │ 2334083
local │ 本机网卡 │ 1365403
│ s-app │ 服务端应用 │ 89280
│ app │ 应用 │ 80079
│ s-gw │ 网关到服务端 │ 11
└──────────┴────────────────┴──────────┘

根据某个字段的值,区分统计空间消耗。例如:查看 event.perf_event 表中不同 duration 的数据行数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SELECT count(), min(duration) AS min_duration_us, max(duration) AS max_duration_us, toUInt64(duration/1000) AS ms FROM event.perf_event GROUP BY ms ORDER BY ms ASC

SELECT
count(),
min(duration) AS min_duration_us,
max(duration) AS max_duration_us,
toUInt64(duration / 1000) AS ms
FROM event.perf_event
GROUP BY ms
ORDER BY ms ASC

┌─count()─┬─min_duration_us─┬─max_duration_us─┬───ms─┐
233129100019991
6912200029992
1209300039993
552400049994
320500259965
187600069926
119703079977
103800389928
...

0x2: 黑色 - 设置数据保存时长

deepflow-server 为所有的数据表都提供了保存时长的设置能力,在 server.yaml 中搜寻 -ttl-hour 配置项可以按需设置特定数据表的保存时长。通常你可以将指标类数据(flow_metricsprometheus 等)设置更长的保存时长,而将日志类数据(flow_log 等)设置更多的保存时长。如你所见,保存时长的精度可以达到小时。

在企业版中你可以直接在 DeepFlow 页面上设置数据的保存时长。社区版仅支持对未创建的数据表设置保存时长,因此当你希望修改某个表的保存时长时,需要先进入 ClickHouse 删除该表然后重启 deepflow-server。

0x3: 绿色 - 降低数据精细程度

我们首先关注绿色的配置项,调整这些配置能帮助我们对数据的精细程度进行取舍,从而降低存储压力。

对于流日志 flow_log.l4_flow_log

  • 设置 l4_log_ignore_tap_sides,可丢弃部分统计位置(tap_side)处采集的流日志。如下图所示,以 K8s 容器环境为例,DeepFlow 默认会采集虚拟网卡和物理网卡上的流日志。当 Pod1 访问 Pod3 时,我们会在沿途的四个网卡上采集到同一股流量的四条流日志。例如我们可以设置此配置项为 c-nd 和 s-nd,来丢弃物理网卡上采集的流日志。关于统计位置的详细描述可参考在线文档
  • 设置 l4_log_tap_types,可丢弃部分采集点处采集的流日志。当我们让 Agent 处理物理交换机的镜像流量(企业版功能)时,可以通过设置此配置项来控制丢弃指定镜像位置的流日志。特别地,将此配置项设置为 [-1] 时,将完全关闭流日志数据。

数据的统计位置(tap-side)数据的统计位置(tap-side)

对于调用日志 flow_log.l7_flow_log

  • 设置 obfuscate-enabled-protocols,可对调用日志的 request_resource 字段进行脱敏,将其中的变量替换为 ?。目前支持对 MySQL、PostgreSQL、Redis 协议进行脱敏。由于脱敏后的字段长度会有明显的降低,因此也就能降低存储开销。注意脱敏会增加 deepflow-agent 的 CPU 开销。
  • 设置 l7_log_ignore_tap_sides,可丢弃部分统计位置(tap_side)处采集的调用日志。以 K8s 容器环境为例,DeepFlow 默认会采集应用进程、虚拟网卡和物理网卡上的调用日志。上图中当 Pod1 访问 Pod3 时,我们会在沿途的两个进程、四个网卡上采集到同一股流量的六条调用日志。例如我们可以设置此配置项为 c-nd 和 s-nd,来丢弃物理网卡上采集的调用日志。
  • 设置 l7_log_tap_types,可丢弃部分采集点处采集的调用日志。当我们让 Agent 处理物理交换机的镜像流量(企业版功能)时,可以通过设置此配置项来控制丢弃指定镜像位置的调用日志。特别地,将此配置项设置为 [-1] 时,将完全关闭调用日志数据,同时也关闭了分布式追踪功能。

调整流日志和调用日志的上述配置项,并不会影响指标数据 flow_metrics 的准确性。而对于指标数据,虽然消耗的存储空间并不大,但我们也暴露了一些配置项:

  • 设置 http-endpoint-extration,可控制如何生成 HTTP 协议的 endpoint 字段。对于 RPC(例如 gRPC/Dubbo 等)协议,endpoint 字段是明确的,通常能从头部直接获取。但 HTTP URI 中究竟哪部分可作为 endpoint 通常是一个难题。默认情况下对于所有的 URI 我们提取前两段作为 endpoint。但 2 对于某些 API 可能过短,对另一些 API 又过长,此时可以调整此配置项,避免生成爆炸的 endpoint 字段值,也避免生成的 endpoint 无法提供足够的信息量。
  • 设置 inactive_server_port_enabled,可将不活跃的 TCP/UDP 端口号存储为 server_port = 0,当网络中存在端口扫描流量时,开启此配置能有效降低指标数据的基数。ClickHouse 对指标基数是不敏感的,但降低指标数据的数量可以让查询更快。此配置项默认是开启的。
  • 设置 inactive_ip_enabled,可将不活跃的 IP 地址存储为 0.0.0.0,当网络中存在 IP 地址扫描流量时,开启此配置能有效降低指标数据的基数。此配置项默认是开启的。
  • 设置 vtap_flow_1s_enabled,可关闭 1s 粒度的聚合数据,当不需要高精度指标数据时可选择关闭此配置,此配置项默认是开启的。

对于持续剖析 profile

  • 设置 on-cpu-profile.frequency,可调整函数调用栈的采样频率,默认为 99,表示每秒采样 99 次,即大约每 10ms 采集一次函数调用栈。将此值调低可减少函数调用栈的数据量。
  • 设置 on-cpu-profile.cpu,可设置是否区分不同 CPU 核心上的函数调用栈,默认为 0 表示不区分 CPU 核心,存储开销低。当设置为 1 时不同 CPU 核心上的函数调用栈不会聚合。

0x4: 蓝色 - 关闭不关心的数据

接下来我们看看蓝色的配置项,调整这些配置能够帮助我们关闭不关心的功能,或者屏蔽不关心的流量,从而达到降低存储开销的目的。

对于调用日志 flow_log.l7_flow_log

  • 设置 l7-protocol-enabled,可选择开启解析的应用协议。默认情况下所有应用协议都是开启解析的,如果你对 Kafka、Redis 等某些协议不关心,可将其从列表中删除。
  • 设置 l7-protocol-ports,可设置特定应用协议尝试解析的端口号列表。默认情况下 DNS 仅解析 53、5353 两个端口的流量,TLS 仅解析 443 端口的流量,其他所有应用协议解析 1-65535 全端口流量。这个配置项对特征不明显的协议非常有效,例如当你非常清楚环境中 Redis 协议的所有通信端口号时,设置该配置项能避免误解析,从而也就能避免存储由于误解析生成的脏数据。

对于指标数据 flow_metrics

  • 设置 l4_performance_enabled,可关闭对高级网络性能指标的计算和存储。高级网络性能指标包括 vtap_flow_* 表中的时延、性能、异常指标(即吞吐类以外的所有指标),具体指标列表详见在线文档
  • 设置 l7_metrics_enabled,可关闭对应用性能指标的计算和存储。应用性能指标对应 vtap_app_* 表。

对于性能事件 event.perf_event

  • 设置 io-event-collect-mode,可调整采集的文件读写事件范围。设置为 0 完全关闭采集,设置为 1 仅采集在 Request 生命周期内发生的文件读写,设置为 2 采集所有文件读写。默认值为 1,聚焦于监控文件读写对 Request 性能的影响。
  • 设置 io-event-minimal-duration,可通过设置耗时阈值,来调整记录的文件读写事件的多少。默认此值为 1ms,仅记录读写耗时大于等于 1ms 的事件。调高此值能降低事件的数量。

对于持续剖析 profile

  • 设置 on-cpu-profile.disabled,可彻底关闭持续剖析功能。

此外,我们还能通过一些配置项从源头处减少数据量:

  • 设置 tap_interface_regex,控制采集流量的网卡列表。当我们不希望采集某些虚拟网卡或物理网卡的流量时,可调整此配置。
  • 设置 dispatcher_bpf,可使用 BPF 表达式过滤采集的流量。例如当我们确认所有 9999 端口的流量都是监控数据传输的流量,如果我们对此不关心,可配置此项为 not port 9999 来屏蔽对这些流量的采集
  • 设置 kprobe_blacklist,可设置一个端口号黑名单,使得 eBPF kprobe 能利用这个端口号列表过滤(丢弃)Socket 数据,减少调用日志的数据量。

0x5: 红色 - 设置过载保护阈值

最后我们来看看红色的配置项。我们不希望极端情况下 agent 输出过量的流日志和调用日志,避免导致占用过大的带宽;也不希望单个 server 的写入量过大,避免导致 ClickHouse 的过载。因此我们暴露了如下配置项:

  • l4_log_collect_nps_threshold,用于控制 agent 发送流日志的最大速率。当实际待发送的流日志超过此速率时,agent 会进行主动的丢弃,此时通过监控 deepflow_system.deepflow_agent_flow_aggr 中的 drop-in-throttle 指标可监控此丢弃行为。
  • l7_log_collect_nps_threshold,用于控制 agent 发送调用日志的最大速率。当实际待发送的调用日志超过此速率时,agent 会进行主动的丢弃,此时通过监控 deepflow_system.deepflow_agent_l7_session_aggr 中的 throttle-drop 指标可监控此丢弃行为。
  • l4-throttle,用于控制单个 server 副本写入流日志的最大速率,当设置为 0 时使用 throttle 配置项的值。当实际待写入的流日志超过此速率时,server 会进行主动的丢弃,此时通过监控 deepflow_system.deepflow_server_ingester_decoder 中的 drop_count 指标(使用 msg_type: l4_log 过滤)可监控此丢弃行为。
  • l7-throttle,用于控制单个 server 副本写入调用日志的最大速率,当设置为 0 时使用 throttle 配置项的值。当实际待写入的调用日志超过此速率时,server 会进行主动的丢弃,此时通过监控 deepflow_system.deepflow_server_ingester_decoder 中的 drop_count 指标(使用 msg_type: l7_log 过滤)可监控此丢弃行为。

0x6: 演进方向

未来 DeepFlow 还将提供更精细的配置能力,例如允许通过 K8s Workload 的 Annotation 来控制 DeepFlow 是否采集对应 Pod 的数据、根据指定字段的值过滤流日志和调用日志等,欢迎提交 Feature Request 和 Pull Request。

0x7: 什么是 DeepFlow

DeepFlow 是云杉网络开发的一款可观测性产品,旨在为复杂的云基础设施及云原生应用提供深度可观测性。DeepFlow 基于 eBPF 实现了应用性能指标、分布式追踪、持续性能剖析等观测信号的零侵扰Zero Code)采集,并结合智能标签SmartEncoding)技术实现了所有观测信号的全栈Full Stack)关联和高效存取。使用 DeepFlow,可以让云原生应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。

GitHub 地址:https://github.com/deepflowio/deepflow

访问 DeepFlow Demo,体验零插桩、全覆盖、全关联的可观测性。