4. 日志收集与存储:集中式日志系统、采集策略、分片与归档

日志收集与存储,说白了就是给 CI 失败日志建一个「档案馆」。没有这个档案馆,你排查问题就像在垃圾堆里翻一张小纸条——费劲又糟心。我早期带过一个项目,日志散落在 8 台构建机上,每次定位失败原因都得 SSH 挨个翻,那滋味……嗯,后来我学乖了,先把日志收好存好,再谈分析。

日志收集与存储 · 知识体系 日志收集与存储 集中式日志系统 日志文件采集策略 分片与归档 ELK 栈 Loki 位置与模式 性能与可靠性 分片策略 归档策略 采集 → 传输 集中存储 分片 → 归档 —— 数据流动方向:从采集到存储再到分片归档 ——

4.1 集中式日志系统:核心选择

集中式日志系统解决什么问题?很简单——把分散在各 CI 节点上的日志,统一收拢到一处。你想想看,一次 CI 构建可能跨 5 台机器、跑 20 个步骤,日志散落各处,怎么查?

目前主流的方案有两个:ELK 和 Loki。我两个都用过,各有利弊。下面展开聊。

ELK 栈(Elasticsearch, Logstash, Kibana)

ELK 是经典组合。Elasticsearch 负责存储和检索,Logstash 做数据清洗和传输,Kibana 提供可视化界面。我在项目中遇到日志量每天几个 TB 的场景,ELK 的全文搜索能力确实强,但资源消耗也大。如果你有足够的服务器资源,ELK 是稳妥之选。

Loki:轻量级替代方案

Loki 我是在一个资源紧张的项目中开始用的。它不像 ELK 那样对日志做全文索引,而是只索引元数据(比如标签),日志内容本身存成压缩块。说白了,它更适合「快速查看日志原文」的场景,而不是「全文检索关键字」。优点是省内存、省磁盘,缺点嘛——复杂查询不够灵活。

选择建议:

  • 需要全文检索、复杂聚合分析 → 选 ELK
  • 资源有限、主要看原文和简单过滤 → 选 Loki
  • 团队已有 Grafana 监控体系 → 优先 Loki(原生集成)

下面这个对比表,是我在实际选型时常用的参考维度:

对比维度 ELK Loki
索引方式 全文索引 仅索引标签
资源消耗 高(内存/磁盘) 低(约 ELK 的 1/3)
查询性能 全文快,聚合强 标签过滤快,内容搜索弱
存储成本 较高(需副本) 低(压缩率高)
运维复杂度 中高 低

4.2 日志文件采集策略

系统选好了,下一步就是怎么把日志「收上来」。这里面的坑,比我预想的多。我曾经在采集环节丢过日志,排查了两天才发现是路径配错了……嗯,从此我对采集策略格外小心。

采集位置与模式

CI 日志通常分布在这么几个地方:

  • 构建工具日志:Jenkins / GitLab Runner 的工作目录
  • 容器日志:Docker stdout/stderr,或是挂载的日志卷
  • 自定义日志文件:测试脚本、编译脚本输出的 .log 文件

采集模式上,我推荐用 增量采集 + 状态文件。说白了,就是只读取新增部分,不重复读已采集的内容。像 Filebeat、Fluentd 都支持通过 sincedb 或 cursor 文件记录偏移量。

小技巧:采集路径尽量用通配符 + 排除模式。比如 /var/log/ci-builds/**/*.log 同时排除临时文件 *.tmp。我在项目中见过因为没排除临时文件,导致采集器重复读同一份数据,ES 里塞了几十万条重复日志。

性能与可靠性

采集器本身也是消耗资源的。尤其是高并发构建场景,日志产生速度极快。我建议关注这三个参数:

  • 采集间隔:别设太短,1-3 秒足够,除非你要求实时
  • 批量大小:单次发送 1-5 MB,太大容易堵塞
  • 重试策略:指数退避 + 本地磁盘缓冲,防止网络抖动丢数据

这里给一个 Filebeat 配置的片段,是我在线上环境验证过的:

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /var/log/ci-builds/**/*.log
  exclude_files: ['\.tmp$', '\.swp$']
  multiline:
    pattern: '^\['
    negate: true
    match: after
  scan_frequency: 3s

output.elasticsearch:
  hosts: ["http://es-cluster:9200"]
  bulk_max_size: 1024
  worker: 2

queue.mem:
  events: 4096
  flush.min_events: 512
  flush.timeout: 5s

注意:多行日志(比如 Java 异常栈、测试用例的详细输出)一定要用 multiline 配置合并。我曾经没配这个,一条异常被拆成 20 条记录,查看时完全看不懂上下文。别踩这个坑。

4.3 日志分片与归档

日志存进来了,但如果不加管理,磁盘很快就会爆。我记得有次接到告警,ES 集群磁盘使用率冲到 97%,原因是日志分片策略没设计好,副本数太多,再加上没有归档策略。那晚扩容扩到凌晨 3 点……

分片策略

分片是为了让数据能水平扩展,同时提高查询并行度。但分片不是越多越好。我总结了几条原则:

  • 按时间分区:每天或每周一个索引,方便老化删除
  • 单分片大小:控制在 20-50 GB,太小则分片过多,太大则迁移慢
  • 副本数:生产环境至少 1 个副本,但别超过 2 个(写放大严重)

举个例子,ELK 中可以用 ILM(索引生命周期管理)自动处理:

PUT _ilm/policy/ci_logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_size": "30GB",
            "max_age": "1d"
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 }
        }
      },
      "delete": {
        "min_age": "30d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

归档策略

归档的目的是「保留历史,释放热存储」。我通常这么设计:

  • 近期日志(1-7 天):热节点,SSD 存储,高性能
  • 中期日志(7-30 天):温节点,HDD 存储,性能适中
  • 长期日志(30 天以上):冷归档,压缩后存 S3 / 对象存储

归档最佳实践:

  • 归档前做一次 数据压缩,常见压缩比 5:1 到 10:1
  • 保留 归档清单文件(manifest),记录每个归档的索引范围和时间段
  • 设置 恢复演练:每季度抽检一个归档文件,确保能正常恢复。我曾经遇到过归档文件损坏、恢复失败的情况,幸好演练发现了,否则真要查历史数据时才发现,那就麻烦了

我的习惯:在 Loki 里,我会用 compactor 组件自动合并小分片,同时配置 retention 策略。ELK 的话,ILM 基本够用,但记得开启 rollover 防止单个索引无限膨胀。

最后说一句:日志收集与存储是整个归因分析的地基。地基不稳,上层分析全是空中楼阁。别在采集和存储上省钱省力,否则你迟早要为它买单。