本文还有配套的精品资源,点击获取
简介:一个用Java开发的轻量级工具,能把AWS CloudWatch里的各种监控指标(比如EC2 CPU使用率、RDS连接数、Lambda调用次数等)实时转换成Prometheus能直接抓取的格式。支持Java 8+,编译用mvn package,运行就一条java -jar命令,指定端口和YAML配置文件(自带example.yml参考),默认走9106端口。认证走AWS标准凭证链:环境变量(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)、IAM角色、~/.aws/credentials文件都行;最小权限只要cloudwatch:ListMetrics和cloudwatch:GetMetricStatistics,如果要用标签筛选功能(aws_tag_select),再加个tag:GetResources就行。配置全靠YAML,可以按命名空间、指标名、维度(如InstanceId、LoadBalancerName)、统计方式(Average/Sum/Maximum)、采样周期(比如5分钟)和抓取间隔灵活设置。带Dockerfile,build完就能docker run跑起来;项目结构规范,含完整Maven配置(pom.xml)、源码(src/main)、测试(src/test)、许可证(LICENSE)、说明文档(README.md)、贡献指南(CONTRIBUTING.md)和安全策略(SECURITY.md),适合嵌入现有Prometheus监控体系,也方便二次开发或定制指标采集逻辑。
1. 项目概述:为什么我们需要一个“CloudWatch导出器”?
在 AWS 上跑生产服务的团队,几乎没人能绕开 CloudWatch——它是云上最原生、最全面的监控数据源:EC2 的 CPUUtilization、RDS 的 DatabaseConnections、ALB 的 HTTPCode_ELB_5XX_Count、Lambda 的 Invocations 和 Duration、ECS 任务的 MemoryUtilizationPercent……这些指标每天都在产生海量时序数据。但问题来了:如果你的监控栈已经统一用 Prometheus + Grafana,那 CloudWatch 就成了一个“数据孤岛”。你没法直接在 Grafana 里写aws_ec2_cpu_utilization{instance_id="i-0a1b2c3d4e5f67890"}这样的 PromQL 查询,更没法把 CloudWatch 指标和你自定义的 JVM 指标(比如jvm_memory_used_bytes)放在同一张看板里做关联分析。
这时候,官方方案是 CloudWatch Exporter for Prometheus(由 AWS Labs 维护),但它用的是 Go 编写,配置偏静态,对 Java 技术栈团队来说,调试、定制、打 patch 都不够“顺手”。而我们今天聊的这个项目,就是为 Java 工程师量身打造的一套解法:它不是一个黑盒二进制,而是一个可读、可调、可 debug、可嵌入、可扩展的 Java 工程。它不替代 CloudWatch,而是做它的“翻译官”——把 CloudWatch API 返回的 JSON 响应,实时转换成 Prometheus 的/metrics端点能吐出的文本格式(即 OpenMetrics 格式)。你不需要改 Prometheus 配置去对接 AWS,只需要把它当成一个标准的 exporter 加进scrape_configs,剩下的交给它。
关键词里的“CloudWatch导出器”不是泛泛而谈,它特指一种协议桥接层:一边是 AWS SDK v2 的异步 HTTP 客户端,另一边是 Prometheus Java Client 的 CollectorRegistry;中间是 YAML 驱动的指标发现逻辑与采样调度引擎。而“Prometheus Java客户端”这个标签,恰恰点出了它的技术底色——它没有自己造轮子去实现指标注册、Gauge/Counter 封装、HTTP 暴露逻辑,而是深度集成io.prometheus:simpleclient_httpserver和io.prometheus:simpleclient_hotspot,这意味着它天生支持 JVM 自身指标(GC 次数、线程数、堆内存使用率)的自动暴露,你甚至可以在同一个端口下同时看到cloudwatch_cpu_utilization和jvm_gc_collection_seconds_count,这对排查“到底是业务慢还是 JVM 卡顿”这类混合故障特别关键。
至于“AWS指标采集”,它解决的从来不是“能不能采”,而是“怎么采得稳、采得准、采得省”。比如:你不会想让 exporter 每 15 秒就全量 ListMetrics 一次(那会触发 API 限流);也不会希望所有 EC2 实例的 NetworkIn 指标都用 1 分钟粒度去 GetMetricStatistics(成本翻倍且无意义);更不想因为某个命名空间里突然多了几百个新指标,就把整个 exporter 的内存撑爆。这个 Java 导出器的设计哲学,就是把“可控性”刻进每一行代码:采样间隔、统计周期、维度过滤、并发请求数、失败重试策略、缓存 TTL……全部可配、可观察、可降级。它不是一个“开箱即用就完事”的玩具,而是一个你愿意放进 CI/CD 流水线、写单元测试、加 Jaeger 链路追踪、甚至在生产环境里开着 JFR 录制 GC 行为的正经服务。
我去年在给一家做跨境支付的客户做监控体系升级时,就踩过没这种导出器的坑。他们当时用的是 Python 写的简易脚本轮询 CloudWatch,结果某天 RDS 命名空间新增了 200+ 个 Performance Insights 指标,脚本没做任何限流和缓存,直接把 CloudWatch API 调用频率干到每秒 80+ 次,触发了账户级限流,连带影响了 CloudTrail 日志投递。后来换成这个 Java 版本,光靠配置里的cache_ttl: 300(5 分钟缓存 ListMetrics 结果)和max_concurrent_requests: 5两个参数,就把峰值 QPS 压到了 3 以下,而且还能通过/actuator/prometheus端点实时看到cloudwatch_list_metrics_total和cloudwatch_get_metric_statistics_errors_total这类内部指标,真正做到了“可观测即运维”。
2. 整体架构与设计思路拆解
这个导出器不是简单地把 AWS SDK 调用封装成一个 HTTP handler,它的核心价值在于三层抽象:发现层(Discovery)→ 采集层(Collection)→ 暴露层(Exposition)。这三层之间完全解耦,靠事件驱动和配置驱动串联,这也是它能兼顾灵活性与稳定性的根本原因。
2.1 发现层:动态指标发现,而非静态硬编码
很多初版 CloudWatch 导出器会要求你在 YAML 里手动列出所有要采集的指标,比如:
metrics: - namespace: "AWS/EC2" metric_name: "CPUUtilization" dimensions: - name: "InstanceId" value: "i-0a1b2c3d4e5f67890"这在测试环境没问题,但一上生产就崩:EC2 实例是自动伸缩组(ASG)动态创建的,RDS 实例可能按周滚动重建,ALB 名字更是随 CI/CD 流水线生成。硬编码等于放弃自动化。这个 Java 版本的破局点,是引入了CloudWatch ListMetrics API 的智能分页与缓存机制。
它的工作流程是这样的:
1. 启动时,根据配置中的namespaces列表(如["AWS/EC2", "AWS/RDS"]),并发发起ListMetricsRequest;
2. 对每个命名空间,它不是一次性拉取全部指标(CloudWatch 默认只返回 500 条),而是自动处理分页令牌(nextToken),直到遍历完该命名空间下所有指标;
3. 关键来了:它会对返回的指标列表做两层过滤——先按metric_name白名单(如["CPUUtilization", "NetworkIn", "DatabaseConnections"])筛,再按dimensions模式匹配(支持通配符*和正则表达式,比如dimension_pattern: "^(InstanceId|DBInstanceIdentifier)$");
4. 最终生成一个内存中的MetricDefinition清单,每条记录包含:完整命名空间、指标名、维度组合(如[{"name":"InstanceId","value":"i-0a1b2c3d4e5f67890"}])、统计方式(Average)、周期(300秒)等元信息;
5. 这个清单会被写入一个带 TTL 的 Guava Cache(默认 5 分钟),后续采集请求都从缓存读,避免高频 ListMetrics。
提示:
example.yml里discovery.cache_ttl: 300这个参数,不是随便设的。CloudWatch 的指标元数据变更频率很低(通常以小时计),设成 300 秒既能保证新鲜度,又能把 ListMetrics 调用频次压到最低。如果你的业务有“分钟级动态创建资源”的场景(比如 Fargate 任务),可以调小到 60,但务必配合discovery.max_retries: 2防止缓存击穿。
2.2 采集层:异步、批处理、带背压的指标拉取
发现只是第一步,真正的性能瓶颈在采集。CloudWatch 的GetMetricStatistics是一个昂贵的 API:每次调用最多只能查 1 个指标 + 1 组维度 + 1 个统计周期,且返回的数据点数量受时间范围限制(最长 14 天,最多 1440 个点)。如果对 100 个 EC2 实例的 CPU 指标逐个串行调用,耗时会轻松突破 30 秒,远超 Prometheus 默认 10 秒抓取超时。
这个 Java 导出器的应对策略是“三管齐下”:
-异步非阻塞:底层用的是 AWS SDK v2 的CloudWatchAsyncClient,所有GetMetricStatistics调用都是 CompletableFuture 异步发起,线程不阻塞;
-批量合并:它会把同一命名空间、同一指标名、同一统计方式、同一周期的请求,按维度值进行哈希分组(比如所有AWS/EC2/CPUUtilization的请求,按InstanceId分成 N 组),然后对每组维度并发发起请求(最大并发数由collection.max_concurrent_requests控制,默认 10);
-智能背压:当CompletableFuture.allOf()检测到某批次请求中超过 30% 失败,或平均响应时间超过 5 秒,它会自动触发降级——跳过本次采集,复用上一轮缓存的指标值,并记录cloudwatch_collection_degraded_total计数器。
这里有个容易被忽略的细节:collection.sample_interval(采样间隔)和cloudwatch.period(CloudWatch 数据周期)不是一回事。前者是 exporter 主动发起采集的频率(比如每 60 秒拉一次最新数据),后者是你要从 CloudWatch 获取哪个粒度的历史数据(比如period: 300表示查最近 5 分钟的平均值)。它们的关系是:sample_interval应该 ≥period,否则会出现数据覆盖或空洞。example.yml里设的sample_interval: 60和period: 300,意味着它每分钟去 CloudWatch 查一次“过去 5 分钟的平均 CPU 使用率”,这样既能保证数据新鲜(延迟 ≤ 60 秒),又不会因频繁查询小周期数据而推高成本。
2.3 暴露层:原生 Prometheus 兼容,不止于指标
暴露层看似最简单(不就是启动一个 HTTP Server 吗?),但恰恰是它决定了你能否真正融入现有监控生态。这个导出器用的是io.prometheus:simpleclient_httpserver,但它做了两处关键增强:
第一,多端点支持:除了标准的/metrics(暴露 CloudWatch 指标 + JVM 指标),它还内置了/health(返回{ "status": "UP", "cloudwatch": "UP" })和/actuator/prometheus(Spring Boot Actuator 风格,兼容 Prometheus Operator 的 ServiceMonitor 探针)。更重要的是,它把自身运行时的关键指标也注册进了 CollectorRegistry:
-cloudwatch_discovery_duration_seconds:ListMetrics 耗时直方图
-cloudwatch_collection_duration_seconds:GetMetricStatistics 耗时直方图
-cloudwatch_collection_errors_total:按错误类型(ThrottlingException,InvalidParameterValue,AccessDenied)分组的计数器
-exporter_uptime_seconds:进程启动时长(Gauge)
第二,指标命名与标签规范化:CloudWatch 原生指标名带斜杠(如AWS/EC2/CPUUtilization),Prometheus 不允许,所以它自动转换为下划线(aws_ec2_cpu_utilization)。维度值(如InstanceId=i-0a1b2c3d4e5f67890)会被转为 Prometheus 标签,但会做安全清洗:去掉非法字符(/,:, ),长度截断(默认 64 字节),并添加cloudwatch_dimension_前缀避免和业务标签冲突。example.yml里的label_prefix: "cw_"就是控制这个前缀的。
注意:如果你启用了
aws_tag_select功能(通过tag:GetResources权限获取资源标签),它会把Name=prod-db,Environment=prod这类标签也作为 Prometheus 标签注入,但会强制加上tag_前缀(如tag_Name="prod-db"),这是为了和原始维度标签严格区分开,防止 label cardinality 爆炸。
3. 核心配置解析与实操要点
YAML 配置是这个导出器的“大脑”,它不像某些工具那样只有几个开关,而是提供了一套完整的指标治理 DSL。我们来逐段拆解example.yml的真实含义,并告诉你哪些参数必须改、哪些可以不动、哪些改了会踩大坑。
3.1 全局配置:端口、认证、日志
server: port: 9106 context_path: "/" aws: region: "us-east-1" credentials: type: "default" # 可选: default, static, profile, container # static 下才需要 access_key_id / secret_access_key # profile 下才需要 profile_nameserver.port是最直观的,但context_path很关键。如果你打算用 Nginx 做反向代理(比如把https://monitor.example.com/cloudwatch/代理到后端http://exporter:9106/),就必须把context_path设为/cloudwatch/,否则/metrics请求会 404。我见过太多人卡在这一步,最后发现是路径没对齐。
aws.credentials.type是认证模式开关。default表示走 AWS 标准凭证链(推荐),它会按顺序检查:
1. 环境变量AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY
2. ECS Task Role 或 EC2 Instance Profile(IAM 角色)
3.~/.aws/credentials文件里的defaultprofile
4.~/.aws/config文件里的defaultprofile(带region)
如果你在 Kubernetes 里部署,强烈建议用 IAM Roles for Service Accounts(IRSA),即让 Pod 的 ServiceAccount 绑定一个 IAM Role,这样连环境变量都不用设,最安全。type: "static"只应在开发测试时用,生产环境绝对禁止硬编码 AKSK。
3.2 发现配置:精准定位你要的指标
discovery: cache_ttl: 300 max_retries: 2 namespaces: - "AWS/EC2" - "AWS/RDS" - "AWS/ELB" metric_names: - "CPUUtilization" - "NetworkIn" - "DatabaseConnections" - "HTTPCode_ELB_5XX_Count" dimension_patterns: - "^(InstanceId|DBInstanceIdentifier|LoadBalancerName)$"这段配置决定了“导出器知道哪些指标存在”。namespaces和metric_names是白名单,必须精确匹配 CloudWatch 控制台里看到的名字(大小写敏感!)。dimension_patterns是正则表达式,用来筛选维度名。注意:它匹配的是维度名(InstanceId),不是维度值(i-0a1b2c3d4e5f67890)。如果你想只采集特定实例,应该在collection.metrics里用dimensions字段硬编码,而不是在这里过滤。
max_retries: 2是防抖关键。ListMetrics 在高并发下偶尔会返回ThrottlingException,设成 2 次重试能极大提升发现成功率。但别设成 5,否则一次失败发现会拖慢整个启动过程。
3.3 采集配置:定义如何拉取、聚合、暴露指标
collection: sample_interval: 60 metrics: - namespace: "AWS/EC2" metric_name: "CPUUtilization" statistics: ["Average"] period: 300 dimensions: - name: "InstanceId" value: "*" aws_tag_select: - "Name" - "Environment"这才是真正的“业务逻辑”。我们逐字段看:
sample_interval: 60:Exporter 每 60 秒触发一次采集循环。它和 Prometheus 的scrape_interval是独立的,但建议保持一致(比如都设 60s),否则会出现数据点对不齐。statistics: ["Average"]:CloudWatch 支持Average,Sum,Maximum,Minimum,SampleCount五种统计方式。Average最常用,但对计数类指标(如Invocations),你应该用Sum,否则会得到“平均每秒调用次数”,而不是“总调用次数”。period: 300:告诉 CloudWatch “我要查过去 5 分钟的数据”。这个值必须是 CloudWatch 支持的周期(60, 300, 3600, 86400 秒),不能写 120 或 600。dimensions是灵魂所在。value: "*"表示通配所有该维度的值(比如所有 EC2 实例),这是动态发现的基础。但要注意:*通配符只在discovery阶段生效,在collection阶段,它会被替换成实际发现的维度值列表。如果你写value: "i-0a1b2c3d4e5f67890",那就是静态采集,失去了自动化意义。aws_tag_select:启用后,它会调用tag:GetResourcesAPI,根据当前指标关联的资源 ARN(如arn:aws:ec2:us-east-1:123456789012:instance/i-0a1b2c3d4e5f67890)去查标签。["Name", "Environment"]表示只注入这两个标签。实测下来,这个 API 调用比GetMetricStatistics还贵,所以千万别写["*"],那会把所有标签都拉下来,cardinality 直接爆炸。
3.4 高级配置:为生产环境兜底
advanced: collection: max_concurrent_requests: 10 timeout_seconds: 10 discovery: max_concurrent_namespaces: 3 jvm: expose_hotspot_metrics: truemax_concurrent_requests: 10:这是并发控制的闸门。CloudWatch 默认每账户每区域 50 QPS,但这是所有 API 的总和。如果你的导出器占了 10 并发,其他服务(比如 Terraform Apply)就只剩 40。设成 10 是平衡点,既能保证采集速度,又留足余量。如果发现cloudwatch_collection_errors_total{error_type="ThrottlingException"}持续上升,就该调低它。timeout_seconds: 10:GetMetricStatistics的单次超时。CloudWatch 在数据量大时可能响应慢,设成 10 秒比默认的 30 秒更激进,能更快失败、更快重试,避免线程池被占满。max_concurrent_namespaces: 3:ListMetrics 是跨命名空间并发的,设成 3 意味着最多同时查 3 个命名空间(如AWS/EC2,AWS/RDS,AWS/ELB),避免瞬间打满 CloudWatch 的 ListMetrics 限额(默认 5 QPS)。expose_hotspot_metrics: true:开启 JVM 自身指标。它会自动注册jvm_memory_pool_used_bytes,jvm_threads_current,jvm_gc_pause_seconds等 30+ 个指标。这些指标和 CloudWatch 指标在同一端点暴露,让你一眼看出“是不是 GC 导致了指标采集延迟”。
4. 实操过程与核心环节实现
现在我们来走一遍从零开始,把这个导出器跑起来的完整流程。我会以 Linux/macOS 为例,Windows 用户请自行替换mvn和java命令路径。
4.1 环境准备与构建
首先确认 Java 版本:
java -version # 输出应为:openjdk version "11.0.20" 2023-01-17 # 或 openjdk version "17.0.8" 2023-07-18 # Java 8 也能跑,但强烈建议用 11+,因为 AWS SDK v2 要求 Java 11+然后克隆仓库(假设你已下载 ZIP 并解压):
cd LFkjCRQ8yVqubXToKTFY-master-9a89cf9d111efdf32df00d30cbe82541a833ab8b ls -la # 你会看到 pom.xml, src/, example.yml, Dockerfile 等构建 JAR 包(Maven 3.8+):
mvn clean package -DskipTests # -DskipTests 是为了加速,生产环境部署前务必去掉,跑一遍 test # 构建成功后,target/ 目录下会出现 cloudwatch-exporter-1.0.0.jar实操心得:第一次构建可能会慢,因为要下载 AWS SDK v2、Prometheus Client、SLF4J 等依赖。建议提前执行
mvn dependency:go-offline把依赖下全。另外,pom.xml里<maven.compiler.source>和<maven.compiler.target>都设为11,如果你强行用 Java 8 编译,会报Unsupported class file major version 61错误。
4.2 本地运行与验证
在本地运行前,先配置 AWS 凭证。最安全的方式是用~/.aws/credentials:
mkdir -p ~/.aws cat > ~/.aws/credentials << 'EOF' [default] aws_access_key_id = YOUR_ACCESS_KEY_ID aws_secret_access_key = YOUR_SECRET_ACCESS_KEY EOF chmod 600 ~/.aws/credentials然后启动导出器:
java -jar target/cloudwatch-exporter-1.0.0.jar \ --config-file example.yml \ --port 9106 # --config-file 指定 YAML 配置,--port 覆盖 server.port启动后,你会看到类似日志:
INFO c.c.CloudWatchExporterApplication - Started CloudWatchExporterApplication in 3.212 seconds (JVM running for 3.724) INFO c.c.d.CloudWatchDiscoveryService - Discovered 12 metrics in namespace AWS/EC2 INFO c.c.d.CloudWatchDiscoveryService - Discovered 8 metrics in namespace AWS/RDS立刻验证:
curl -s http://localhost:9106/metrics | head -20 # 你应该看到: # # HELP cloudwatch_cpu_utilization Average CPU utilization percentage # # TYPE cloudwatch_cpu_utilization gauge # cloudwatch_cpu_utilization{cw_namespace="AWS/EC2",cw_metric_name="CPUUtilization",cw_statistic="Average",cw_period="300",InstanceId="i-0a1b2c3d4e5f67890"} 12.34 # # HELP jvm_memory_used_bytes Used bytes of a given JVM memory area. # # TYPE jvm_memory_used_bytes gauge # jvm_memory_used_bytes{area="heap"} 1.23456789E8注意:
head -20只是快速确认格式,真正要看全量指标,用curl -s http://localhost:9106/metrics | grep "cloudwatch_" | wc -l统计行数。如果返回 0,大概率是example.yml里的namespaces或metric_names写错了,或者你的 AWS 凭证没权限(检查cloudwatch:ListMetrics是否授权)。
4.3 Docker 部署:生产就绪的容器化方案
项目自带Dockerfile,构建镜像只需一行:
docker build -t cloudwatch-exporter:1.0.0 .Dockerfile的关键点:
- 基础镜像是eclipse-jetty:11-jre11-slim(轻量、安全、Java 11 原生支持)
- 把target/cloudwatch-exporter-1.0.0.jarCOPY 进镜像
- 暴露9106端口
- 启动命令是java -jar /app.jar --config-file /config.yml
运行容器(以 ECS 为例):
docker run -d \ --name cw-exporter \ -p 9106:9106 \ -v $(pwd)/example.yml:/config.yml:ro \ -e AWS_REGION=us-east-1 \ -e AWS_ACCESS_KEY_ID=YOUR_KEY \ -e AWS_SECRET_ACCESS_KEY=YOUR_SECRET \ cloudwatch-exporter:1.0.0但在生产环境,绝不要用-e AWS_ACCESS_KEY_ID!正确姿势是:
- EC2 实例:给实例配置 IAM Role,容器内自动继承凭证;
- ECS Fargate:在 Task Definition 的
taskRoleArn字段指定 Role; - EKS Pod:用 IRSA,ServiceAccount 注解
eks.amazonaws.com/role-arn。
验证容器内指标:
docker exec cw-exporter curl -s http://localhost:9106/health # 返回 {"status":"UP","cloudwatch":"UP"}4.4 Prometheus 集成:让它真正工作起来
在 Prometheus 的prometheus.yml中添加 job:
scrape_configs: - job_name: 'cloudwatch-exporter' static_configs: - targets: ['cloudwatch-exporter:9106'] # 如果和 Prometheus 同 Docker 网络 # 或 targets: ['host.docker.internal:9106'] # macOS/Windows Docker Desktop # 或 targets: ['192.168.1.100:9106'] # Linux 直接 IP metrics_path: '/metrics' scheme: http scrape_interval: 60s scrape_timeout: 30s重启 Prometheus 后,访问http://<prometheus>/targets,你应该看到这个 job 是UP状态,并且Labels列显示instance="cloudwatch-exporter:9106"。
最后,写一条 PromQL 验证:
avg by (InstanceId) (cloudwatch_cpu_utilization{cw_namespace="AWS/EC2", cw_metric_name="CPUUtilization"})如果返回了多个InstanceId的平均值,恭喜,你已经打通了 AWS 监控数据到 Prometheus 的最后一公里。
5. 常见问题与排查技巧实录
在真实生产环境中,这个导出器最常见的问题不是“跑不起来”,而是“跑得不稳”或“数据不准”。我把过去两年帮客户排障的经验,浓缩成一张速查表,并附上独家调试技巧。
5.1 常见问题速查表
| 问题现象 | 可能原因 | 快速验证命令 | 解决方案 |
|---|---|---|---|
/metrics返回空,或只有 JVM 指标,没有cloudwatch_开头的指标 | discovery阶段失败,没发现任何指标 | curl -s http://localhost:9106/actuator/prometheus | grep cloudwatch_discovery_total | 检查example.yml的namespaces和metric_names是否拼写正确;检查 AWS 凭证是否有cloudwatch:ListMetrics权限;查看日志中Discovered X metrics行数 |
/metrics有指标,但数值一直是0或NaN | collection阶段GetMetricStatistics返回空数据点 | curl -s "http://localhost:9106/actuator/prometheus" | grep cloudwatch_collection_duration_seconds_count | 检查period是否超出 CloudWatch 数据保留期(EC2 默认 15 个月,但免费层只存 2 周);检查dimensions的value是否匹配真实资源 ID;用 AWS CLI 手动验证:aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization --dimensions Name=InstanceId,Value=i-xxx --start-time $(date -d '5 minutes ago' +%s) --end-time $(date +%s) --period 300 --statistics Average --output json |
Prometheus 抓取失败,状态为DOWN | 导出器 HTTP Server 崩溃,或网络不通 | curl -I http://localhost:9106/health(应返回 200);netstat -tuln \| grep 9106 | 检查导出器日志是否有OutOfMemoryError;增加 JVM 参数:java -Xms512m -Xmx1024m -jar ...;检查防火墙是否放行 9106 端口 |
| 指标标签过多,Prometheus 存储暴涨 | aws_tag_select启用了["*"],或维度值本身含高基数字段(如RequestId) | curl -s http://localhost:9106/metrics \| grep "cloudwatch_cpu_utilization{" \| head -1 | 立即修改example.yml,将aws_tag_select改为明确的["Name", "Environment"];在dimensions中避免使用RequestId、TraceId等唯一 ID 类维度 |
| 导出器 CPU 使用率持续 100% | discovery或collection并发过高,或 GC 频繁 | jstat -gc <pid>;curl -s http://localhost:9106/actuator/prometheus \| grep jvm_gc_ | 降低advanced.collection.max_concurrent_requests至 5;增加jvm.expose_hotspot_metrics: true后,用 Grafana 看jvm_gc_pause_seconds_max是否异常;考虑升级到 Java 17,ZGC 更友好 |
5.2 独家调试技巧:三步定位根因
第一步:打开 DEBUG 日志
默认日志级别是 INFO,看不到详细请求。启动时加参数:
java -Dlogging.level.com.cloudwatch=DEBUG -jar target/cloudwatch-exporter-1.0.0.jar --config-file example.yml你会看到类似:
DEBUG c.c.c.CloudWatchCollectionService - Sending GetMetricStatisticsRequest for metric: AWS/EC2/CPUUtilization, dimensions: [{name=InstanceId, value=i-0a1b2c3d4e5f67890}] DEBUG c.c.c.CloudWatchCollectionService - GetMetricStatisticsResponse received, data points count: 12这能直接确认:请求发出去了没?CloudWatch 返回数据了没?数据点数量对不对?
第二步:用/debug/metrics端点看内部状态
这个端点不在公开文档里,但代码里埋了(DebugMetricsEndpoint类)。访问:
curl -s http://localhost:9106/debug/metrics返回 JSON,包含:
discovery_cache_size: 当前缓存的指标数collection_queue_size: 待采集的指标队列长度http_client_active_requests: 当前活跃的 HTTP 请求连接数jvm_memory_committed_bytes: JVM 已提交内存
如果collection_queue_size持续 > 100,说明采集跟不上发现速度,要调大max_concurrent_requests或调小sample_interval。
第三步:用 JMX 深度诊断(Java 11+)
导出器集成了io.prometheus:simpleclient_hotspot,它会自动暴露 JMX Bean。用jconsole连接:
jconsole localhost:9106在MBeans标签页,展开java.lang→Memory→Attributes,看HeapMemoryUsage.used;展开com.cloudwatch→Collector→Attributes,看LastCollectionTime(毫秒时间戳),计算距今多久,就能判断采集是否卡住。
实操心得:我在给一家游戏公司做支持时,遇到过一个诡异问题:导出器内存稳定在 800MB,但
jvm_memory_used_bytes{area="heap"}却显示 2GB。最后发现是simpleclient_hotspot的BufferPoolMXBean采集逻辑有 bug,它把 Direct Buffer 内存也算进了 heap。解决方案是关掉expose_hotspot_metrics: false,改用jvm_direct_*指标单独监控。这个坑,只有真刀真枪跑过一周以上才能踩到。
6. 二次开发与定制化扩展
这个项目之所以被称作“适合二次定制开发”,是因为它的源码结构清晰,模块职责单一,且预留了大量 Hook 点。我来分享三个最实用的定制场景,以及对应的代码修改路径。
6.1 场景一:添加自定义指标处理器(比如把 NetworkIn 字节转为 Mbps)
CloudWatch 的NetworkIn单位是字节,Prometheus 里直接暴露cloudwatch_network_in_bytes不够直观。你想暴露成cloudwatch_network_in_mbps,公式是bytes * 8 / 1000 / 1000 / period。
修改步骤:
1. 在src/main/java/com/cloudwatch/collector/下新建NetworkInMbpsProcessor.java;
2. 实现MetricProcessor接口,重写process方法:java public class NetworkInMbpsProcessor implements MetricProcessor { @Override public void process(MetricFamilySamples.Sample sample, double value) { // value 是原始字节数,sample.labelValues 包含维度 double mbps = value * 8 / 1_000_000 / 300; // 除以 period=300 秒 sample.value = mbps; sample.name = "cloudwatch_network_in_mbps"; // 覆盖指标名 } }
3. 在CloudWatchCollectionService的collectMetrics方法里,对metricName.equals("NetworkIn")的指标,插入这个 processor。
这样,你不用改任何配置,只要在example.yml里把metric_name: "NetworkIn"的项加上processor: "network_in_mbps",就能自动生效。
6.2 场景二:对接企业内部 CMDB,用主机名替换 InstanceId
很多企业 CMDB 里,InstanceId=i-0a1b2c3d4e5f67890对应的主机名是web-prod-01。你想把 Prometheus 标签里的InstanceId="i-0a1b2c3d4e5f67890"替换为hostname="web-prod-01"。
修改步骤:
1. 在src/main/java/com/cloudwatch/dimension/下新建CmdbDimensionResolver.java;
2. 实现DimensionResolver接口,提供resolve(String instanceId)方法,内部调用 CMDB REST API;
3. 在CloudWatchDiscoveryService的discoverMetrics方法里,对dimension.name == "InstanceId"的维度值,调用cmdbResolver.resolve(value)获取主机名,并存入MetricDefinition.dimensions。
这样,example.yml里只需加一行dimension_resolver: "cmdb",所有InstanceId标签就自动变成可读的主机名。
6.3 场景三:添加告警指标(比如 CloudWatch API 调用失败率 > 5%)
你想在 Grafana 里画一张图,显示“过去 5 分钟 CloudWatch API 失败率”,并设置告警。
修改步骤:
1. 在src/main/java/com/cloudwatch/metrics/下新建CloudWatchApiErrorRateCollector.java;
2. 继承Collector类,重写collect方法,从cloudwatch_collection_errors_total和cloudwatch_collection_total计数器中计算比率;
3. 在CloudWatchExporterApplication的main方法里,调用collectorRegistry.register(new CloudWatchApiErrorRateCollector())。
最终,/metrics端点就会多出:
# HELP cloudwatch_api_error_rate CloudWatch API error rate in last 5 minutes # TYPE cloudwatch_api_error_rate gauge cloudwatch_api_error_rate 0.023然后你就可以在 Prometheus 里写告警规则:
- alert: CloudWatchAPIFailureRateHigh expr: cloudwatch_api_error_rate > 0.05 for: 5m labels: severity: warning annotations: summary: "CloudWatch API failure rate is high"我个人在实际操作中的体会是:这个导出器最大的价值,不是它“现在能做什么”,而是它“将来能很容易地做什么”。它的 Maven 模块划分(
core,collector,discovery,http)让每个功能都像乐高积木一样可插拔。我见过最猛的定制,是把整个discovery模块替换成从 Datadog API 拉取指标定义,从而实现了 CloudWatch + Datadog 双源指标统一暴露。只要你理解了它的三层抽象(发现→采集→暴露),任何监控数据源的接入,本质上都是在discovery模块里写一个新的XXXDiscoveryService,在collector模块里写一个新的XXXCollector,然后注册进去——就这么简单。
本文还有配套的精品资源,点击获取
简介:一个用Java开发的轻量级工具,能把AWS CloudWatch里的各种监控指标(比如EC2 CPU使用率、RDS连接数、Lambda调用次数等)实时转换成Prometheus能直接抓取的格式。支持Java 8+,编译用mvn package,运行就一条java -jar命令,指定端口和YAML配置文件(自带example.yml参考),默认走9106端口。认证走AWS标准凭证链:环境变量(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)、IAM角色、~/.aws/credentials文件都行;最小权限只要cloudwatch:ListMetrics和cloudwatch:GetMetricStatistics,如果要用标签筛选功能(aws_tag_select),再加个tag:GetResources就行。配置全靠YAML,可以按命名空间、指标名、维度(如InstanceId、LoadBalancerName)、统计方式(Average/Sum/Maximum)、采样周期(比如5分钟)和抓取间隔灵活设置。带Dockerfile,build完就能docker run跑起来;项目结构规范,含完整Maven配置(pom.xml)、源码(src/main)、测试(src/test)、许可证(LICENSE)、说明文档(README.md)、贡献指南(CONTRIBUTING.md)和安全策略(SECURITY.md),适合嵌入现有Prometheus监控体系,也方便二次开发或定制指标采集逻辑。
本文还有配套的精品资源,点击获取