exporter句柄泄漏致target批量down,本质是其fd耗尽无法建连;需用pgrep、ls /proc/pid/fd、lsof定位tcp连接未关闭问题,通过调优http客户端、强制关闭resp.body及容器ulimit限制根治。

Exporter本地句柄泄漏导致大面积Target Down,本质不是监控失效,而是Exporter自身已“瘫痪”——它占尽文件描述符(fd),无法建立新连接去抓取指标,Prometheus自然收不到响应,所有关联Target批量变DOWN。
快速定位泄漏源:别猜,直接看fd数量和类型
登录运行Exporter的节点,执行三步确认:
- 查进程PID:
pgrep -f csp_exporter(替换成你的Exporter名) - 统计打开句柄数:
ls /proc/<pid>/fd | wc -l</pid>;若远超ulimit -n设定值(如>60000),基本锁定泄漏 - 分析句柄分布:
lsof -p <pid> -a -i -n | awk '{print $9}' | sort | uniq -c | sort -nr | head -10</pid>;若大量显示xxx:port->target:443 (established)且连接数随时间线性上涨,就是TCP连接未关闭
紧急止血:不让问题扩散到整个采集链路
在不重启Exporter的前提下,先保Prometheus能继续抓其他Target:
- 临时提升该进程fd上限(需root):
echo -n "65536:65536" > /proc/<pid>/limits</pid> - 检查proxy-server或proxy-agent是否也出现类似fd堆积(尤其当它们复用同一连接池);如有,同步处理
- 在Prometheus配置中,对疑似故障Exporter加
sample_limit或临时降低scrape_interval,减少其被轮询频次
根治代码层:Go Exporter常见泄漏点与修复写法
多数自研Exporter用Go开发,以下两点最常引发句柄泄漏:
-
HTTP客户端未复用:每次采集都新建
http.Client,且未设置Transport.MaxIdleConns等参数。应全局复用一个client,并显式配置连接池:client := &http.Client{Transport: &http.Transport{MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 30 * time.Second}} -
响应体未关闭:调用
resp.Body.Close()被遗漏,尤其在错误分支里。务必用defer resp.Body.Close(),且确保它在所有return路径前执行
防御性部署:从容器到宿主机设好“安全阀”
单靠代码修复不够,必须叠加资源约束:
- Docker启动时强制限制:
docker run --ulimit nofile=32768:32768 ... - Compose中声明:
ulimits: { nofile: { soft: 32768, hard: 32768 } } - 宿主机Docker daemon默认限:
/etc/docker/daemon.json加入"default-ulimits": {"nofile": {"Soft": 32768, "Hard": 32768}},避免新容器继承过低默认值 - Prometheus侧加告警:
container_open_fds{container=~"csp_exporter.*"} / container_limits_nofile > 0.8,提前预警
这类问题不复杂但容易忽略——它不报错,只悄悄吃光句柄,直到整个采集面塌陷。关键是把fd监控当成和内存、CPU一样的基础指标来看待。











