ESP32接入Prometheus监控:嵌入式设备实现云原生监控实践

📅 2026/8/19 3:54:51
ESP32接入Prometheus监控:嵌入式设备实现云原生监控实践
1. 项目概述当嵌入式遇上云原生监控最近在折腾一个物联网项目用ESP32采集一些传感器数据比如温湿度、电压什么的。数据是采上来了但怎么实时、直观地看并且能设置个阈值告警成了个问题。用串口打印太原始自己写个简陋的Web服务器又不够专业。正好团队里搞后端和运维的同事天天把Prometheus和Grafana挂在嘴边我就琢磨能不能把这套在云原生领域叱咤风云的监控体系直接搬到ESP32这个小小的微控制器上这个想法听起来有点“跨界”——一边是资源极度受限、通常跑在RTOS上的嵌入式设备另一边是吃内存、吃CPU的现代监控栈。但仔细一想逻辑是通的Prometheus的核心是拉取Pull模型和简单的文本格式Exposition FormatESP32完全有能力运行一个轻量级的HTTP服务器按固定格式暴露自己的指标。这样一来ESP32就变成了一个“Node Exporter”而运行在服务器上的Prometheus则定期来“抓取”数据最后由Grafana进行酷炫的可视化。我花了些时间把这条路走通了。实测下来ESP32以ESP32-S3为例在运行Wi-Fi连接和一个小型HTTP服务器的情况下内存占用增加约20KBCPU负载在数据暴露瞬间有短暂峰值但完全在可接受范围内。更重要的是整个监控链路打通后你获得的是一个企业级的、可扩展的监控方案。你可以为成百上千个ESP32节点配置自动发现可以在Grafana里为所有设备创建统一的仪表盘可以基于PromQL设置复杂的告警规则比如“连续5分钟温度超过50度且湿度低于30%的设备”。这比每个设备各自为战要强大得多。所以这篇内容就是记录我如何让ESP32“学会”说Prometheus的语言并最终在Grafana上呈现出专业仪表盘的全过程。无论你是物联网开发者想提升运维能力还是运维工程师想了解如何监控边缘设备这里都有可以直接“抄作业”的步骤。2. 核心思路与方案选型为什么是Prometheus拉取模型在决定用Prometheus监控ESP32之前有几个备选方案MQTT上报到中间件再处理、设备直接写数据库、或者用传统的SNMP。最终选择Prometheus是基于以下几个核心考量这也是整个项目的设计基石。2.1 拉取Pull vs. 推送Push谁主动谁更合理物联网设备上报数据无非两种模式设备主动推Push或者服务器主动拉Pull。MQTT是典型的推送模型设备作为发布者将数据发送到Broker。这种方式下设备需要维持与Broker的长连接并且要处理消息队列、重连等逻辑对ESP32来说有一定复杂度。更重要的是如果设备数量巨大海量的推送请求对中心服务会造成压力。Prometheus采用的是拉取模型。监控目标Target只需要提供一个HTTP端点返回规定格式的文本。Prometheus Server会按照配置的抓取间隔scrape_interval主动去访问这些端点拉取数据。对于ESP32这样的边缘设备这意味着资源消耗更可控ESP32只需要在Prometheus来访问时临时生成一次响应即可无需维持长连接或后台任务大部分时间处于低功耗状态。服务端掌握主动权抓取频率、超时时间、重试策略完全由Prometheus Server控制便于统一管理和降级。即使ESP32短暂离线Prometheus也会记录抓取失败而不会丢失历史队列中的消息。安全性可以在网络层面设置防火墙只允许Prometheus Server的IP访问ESP32的特定端口例如9100而不是让ESP32去主动连接外网某个地址。当然拉取模型也有缺点比如需要为每个设备配置抓取目标。但Prometheus提供了基于文件、DNS、Consul等多种服务发现机制可以动态管理大量设备完美弥补了这个不足。2.2 Exposition格式简单到极致就是美Prometheus的数据暴露格式是其成功的关键之一。它既不是复杂的JSON也不是二进制的协议而是一种纯文本格式每行一个指标结构清晰。# HELP sensor_temperature_celsius Current temperature in Celsius. # TYPE sensor_temperature_celsius gauge sensor_temperature_celsius{device_idesp32_kitchen, sensordht22} 24.5 # HELP sensor_humidity_percent Current relative humidity. # TYPE sensor_humidity_percent gauge sensor_humidity_percent{device_idesp32_kitchen, sensordht22} 65.2这种格式对ESP32来说简直是福音。在C/C环境中我们不需要引入复杂的JSON解析库只需要用snprintf函数按照模板将传感器读数、设备标签Labels拼接成字符串通过HTTP响应返回即可。内存开销极小代码也非常直观。2.3 整体架构与组件分工整个系统的架构非常清晰数据生产者ESP32运行Arduino或ESP-IDF程序连接传感器并启动一个轻量级HTTP服务器如AsyncWebServer在/metrics路径上响应请求返回符合Prometheus格式的指标数据。数据采集与存储Prometheus Server运行在x86/ARM服务器、虚拟机或容器中。其配置文件prometheus.yml中将ESP32的IP和端口列为抓取目标。Prometheus会定时抓取并存储这些时间序列数据。数据可视化与告警Grafana连接Prometheus作为数据源。利用其强大的仪表盘功能绘制曲线图、状态图等。同时也可以在Prometheus或Grafana中配置告警规则Alert Rules当指标异常时通过邮件、钉钉、微信等渠道通知。这个架构的扩展性极强。当你有了第二个、第三个ESP32设备时只需要在Prometheus的配置文件中添加新的目标或者更好的是使用基于文件的服务发现将设备列表写在一个JSON文件中Prometheus会自动读取并更新抓取列表。注意网络可达性。这是拉取模型的前提。Prometheus Server必须能够通过网络访问到ESP32设备。这意味着ESP32通常需要连接到一个与Prometheus Server在同一局域网LAN的Wi-Fi或者通过有公网IP的服务器进行反向代理/隧道。对于完全在NAT后的设备可能需要考虑使用Pushgateway作为中转但这会引入额外组件和复杂性不是本方案的首选。3. ESP32端实现详解从传感器到/metrics端点让ESP32提供Prometheus指标核心是两件事采集传感器数据以及提供HTTP服务。下面我以Arduino框架为例因为它的生态丰富上手快。如果你追求极致的性能和可控性ESP-IDF是更专业的选择但核心逻辑是相通的。3.1 硬件准备与开发环境搭建我使用的硬件是ESP32-S3-DevKitC-1搭配了一个DHT22温湿度传感器和一个分压电路用于测量0-3.3V的模拟电压。你完全可以根据自己的项目替换传感器。开发环境安装Arduino IDE或VS Code with PlatformIO。在Arduino IDE中需要通过“开发板管理器”安装“esp32 by Espressif Systems”平台支持。库依赖我们需要两个关键的Arduino库DHT sensor library用于读取DHT22数据。AsyncTCP和ESPAsyncWebServer这是一个非阻塞异步的Web服务器库性能比传统的WiFiServer好得多特别适合需要同时处理网络和其他任务如传感器读取的场景。在Arduino库管理中搜索安装即可。3.2 核心代码拆解异步服务器与指标生成完整的代码较长我拆解几个最关键的部分。第一部分全局变量与Wi-Fi连接#include WiFi.h #include ESPAsyncWebServer.h #include DHT.h const char* ssid Your_WiFi_SSID; const char* password Your_WiFi_Password; AsyncWebServer server(9100); // 使用9100端口这是Node Exporter的默认端口习惯性沿用 #define DHTPIN 4 // DHT22数据引脚连接GPIO4 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); #define VOLTAGE_PIN 34 // 电压测量使用ADC1通道6 (GPIO34) float temperature 0.0; float humidity 0.0; float voltage 0.0; unsigned long lastSensorRead 0; const long sensorInterval 2000; // 传感器读取间隔2秒这里定义了服务器对象和传感器对象。注意服务器端口设为9100这是约定俗成的用于暴露指标的非特权端口。第二部分指标生成函数这是最核心的函数它负责生成符合Prometheus格式的响应字符串。String getPrometheusMetrics() { String metrics ; // 1. 生成HELP和TYPE行可选但强烈建议提供提高可读性 metrics # HELP esp32_temperature_celsius Current temperature measured by DHT22.\n; metrics # TYPE esp32_temperature_celsius gauge\n; // 2. 生成指标行包含标签Labels和值 metrics esp32_temperature_celsius{device\esp32_s3\,sensor\dht22\,location\desk\} ; metrics String(temperature); metrics \n; metrics # HELP esp32_humidity_percent Current relative humidity measured by DHT22.\n; metrics # TYPE esp32_humidity_percent gauge\n; metrics esp32_humidity_percent{device\esp32_s3\,sensor\dht22\,location\desk\} ; metrics String(humidity); metrics \n; // 3. 模拟电压测量假设使用分压电路ADC读数转换为电压 // ESP32 ADC参考电压通常为3.3V12位分辨率0-4095 int adcValue analogRead(VOLTAGE_PIN); voltage (adcValue / 4095.0) * 3.3 * 2.0; // 假设分压比为2:1 metrics # HELP esp32_input_voltage_volts Voltage at analog input pin.\n; metrics # TYPE esp32_input_voltage_volts gauge\n; metrics esp32_input_voltage_volts{device\esp32_s3\,pin\gpio34\} ; metrics String(voltage, 2); // 保留两位小数 metrics \n; // 4. 可以添加一些系统指标如Wi-Fi信号强度、堆内存空闲量 metrics # HELP esp32_wifi_rssi_dbm WiFi signal strength in dBm.\n; metrics # TYPE esp32_wifi_rssi_dbm gauge\n; metrics esp32_wifi_rssi_dbm{device\esp32_s3\} ; metrics String(WiFi.RSSI()); metrics \n; metrics # HELP esp32_free_heap_bytes Amount of free heap memory.\n; metrics # TYPE esp32_free_heap_bytes gauge\n; metrics esp32_free_heap_bytes{device\esp32_s3\} ; metrics String(ESP.getFreeHeap()); metrics \n; return metrics; }关键点解析指标命名我使用了esp32_作为前缀避免与Prometheus其他指标冲突。名称应使用蛇形命名snake_case。标签Labels{device\esp32_s3\,sensor\dht22\,location\desk\}。标签是Prometheus强大的维度查询基础。通过标签你可以在Grafana中轻松筛选或聚合特定设备、特定类型传感器的数据。标签值建议使用静态或半静态信息。指标类型传感器读数温度、湿度、电压和Wi-Fi强度都是gauge类型表示一个可以任意上下变化的瞬时值。内存空闲量也是gauge。如果你的指标是计数器只增不减如设备重启次数、数据包发送总数则应使用counter类型。第三部分设置路由与主循环void setup() { Serial.begin(115200); dht.begin(); pinMode(VOLTAGE_PIN, INPUT); WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(\nConnected to WiFi. IP address: ); Serial.println(WiFi.localIP()); // 设置Prometheus指标抓取端点 server.on(/metrics, HTTP_GET, [](AsyncWebServerRequest *request){ request-send(200, text/plain; version0.0.4, getPrometheusMetrics()); }); // 可以设置一个根路径用于简单状态检查 server.on(/, HTTP_GET, [](AsyncWebServerRequest *request){ request-send(200, text/html, h1ESP32 Prometheus Exporter/h1pa href/metricsMetrics/a/p); }); server.begin(); Serial.println(HTTP server started on port 9100); } void loop() { unsigned long currentMillis millis(); if (currentMillis - lastSensorRead sensorInterval) { lastSensorRead currentMillis; // 读取传感器数据注意DHT22读取相对较慢 float newTemp dht.readTemperature(); float newHumi dht.readHumidity(); if (!isnan(newTemp) !isnan(newHumi)) { // 检查读数是否有效 temperature newTemp; humidity newHumi; } else { Serial.println(Failed to read from DHT sensor!); } // 电压在getPrometheusMetrics函数中实时读取这里不需要更新 } // AsyncWebServer是非阻塞的不需要在loop中调用client.handle() }在setup()中我们定义了两个路由/metrics这是给Prometheus抓取的专用端点。返回的Content-Type是text/plain; version0.0.4其中version0.0.4是Prometheus Exposition Format的版本标识最好加上。/一个简单的状态页方便我们通过浏览器手动检查设备是否在线。在loop()中我们以固定的间隔例如2秒读取传感器数据并更新全局变量。当Prometheus访问/metrics端点时getPrometheusMetrics函数会使用这些最新的变量值来生成响应。这种“缓存”读取值的方式避免了在HTTP请求处理函数中直接进行可能耗时的传感器读取操作保证了服务器的响应速度。实操心得异步服务器的优势。使用ESPAsyncWebServer而非WiFiServer是因为前者是非阻塞的。这意味着当Prometheus来抓取数据时服务器可以快速处理请求并返回响应而不会阻塞loop()函数中传感器读取或其他任务。这对于需要同时处理网络和实时任务的嵌入式设备至关重要。如果你用同步服务器在处理一个较慢的客户端请求时可能会导致传感器读取间隔严重漂移。4. Prometheus Server配置与服务发现ESP32准备就绪后我们需要配置Prometheus Server来抓取它。假设你的Prometheus已经通过Docker或二进制包安装好。4.1 基础静态配置最基本的配置是在prometheus.yml的scrape_configs部分添加一个job。scrape_configs: # 监控Prometheus自身 - job_name: prometheus static_configs: - targets: [localhost:9090] # 监控ESP32设备 - job_name: esp32 scrape_interval: 15s # 每15秒抓取一次可根据需要调整 static_configs: - targets: [192.168.1.100:9100] # 替换为你的ESP32 IP地址 labels: region: home platform: esp32-s3保存配置并重启Prometheus。访问Prometheus的Web UI默认http://your-server:9090在“Status” - “Targets”页面你应该能看到esp32这个job并且状态是“UP”。点击“Endpoint”列的链接应该能直接看到ESP32返回的指标文本。4.2 动态服务发现应对多设备场景当你有多个ESP32设备时手动维护IP列表非常麻烦。Prometheus支持多种服务发现方式。对于嵌入式场景基于文件的发现file_sd_configs是最简单实用的。创建目标文件创建一个JSON文件例如/etc/prometheus/esp32_targets.json内容如下[ { targets: [ 192.168.1.100:9100 ], labels: { device_id: kitchen_sensor, platform: esp32-s3 } }, { targets: [ 192.168.1.101:9100 ], labels: { device_id: living_room_sensor, platform: esp32-c3 } } ]修改Prometheus配置scrape_configs: - job_name: esp32-file-sd scrape_interval: 15s file_sd_configs: - files: - /etc/prometheus/esp32_targets.json refresh_interval: 5m # 每5分钟重新读取文件动态更新当你新增或移除设备时只需要更新这个JSON文件。Prometheus会定期refresh_interval扫描文件自动更新抓取目标无需重启Prometheus服务。注意事项标签的管理。在文件服务发现中你可以在JSON里为每个target单独指定labels。这些标签会和ESP32自身暴露的指标标签合并。我建议将相对静态的、用于标识和分组的属性如device_id,location,platform放在Prometheus的配置里管理。而将可能变化的、与具体测量相关的属性如sensor_type放在ESP32代码中暴露。这样职责更清晰。4.3 抓取参数调优对于网络可能不稳定的边缘设备适当调整抓取参数很重要。scrape_configs: - job_name: esp32 scrape_interval: 30s # 抓取间隔可以适当放长减少设备压力 scrape_timeout: 10s # 抓取超时时间如果ESP32响应慢可以适当增加 static_configs: - targets: [192.168.1.100:9100] # 重试机制 honor_timestamps: true # 如果ESP32能提供精确的时间戳可以设为true但通常用Prometheus抓取时间即可如果ESP32部署在远程网络延迟高scrape_timeout需要设置得比默认值10秒更长一些避免因单次超时导致目标被标记为“DOWN”。5. Grafana仪表盘配置与告警规则设置数据流入Prometheus后最后一步就是用Grafana把它变成直观的图表和告警。5.1 连接Prometheus数据源在Grafana中添加数据源选择“Prometheus”。URL填写你的Prometheus Server地址如http://localhost:9090。其他参数通常保持默认即可。点击“Save Test”应该显示“Data source is working”。5.2 创建第一个仪表盘新建仪表盘Dashboard点击“” - “Dashboard”。添加面板Panel点击“Add new panel”。编写PromQL查询温度曲线在Metrics浏览器中输入esp32_temperature_celsius。你会看到所有带有该指标名称的时间序列。如果你有多个设备可以使用标签进行过滤例如esp32_temperature_celsius{device_idkitchen_sensor}。或者查看所有设备esp32_temperature_celsius。图表面板设置在右侧“Panel options”中可以设置标题如“厨房温度”选择合适的可视化类型Time series。单位设置在“Standard options” - “Unit”中选择“Temperature” - “Celsius (°C)”这样图表上会自动显示°C单位。应用并保存点击“Apply”保存面板然后点击仪表盘顶部的“Save”保存整个仪表盘。你可以用同样的方法添加湿度、电压、Wi-Fi信号强度、空闲内存等面板。Grafana支持灵活的拖拽布局可以排布成你喜欢的样式。5.3 高级查询与函数应用PromQL的强大之处在于其查询和聚合能力。查看所有设备的平均温度avg(esp32_temperature_celsius) by (location)。这会按location标签分组计算平均温度。查看设备内存使用率假设ESP32总堆内存约为320KB那么使用率可以这样计算(1 - (esp32_free_heap_bytes / 327680)) * 100。在Grafana中你可以直接在查询框里写这个表达式。计算电压的5分钟滑动平均值avg_over_time(esp32_input_voltage_volts[5m])。这可以平滑掉瞬时波动。5.4 设置告警规则告警可以在Prometheus或Grafana中设置。对于简单的物联网监控在Grafana中设置可能更直观。在刚才的温度图表面板点击标题选择“Edit”。切换到“Alert”标签页。创建告警规则Rule name:HighTemperatureAlertEvaluate every:1m(每分钟评估一次)For:2m(持续2分钟满足条件才触发避免瞬时毛刺)设置条件WHENlast()ofquery(A, 1m, now)IS ABOVE35。这里query(A, ...)指的是你面板中的A查询即温度查询。意思是最近1分钟内最后一个温度值如果高于35度。设置通知渠道你需要先在Grafana的“Alerting” - “Notification channels”中配置好邮件、钉钉、Webhook等渠道。然后在这里选择对应的渠道。保存保存面板和仪表盘。现在当你的ESP32传感器温度持续超过35度达2分钟时Grafana就会触发告警并发送通知。实操心得告警策略的思考。对于物联网设备告警策略需要更谨慎。因为网络抖动可能导致抓取失败up{jobesp32} 0这本身就应该是一个告警。对于传感器数据除了简单的阈值如温度35°C更有效的可能是基于变化率的告警例如“温度在10分钟内上升超过10°C”这可能预示着设备异常发热。PromQL可以写为rate(esp32_temperature_celsius[10m]) 0.0167(即每分钟上升超过1°C)。这种动态阈值比固定阈值更能适应不同环境。6. 性能优化、问题排查与进阶思考项目基本跑通后我们还需要关注一些深层次的问题让整个系统更健壮、更高效。6.1 ESP32端性能与内存优化在资源受限的ESP32上运行HTTP服务器优化是永恒的主题。减少字符串拼接开销getPrometheusMetrics函数中大量的String拼接可能会产生内存碎片。对于固定不变的字符串如HELP、TYPE行和指标名前缀可以考虑使用PROGMEM存储在Flash中或者使用const char*指针。对于变化的数值使用char数组和snprintf进行格式化效率更高。char metricBuffer[256]; // 根据实际需要调整大小 snprintf(metricBuffer, sizeof(metricBuffer), # HELP esp32_temperature_celsius Current temperature.\n # TYPE esp32_temperature_celsius gauge\n esp32_temperature_celsius{device\%s\,sensor\%s\} %.2f\n, device_id, sensor_type, temperature); // 然后将多个buffer的内容通过server.sendContent分段发送避免构建一个巨大的String。控制指标数量只暴露必要的指标。每个指标、每个标签都会增加响应体的大小和Prometheus存储的负担。避免暴露每秒都在快速变化的中间变量。使用ESPAsyncWebServer的onNotFound处理对于不存在的路径返回404避免无效请求消耗资源。监控ESP32自身务必暴露esp32_free_heap_bytes和esp32_wifi_rssi_dbm这类系统指标。它们是你诊断设备是否健康的第一手资料。如果发现内存持续下降可能就有内存泄漏。6.2 常见问题排查实录Prometheus Targets页面显示“DOWN”检查网络连通性在Prometheus服务器上执行curl http://esp32_ip:9100/metrics看是否能获取到数据。如果不能检查防火墙规则、ESP32的Wi-Fi连接是否稳定、IP地址是否正确。检查ESP32日志通过串口监视器查看ESP32启动时是否成功连接Wi-Fi以及服务器是否正常启动。检查是否有内存分配失败的报错。检查端口冲突确保9100端口没有被ESP32上的其他程序占用。能访问/metrics但Prometheus抓取失败抓取超时如果ESP32响应慢增加Prometheus配置中的scrape_timeout。响应格式错误用浏览器或curl访问/metrics端点仔细检查返回的文本格式。常见错误包括指标名或标签名包含非法字符如空格、连字符-指标值不是数字缺少换行符或者HELP/TYPE行格式不正确。Prometheus对格式要求非常严格。Grafana中查询不到数据检查数据源确认Grafana连接的是正确的Prometheus服务器且状态正常。检查时间范围Grafana右上角的时间范围可能设置到了过去而你的设备是刚启动的。调整为“Last 1 hour”或“Last 5 minutes”。检查PromQL在Grafana的“Explore”页面或Prometheus的Graph页面手动输入你的指标名如esp32_temperature_celsius进行查询看是否有数据返回。这有助于区分是数据问题还是面板配置问题。ESP32内存不足频繁重启优化代码使用上述的字符串处理优化方法。减少并发连接AsyncWebServer可以设置最大并发连接数。如果连接数过多可以考虑限制。使用更轻量的库如果ESPAsyncWebServer仍然太重可以考虑使用更原始的WiFiServer和手动解析HTTP请求但这会大大增加代码复杂度。6.3 进阶方向与扩展使用TLS加密通信在生产环境中明文HTTP是不安全的。可以为ESP32配置证书让Prometheus通过HTTPShttps://...抓取数据。这需要ESP32支持TLS并且管理证书有一定复杂度。身份认证在/metrics端点上添加HTTP Basic认证或Bearer Token认证防止未授权的访问。这可以在ESPAsyncWebServer的请求处理函数中实现。远程设备与Pushgateway如果ESP32位于无法被Prometheus Server直接访问的网络如移动蜂窝网络可以考虑使用Pushgateway。ESP32将指标主动推送到Pushgateway然后Prometheus再从Pushgateway拉取。但要注意Pushgateway会破坏Prometheus的拉模型语义如无法检测设备下线通常只用于批处理作业对于常驻设备需谨慎使用。与MQTT集成如果你已有的系统基于MQTT可以做一个适配器。让ESP32仍然发布MQTT消息然后由一个中间服务如Node-RED、Telegraf或自定义服务订阅这些消息并将其转换为Prometheus指标暴露出去。这样既保留了现有的MQTT架构又接入了Prometheus监控体系。让ESP32支持Prometheus监控本质上是在资源受限的嵌入式设备与现代云原生观测体系之间架起一座桥梁。这个过程会遇到性能、网络、格式等各种挑战但一旦打通其带来的运维可见性和管理效率的提升是巨大的。它让小小的物联网设备也能享受到与大型服务器集群同级别的监控能力。