Telegraf unbound 输入插件:采集 Unbound DNS 解析器统计指标的完整配置与实现解析
【免费下载链接】telegrafAgent for collecting, processing, aggregating, and writing metrics, logs, and other arbitrary data.项目地址: https://gitcode.com/GitHub_Trending/te/telegraf
本文基于 Telegraf 仓库中plugins/inputs/unbound/README.md及其配套源码 unbound.go、sample.conf、unbound_test.go 整理。该插件自 Telegraf v1.5.0 引入,适用于所有平台(server、network 类插件)。它通过调用unbound-control命令行工具,周期性采集 Unbound 递归 DNS 解析器的查询量、缓存命中率、递归时延、内存占用、DNSSEC 验证结果等统计指标,并可开启线程级指标与递归查询时延直方图,帮助你在 DNS 基础设施监控中定位解析性能退化、缓存异常与验证失败等问题。读完本文,你可以完成插件的配置、权限设置(组成员或 sudo 两种方式),理解全部配置项的实际行为,以及从源码层面弄清指标是如何从unbound-control的原始输出解析成 Telegraf 测量值的。
工作原理:shell out 到 unbound-control
从源码看,该插件并非通过 socket 直接读取统计数据,而是在每次采集时"shell out"执行unbound-control二进制,参数固定为stats_noreset(读取统计但不重置计数器)。核心采集逻辑在 Gather 方法与 unboundRunner 中,调用链为:
init()将插件以名称unbound注册到输入插件注册表(默认值:Binary=/usr/sbin/unbound-control、Timeout=1s、UseSudo=false、Server=""、ThreadAsTag=false、ConfigFile=""、Histogram=false),注册入口见 init 函数;- 若配置了
server,先解析出host:port。由于unbound-control要求 IP 地址,插件会用net.Resolver对该主机名做一次带超时(timeout配置)的 DNS 查询,取第一个 IP,再拼成-s IP@port参数(未显式指定端口时,不追加端口参数); - 若配置了
config_file,追加-c参数指向 unbound 配置文件; - 若
use_sudo = true,则改用sudo <binary> ...形式执行; - 通过 internal.RunTimeout 启动命令并按
timeout限制运行时间,超时会尝试杀死进程;标准输出即key=value形式的统计行。
stats_noreset意味着采集是无副作用的:计数器保持累计值,插件重启或采集间隔变化都不会清零历史统计,这对计算速率型指标(如每秒查询量)是必要的。
配置
插件的完整示例配置如下(与仓库中 sample.conf 一致):
# A plugin to collect stats from the Unbound DNS resolver [[inputs.unbound]] ## Address of server to connect to, read from unbound conf default, optionally ':port' ## Will lookup IP if given a hostname server = "127.0.0.1:8953" ## If running as a restricted user you can prepend sudo for additional access: # use_sudo = false ## The default location of the unbound-control binary can be overridden with: # binary = "/usr/sbin/unbound-control" ## The default location of the unbound config file can be overridden with: # config_file = "/etc/unbound/unbound.conf" ## The default timeout of 1s can be overridden with: # timeout = "1s" ## When set to true, thread metrics are tagged with the thread id. ## ## The default is false for backwards compatibility, and will be changed to ## true in a future version. It is recommended to set to true on new ## deployments. thread_as_tag = false ## Collect metrics with the histogram of the recursive query times: # histogram = false各配置项的含义与源码行为对照如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
server | ""(读取 unbound 自身配置) | unbound-control连接的地址,可带:port。给主机名时插件会先做 DNS 解析再传 IP 给unbound-control(源码强制要求 IP) |
use_sudo | false | 为true时以sudo前缀执行二进制,用于受限用户场景 |
binary | /usr/sbin/unbound-control | unbound-control可执行文件路径 |
config_file | "" | 传给unbound-control -c的 unbound 配置文件路径,默认读取系统默认配置 |
timeout | 1s | 命令执行超时,同时也是主机名解析的上下文超时;超时后进程会被尝试终止 |
thread_as_tag | false | 为true时,逐线程统计从unbound测量中拆出,写入带thread标签的unbound_threads测量。默认false是向后兼容考虑,官方建议新部署设为true |
histogram | false | 为true时额外采集递归查询时延直方图字段。注意:直方图统计要求 unbound 侧开启extended-statistics: yes |
除插件自身配置外,它同样支持 Telegraf 的全局与插件级配置选项(字段/标签过滤、别名、插件排序等),参见 docs/CONFIGURATION.md 的 Plugins 章节;README 中该部分由 docs/includes/plugin_config.md 生成嵌入。
权限设置
文档特别强调:插件依赖unbound-control,该工具通常需要额外权限才能成功执行。取决于执行插件的 telegraf 用户/组权限,你可能需要调整组成员、设置 ACL,或使用 sudo。文档给出两种方案。
方案一:组成员(推荐)
将 telegraf 用户加入 unbound 组:
$ groups telegraf telegraf : telegraf $ usermod -a -G unbound telegraf $ groups telegraf telegraf : telegraf unbound这是官方文档推荐的方式,配置文件中无需任何额外项。
方案二:sudo 授权
配置中开启:
[[inputs.unbound]] use_sudo = true并在 sudoers 中追加(visudo):
$ visudo # Add the following line: Cmnd_Alias UNBOUNDCTL = /usr/sbin/unbound-control telegraf ALL=(ALL) NOPASSWD: UNBOUNDCTL Defaults!UNBOUNDCTL !logfile, !syslog, !pam_session从源码看,use_sudo = true时命令会被重组为sudo /usr/sbin/unbound-control [-s IP@port] [-c config] stats_noreset(见 unboundRunner),因此 sudoers 中放行的可执行文件路径必须与binary配置一致。文档最后建议"选择你认为最合适的方案"。
输出指标
插件输出的字段与 unbound 配置相关:基础统计随unbound-control stats_noreset输出,扩展统计(extended statistics)需要在 unbound 配置中开启extended-statistics: yes才会出现。原始统计名中的点号会被替换为下划线。下面列出 README 给出的完整字段清单。
unbound(整机级测量)
- unbound - fields: total_num_queries total_num_cachehits total_num_cachemiss total_num_prefetch total_num_recursivereplies total_requestlist_avg total_requestlist_max total_requestlist_overwritten total_requestlist_exceeded total_requestlist_current_all total_requestlist_current_user total_recursion_time_avg total_recursion_time_median time_now time_up time_elapsed mem_total_sbrk mem_cache_rrset mem_cache_message mem_mod_iterator mem_mod_validator num_query_type_A num_query_type_PTR num_query_type_TXT num_query_type_AAAA num_query_type_SRV num_query_type_ANY num_query_class_IN num_query_opcode_QUERY num_query_tcp num_query_ipv6 num_query_flags_QR num_query_flags_AA num_query_flags_TC num_query_flags_RD num_query_flags_RA num_query_flags_Z num_query_flags_AD num_query_flags_CD num_query_edns_present num_query_edns_DO num_answer_rcode_NOERROR num_answer_rcode_SERVFAIL num_answer_rcode_NXDOMAIN num_answer_rcode_nodata num_answer_secure num_answer_bogus num_rrset_bogus unwanted_queries unwanted_replies字段大致分组:total_*为查询/缓存/递归总量与请求队列水位;time_*为时间戳与运行时长;mem_*为内存模块占用;num_query_*按查询类型、class、opcode、TCP/IPv6、DNS 头部标志位与 EDNS 维度计数;num_answer_*为应答 rcode 与 DNSSEC 验证结果(secure/bogus);unwanted_*为被拒绝的查询与应答。
unbound_threads(thread_as_tag = true 时)
- unbound_threads - tags: - thread - fields: - num_queries - num_cachehits - num_cachemiss - num_prefetch - num_recursivereplies - requestlist_avg - requestlist_max - requestlist_overwritten - requestlist_exceeded - requestlist_current_all - requestlist_current_user - recursion_time_avg - recursion_time_medianhistogram 字段(histogram = true 时)
开启histogram后,额外采集递归查询时延直方图,字段名表示每个 bin 的下界:
- unbound: - fields: histogram_.000000 histogram_.000001 histogram_.000002 histogram_.000004 histogram_.000008 histogram_.000016 histogram_.000032 histogram_.000064 histogram_.000128 histogram_.000256 histogram_.000512 histogram_.001024 histogram_.002048 histogram_.004096 histogram_.008192 histogram_.016384 histogram_.032768 histogram_.065536 histogram_.131072 histogram_.262144 histogram_.524288 histogram_1.000000 histogram_2.000000 histogram_4.000000 histogram_8.000000 histogram_16.000000 histogram_32.000000 histogram_64.000000 histogram_128.000000 histogram_256.000000 histogram_512.000000 histogram_1024.000000 histogram_2048.000000 histogram_4096.000000 histogram_8192.000000 histogram_16384.000000 histogram_32768.000000 histogram_65536.000000 histogram_131072.000000 histogram_262144.000000示例输出
unbound,host=localhost total_requestlist_avg=0,total_requestlist_exceeded=0,total_requestlist_overwritten=0,total_requestlist_current_user=0,total_recursion_time_avg=0.029186,total_tcpusage=0,total_num_queries=51,total_num_queries_ip_ratelimited=0,total_num_recursivereplies=6,total_requestlist_max=0,time_now=1522804978.784814,time_elapsed=310.435217,total_num_cachemiss=6,total_num_zero_ttl=0,time_up=310.435217,total_num_cachehits=45,total_num_prefetch=0,total_requestlist_current_all=0,total_recursion_time_median=0.016384 1522804979000000000 unbound_threads,host=localhost,thread=0 num_queries_ip_ratelimited=0,requestlist_current_user=0,recursion_time_avg=0.029186,num_prefetch=0,requestlist_overwritten=0,requestlist_exceeded=0,requestlist_current_all=0,tcpusage=0,num_cachehits=37,num_cachemiss=6,num_recursivereplies=6,requestlist_avg=0,num_queries=43,num_zero_ttl=0,requestlist_max=0,recursion_time_median=0.032768 1522804979000000000 unbound_threads,host=localhost,thread=1 num_zero_ttl=0,recursion_time_avg=0,num_queries_ip_ratelimited=0,num_cachehits=8,num_prefetch=0,requestlist_exceeded=0,recursion_time_median=0,tcpusage=0,num_cachemiss=0,num_recursivereplies=0,requestlist_max=0,requestlist_overwritten=0,requestlist_current_user=0,num_queries=8,requestlist_avg=0,requestlist_current_all=0 1522804979000000000注意示例输出中的total_tcpusage、total_num_queries_ip_ratelimited、num_zero_ttl等字段:README 的静态清单描述的是较基础的统计集,而实际输出还包含 unbound 版本相关的扩展字段(如 TCP 使用率、IP 限速、零 TTL 计数)。这是因为插件对stats_noreset的输出生成式解析——任何形如key=value且值可解析为数字的行都会成为字段,所以新版 Unbound 增加统计项时无需改动插件代码即可自动出现。
源码级实现细节
输出解析与容错
Gather 用bufio.Scanner逐行读取命令输出,按第一个=切分键值:
- 每行必须恰好切出两段(
len(cols) == 2),否则整行静默跳过; - 值用
strconv.ParseFloat(value, 64)解析,非数字时通过acc.AddError记录错误(expected a numerical value for ...)并跳过该行——注意这不会使整次采集失败,而是部分容错; - 其余统计名统一
strings.ReplaceAll(stat, ".", "_")后进入unbound测量。
这种"宽松解析"与测试文件中的断言一致:TestParseFullOutput 用一段真实的unbound-control输出(63 个字段)验证默认模式;TestParseFullOutputHistogram 验证开启直方图后字段数变为 103(恰好多出 40 个histogram_*字段);TestParseFullOutputThreadAsTag 与 TestParseFullOutputThreadAsTagHistogram 验证线程拆分后的两个测量。
thread_as_tag 的拆分规则
当ThreadAsTag为true时,凡以thread开头的统计名(如thread0.num.queries)会被拆分:去掉thread前缀的部分(0)作为thread标签值,剩余 token 用下划线连接作为字段名(num_queries),归入unbound_threads测量(见 Gather 中的线程分支 与 unbound_threads 写入)。有两点值得注意:
- 线程号必须能解析为整数,否则该行留在
unbound测量中按普通点号替换处理; - 该分支只在
ThreadAsTag开启时生效。默认false时,thread0.num.queries这类字段会以thread0_num_queries形式平铺在unbound测量里——测试期望值(如thread0_num_queries)证实了这一点。这也解释了 README 中"默认 false 为向后兼容,未来版本将改为 true,新部署建议设为 true"的含义。
histogram 的命名归一化
Unbound 扩展统计中直方图行形如histogram.000000.000512.to.000000.001024=5503(区间下界.to.上界)。插件的处理是(见 histogram 分支):
- 先判断是否以
histogram.开头;只有histogram = true时才转换,否则该行被完全丢弃(不会以其他形式出现); - 取
.to.之前的部分作为 bin 下界,再TrimLeft(suffix, "0")去掉前导零;若结果以.开头(原下界全零,如000000.000000)则补回一个0,得到histogram_.000000; - 因此
histogram.000000.000512.to.000000.001024→ 字段histogram_.000512,histogram.000001.000000.to.000002.000000→histogram_1.000000。测试期望表 parsedFullOutputHistogram 中histogram_.000000=20、histogram_1.000000=136等值可以直接对照验证。
另外,直方图行即使histogram = false也会被识别为该分支(只是不采集),不会混入unbound普通字段——从分支顺序看,histogram.前缀判断先于通用的点号替换分支执行。
超时控制
命令执行统一走 internal.RunTimeout:先Start,再WaitTimeout;文档注释明确"超时会尝试杀死进程"。因此timeout同时约束 DNS 主机名解析(resolver 上下文)与命令本身运行时长,对守护进程卡死的unbound-control起到兜底作用。
采集失败的表现
unboundRunner返回的错误(主机名解析失败、无 IP、命令执行失败/超时)会被Gather包装为error gathering metrics: ...向上返回,导致本次采集记为错误并进入插件错误计数,而不是部分写入。这一点与行级解析错误(AddError后继续)不同:进程级失败整次丢弃,行级失败只丢该行。
验证采集
可以使用--test参数只运行输入插件并输出到 stdout(见 docs/COMMANDS_AND_FLAGS.md 中的说明),快速验证插件配置与权限是否正确:
telegraf --config /etc/telegraf/telegraf.conf --test若权限不足,通常会看到error running unbound-control ...之类的错误;若server主机名无法解析,则会看到error looking up ip for server ...或error no ip for server ...(均出自 unboundRunner)。正常时输出形如上文"示例输出"中的unbound,host=...行。
小结与注意事项
- 插件本质是
unbound-control stats_noreset的包装器:二进制路径、配置文件、sudo 前缀、目标地址都是对这条命令的参数拼装,理解命令即理解插件; server填主机名可以,但插件会替你做一次 DNS 解析,若该解析本身依赖被监控的 Unbound,要留意采集链路不形成自我依赖故障放大;- 新部署建议
thread_as_tag = true,以获得带thread标签的unbound_threads测量,避免线程字段平铺在unbound上; - 需要直方图时在 unbound 配置中开启
extended-statistics: yes,并在插件中设置histogram = true,字段数会从 63 增至 103(以测试期望为准); - 权限二选一:加入 unbound 组(推荐)或按文档配置 sudoers 白名单,后者需保证
binary路径与 sudoers 中放行路径一致; - 解析是"所见即所得"的宽松模式,Unbound 版本升级带来的新统计项会自动映射为字段(点换下划线),排查字段缺失时优先核对 unbound 侧
extended-statistics配置,而不是插件本身。
【免费下载链接】telegrafAgent for collecting, processing, aggregating, and writing metrics, logs, and other arbitrary data.项目地址: https://gitcode.com/GitHub_Trending/te/telegraf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考