4_AWS日志搜索方案
我们给一个跑在 AWS ECS Fargate 上的 Go 服务做了一套日志搜索方案。链路很短:CloudWatch Logs → Firehose → S3 → EC2 → EBS → ripgrep。日志最终落到一台小 EC2 的本地磁盘上,开发者 SSH 进去用 rg 直接全文搜索。
1. 背景:我们的需求
服务叫 my-service(化名),是一个 Go 写的实时 API:
- 部署在 AWS ECS Fargate(AWS 托管的 Docker 调度平台)
- 每天日志量约 200 MB(不大但不小)
- 调试时主要靠
req_id、用户 ID、API 路径等关键词捞日志 - 不要求实时(接受 5–10 分钟延迟)
- 不要求超长历史(最近 7 天足够;更早的去 CloudWatch Logs 兜底)
需求清晰之后,要做的就是从一堆候选方案中挑出性价比最高的那一个。
2. 选型:10 个方案横评
我们系统地评估了 10 个候选方案,最终选了第 10 个。下面逐个讲为什么放弃前 9 个。
2.1 ELK / OpenSearch — 太重
业界经典的日志搜索栈。AWS 托管版叫 OpenSearch Service,最小可用集群(3 节点 t3.small.search)每月起步约 200 美元(含实例 + EBS 存储)。OpenSearch Serverless 更贵——最小配置 4 个 OCU(2 索引 + 2 搜索),每 OCU 0.24 美元/小时,月费约 700 美元起。
我们用不上全文索引、相关性评分、聚合分析、Kibana 仪表盘。杀鸡用牛刀。
2.2 Grafana Loki — 托管贵、自托管重
号称 “ 轻量级 ELK”,只索引标签不索引正文。Grafana Cloud 按摄入量计费:免费额度 50 GB/月,超出后 0.50 美元/GB。我们 6 GB/月刚好在免费额度内,但一旦日志量翻几倍就得掏钱。自托管需要 Kubernetes + 对象存储 + 一堆 YAML 配置。没简化反而更复杂。
2.3 Datadog / Splunk / New Relic — 商业 SaaS 太贵
按摄入 GB 收费。Datadog Log Management 摄入价 0.10 美元/GB,但要用搜索和分析功能需要 Indexed Logs,起步 1.70 美元/百万事件/月。Splunk Cloud 按摄入量 GB 计费,企业合同制。New Relic 日志摄入 0.30 美元/GB。
这些产品强在 APM + Logs + Metrics 三合一的关联体验,但我们只需要 grep 日志。对内部调试工具不划算。
2.4 CloudWatch Logs Insights — 慢、正则弱
AWS 原生的日志查询服务,不用搬数据。每次查询按扫描数据量收费 0.005 美元/GB。查询语法是 CW 自己的,不支持 PCRE 正则。每次 grep 一周日志要 5–10 秒等结果。够用但不爽。
2.5 CloudWatch Logs Live Tail — 只能看实时
2023 年推出的功能,类似 tail -f。支持按关键词过滤和高亮,近实时流式输出。按会话时长计费 0.01 美元/分钟。
致命限制:只能看当前正在写入的日志,不能搜索历史。适合实时盯着看,不适合事后排查。而且单会话最长 3 小时,超过 500 条/秒会采样。只能当辅助工具。
2.6 S3 + Athena — 启动慢,体验糟
把日志归档到 S3,用 Athena 跑 SQL 查询。Athena 按扫描数据量收费 5 美元/TB。启动延迟 5–10 秒(要编译查询计划、启动执行引擎)。
如果日志是 Parquet/ORC 格式且做了分区,Athena 的效率很高——但日志通常是 JSONL/纯文本,扫描效率低。适合分析、不适合调试时随手 grep。
2.7 S3 Select — 行级过滤但限制多
在 S3 侧直接做行级过滤,不启动 Athena。按扫描量 + 返回量收费(扫描 0.002 美元/GB,返回 0.0007 美元/GB)。
限制:只支持 CSV/JSON/Parquet 格式,不支持 gzip 内嵌多 member(我们的日志格式),单文件最大 128 MB(非压缩),SQL 子集极小(不支持 JOIN、子查询、正则)。对我们的场景几乎不可用。
2.8 Fluent Bit 直推 S3 — 绕过 CW Logs 省钱
用 Fluent Bit 替代 awslogs 驱动,从容器 stdout 直接推到 S3,省掉 CloudWatch Logs 的 0.50 美元/GB 摄入费。Fluent Bit 的 S3 output 插件支持 gzip 压缩、按时间分区、自动 multipart upload。
为什么没选:ECS Fargate 使用 Fluent Bit 需要配 FireLens(AWS 的 sidecar 日志路由器),多一个 sidecar 容器要额外分配 CPU/内存。而且我们的 CW Logs 已经存在(合规要求),再加一路 Fluent Bit 等于维护两套日志 pipeline。省下的钱不够覆盖运维复杂度。
2.9 Vector / Fluentd — 更重的 Pipeline
Vector(Rust 写)和 Fluentd(Ruby 写)是通用的日志 pipeline 工具。功能比 Fluent Bit 更丰富,但也更重。在 Fargate 上跑 sidecar 的问题和 Fluent Bit 一样。自建 aggregator 集群又回到了 “ 重基础设施 “ 的老路。不适合我们的极简需求。
2.10 最终方案:把日志投到一台小 EC2,本地 Grep
思路简单粗暴:
把生产日志同步一份到一台 EC2 的本地磁盘,用 ripgrep 直接 grep 文件。
ripgrep(rg)是 Rust 写的 grep 替代品,原生支持 gzip 解压、并行搜索,单核可达 200 MB/s。一台 2 vCPU 的 EC2 + 100 GB 本地磁盘,足够装 7 天热数据。
这种思路能跑通的前提有三个:日志规模在 GB 量级(不是 TB)、允许少量延迟、且只服务内部开发者。我们都满足。
2.11 结构化对比
| 方案 | 月费估算(6 GB/月) | 查询延迟 | 运维复杂度 | 查询能力 | 扩展上限 |
|---|---|---|---|---|---|
| OpenSearch 托管 | ~$200 起 | 秒级 | 中(集群管理) | 全文索引 + 聚合 | TB 级 |
| OpenSearch Serverless | ~$700 起 | 秒级 | 低 | 全文索引 + 聚合 | TB 级 |
| Grafana Loki 托管 | 免费~$3 | 秒级 | 低 | 标签过滤 + LogQL | TB 级 |
| Datadog Log Mgmt | ~$10+ | 秒级 | 无(SaaS) | 全文 + facet | TB 级 |
| CW Logs Insights | ~$0.03/次 | 5–10 秒 | 无 | CW 查询语法 | TB 级 |
| CW Live Tail | ~$0.01/分 | 实时 | 无 | 关键词过滤 | 仅实时流 |
| S3 + Athena | <$0.01/次 | 5–10 秒 | 低 | SQL | PB 级 |
| S3 Select | <$0.01/次 | 秒级 | 低 | SQL 子集 | 单文件 128 MB |
| Fluent Bit + S3 | ~$20(EC2) | 分钟级 | 中(sidecar) | grep | GB 级 |
| EC2 + ripgrep(已选) | $21 | 亚秒 | 低 | 正则全文 | GB 级 |
方案选定后,下一步是把整条数据流串起来。
3. 架构总览
flowchart LR
A[ECS Task<br/>Go App stdout] -->|awslogs driver| B[CloudWatch Logs<br/>Log Group]
B -->|Subscription Filter| C[Kinesis Data Firehose<br/>5-min buffer / GZIP]
C -->|S3 PUT| D[S3 Transit Bucket<br/>1-day lifecycle]
D -->|aws s3 cp<br/>via Gateway Endpoint| E[EC2 t4g.small<br/>parse-firehose.py]
E -->|append per-hour gzip| F[EBS 100GB gp3<br/>/logs/yyyymmdd/]
G[Developer<br/>laptop] -->|aws ssm start-session| E
E -->|rg -za| F3.1 第一站:App Stdout → CloudWatch Logs
Go 应用用 log/slog 写到 stdout:
1 | slog.Info("api request", "method", "POST", "path", "/api/v1/heartbeat", "req_id", id) |
ECS Fargate 的 task definition 配置 awslogs 日志驱动:
1 | "logConfiguration": { |
ECS 容器引擎自动收集 stdout/stderr,分批推到 CloudWatch Logs(CW Logs)的对应 Log Group。这一步应用方本来就有——所有合规的容器化应用都会配,不是我们这次新加的。
CW Logs 计费:0.50 美元/GB 摄入费 + 0.03 美元/GB·月 存储费。retention 默认 30 天。
日志进了 CW Logs,下一步是把它 “ 分流 “ 出来——既要保留 CW Logs 原数据满足合规,又要能进入我们自己的搜索链路。
3.2 第二站:Subscription Filter → Firehose → S3
Subscription Filter
CW Logs 有个叫 subscription filter 的功能:可以把一个 Log Group 里的实时日志流旁路一份到下游(Lambda / Kinesis Streams / Firehose 三选一)。纯转发,不影响原始 CW Logs 数据。
为什么选 Firehose
Kinesis Data Firehose(现已更名为 Amazon Data Firehose)是托管的数据投递服务:你往里推数据,它替你做缓冲、压缩、格式转换、最终写到目标存储(S3 / Redshift / OpenSearch / 自定义 HTTP)。不用写消费者代码。
和 Kinesis Data Streams 的区别:
| Kinesis Data Streams | Kinesis Data Firehose | |
|---|---|---|
| 角色 | 原始消息流 | 投递管道 |
| 消费方式 | 自己写 consumer,管 checkpoint | AWS 帮你写到目标存储 |
| 延迟 | 毫秒级 | 几十秒到几分钟 |
| 计费 | 按 shard 小时数(固定开销) | 按摄入数据量(按需) |
Firehose 计费简单:只按摄入数据量收费,0–500 TB/月 档位 0.029 美元/GB。没有 stream 数量费、没有小时费、没有最低消费。我们 200 MB/天 ≈ 6 GB/月 → 每月 0.18 美元。
CloudWatch Logs 本身有 “export to S3” 功能,但只能批量触发(最快每小时一次)、格式不可控、跨账号有 IAM 麻烦。Firehose 是流式的、5 分钟级延迟、文件格式可控。
Firehose 关键配置
1 | extended_s3_configuration { |
buffering_size 和 buffering_interval 是 “ 或 “ 关系——任意先到先投递。低流量服务的端到端延迟约等于 buffering_interval(5 分钟)。
CW Logs → Firehose 数据格式(最大坑)
CW Logs subscription filter 投给 Firehose 的数据,每条记录长这样:
flowchart TB
subgraph Outer["Firehose S3 文件(外层 GZIP)"]
direction TB
R1["record_1<br/>(binary gzip)"]
N1["\\n"]
R2["record_2<br/>(binary gzip)"]
N2["\\n"]
R3["..."]
end
subgraph Inner["每条 record_i 解 gzip 后"]
J["{<br/> "messageType": "DATA_MESSAGE",<br/> "owner": "...",<br/> "logGroup": "...",<br/> "logEvents": [<br/> {"timestamp": ..., "message": "..."},<br/> ...<br/> ]<br/>}"]
end
R1 -.解 gzip.-> Inner外层 Firehose 给整批记录套了一层 gzip(来自 compression_format = "GZIP")。里面是若干二进制 gzip 记录用换行分隔(来自 AppendDelimiterToRecord)。每个 gzip 记录解开是一坨 JSON,包含一个 logEvents 数组。
⚠️ 踩坑:很多文章和半官方文档暗示 “CW Logs 投给 Firehose 的数据是 base64 编码 “,我们最初的解码 pipeline 就用
base64 -d处理。结果生产数据里的第一字节是1f 8b(gzip 魔数),不是 ASCII。实际是原始二进制 gzip,没有 base64 一层。这导致解码 pipeline 全部产出空文件。修复:用 Python 按
1f 8b切分记录边界,逐个gzip.decompress()+json.loads()+ 提取logEvents[].message。
数据格式搞清楚后,剩下的就是把 S3 文件搬到 EC2 并解码落盘。
3.3 第三站:S3 Transit Bucket → EC2
S3 transit bucket 配了 1 天的 lifecycle expire——文件被 EC2 处理完就 aws s3 rm 删掉,万一处理失败的兜底也只留 24 小时。这个 bucket 永远只有几十兆数据。
EC2 上一个每分钟跑的 cron 同步:
1 | while IFS= read -r s3_key; do |
parse-firehose.py 干两件事:
- 解码:按 gzip magic 切记录 → 每条
gzip.decompress→ 提取logEvents[].message - 按小时折叠:根据文件名解析出年月日时(
my-project-logs-prod-my-service-2-2026-04-25-15-05-00-uuid.gz→2026-04-25-15),把消息 append 到/logs/{yyyymmdd}/{prefix}-{yyyy-MM-dd-HH}.gz
💡 小知识:gzip 文件可拼接
cat a.gz b.gz > c.gz后zcat c.gz能完整读两份内容。这是 RFC 1952 规定的 multi-member gzip stream 行为。我们的 append 实现就是基于这个:先把旧 hour 文件复制到 tmp,再追加新 gzip 流,最后os.rename(tmp, target)原子替换。
3.4 第四站:用户怎么搜
开发者本地:
1 | # 进实例(aws ssm session manager 协议,TLS 加密) |
⚠️ 踩坑 5:rg 默认会把含 null byte 的文件当二进制跳过。生产日志里偶尔有 binary 字节(base64 解码后的某些 padding 上下文),导致
rg -z keyword /logs/漏掉文件。修复:加
-a(”as text”),变成rg -za。
链路全部跑通后,再看一眼账单——还能不能更便宜。
4. 成本优化历程
4.1 起点:56 美元/月
| 项目 | 月费 |
|---|---|
| EC2 c6g.medium(1 dedicated vCPU,2 GB) | $25 |
| EBS 100 GB gp3 | $8 |
| 3 × VPC Interface Endpoints(ssm/ssmmessages/ec2messages) | $22 |
| Firehose 摄入(6 GB/月) | $0.18 |
| S3 + CloudWatch Alarms 等 | <$1 |
| 总 | $56 |
4.2 优化 A:去掉 3 个 SSM Interface Endpoints(省 22 美元)
⚠️ 踩坑:原方案为了 “ 完全私网部署 “,给 EC2 配了 3 个 VPC Interface Endpoint(让 SSM 的控制流和数据流不经公网)。每个 Interface Endpoint 收 0.01 美元/小时,三个一个月 ~22 美元。
但我们的 shared VPC 本来就有 NAT Gateway(ECS 应用们在用)。SSM 走 NAT 出公网到 ssm.us-west-2.amazonaws.com,全程 TLS,安全性没差别。NAT 的小时费已经被 ECS 应用摊掉了,我们只多消耗几 KB/分钟的心跳流量(按 0.045 美元/GB 算约半美分/月)。
关键判断:S3 Gateway Endpoint 必须保留——它免费、且让日志数据不经 NAT,未来日志量增长不会带来 NAT 数据处理费。
4.3 优化 B:c6g.medium → t4g.small(省 13 美元)
最初选 c6g.medium(compute 系列,1 个永久独占 vCPU)。但我们的 CPU 用量画像是:99% 的时间闲、偶尔 rg 跑几秒打满。这是突发型负载的教科书场景。
t4g.small:2 个突发型 vCPU、2 GB RAM,月费 12 美元。CPU 积分平时一直涨,搜索时短促爆发完全够用。反而比 c6g.medium 的单核更快(rg 自带多线程并行)。
4.4 终点:21 美元/月
| 项目 | 月费 |
|---|---|
| EC2 t4g.small(2 burstable vCPU,2 GB) | $12 |
| EBS 100 GB gp3 | $8 |
| Firehose 摄入 | $0.18 |
| 周边 | <$1 |
| 总 | $21 |
整体省了 35 美元/月,降幅 62%。回过头看,几个关键判断值得提炼。
5. 总结
- 现成方案不一定都得用。日志量在 GB 级、用户都是内部工程师时,” 一台 EC2 + ripgrep” 比 ELK / Loki / Datadog 简单一个数量级、便宜两个数量级。
- 选型要看需求不看功能。10 个方案里有 7 个功能比我们强,但我们不需要全文索引、不需要 SQL、不需要仪表盘——需要的只是 “ 用正则 grep 最近 7 天的日志 “。
- Firehose 是数据管道里的万金油。不用写 consumer、按摄入量计费、低延迟(5 分钟)。但 CW Logs 投递格式有暗坑——是二进制 gzip 不是 base64。
- gzip 可拼接这个特性能让 “ 按时间折叠 “ 做得极简:
cat new.gz >> old.gz就完了,配合原子 rename 保证读者永远看到完整文件。 - AWS” 最佳实践 “ 要看上下文。3 个 SSM Interface Endpoints 在 “ 完全私网无 NAT” 场景是必须的,但在 “ 已有 NAT” 的 VPC 里就是白花钱。盲目套 AWS 推荐架构容易做出过度设计。
- t- 系列实例对突发型负载是福音。compute 系列贵,但很多场景的 CPU 是 99% 闲 + 1% 爆——这正是 t- 系列的甜区。