华为云 LTS 日志服务采集断流与跨区域转储失败的一次完整排查
华为云 LTS 日志服务采集断流与跨区域转储失败的一次完整排查
背景:告警说日志断了,但采集配置没动过
周二早上 8 点 40,值班群弹出一条告警:业务侧 Lambda 的日志在 LTS 里查不到最近 30 分钟的数据,而应用本身的日志文件是在正常写入的。生产环境用的是云上 CCE 集群,日志由节点上的 Fluent Bit 采集到 LTS。
奇怪的是,这次没人改过采集配置,也没有发布新版本。我先做了最朴素的确认——在 LTS 控制台按时间范围查,确实是空的;然后登进集群 `kubectl exec` 进一个 Pod,tail 容器里的日志文件,内容一直在滚。也就是**数据在节点上,但没进云上**。
排查从这时候开始。
第一步:先分清是「采集断了」还是「查询断了」
第一个容易犯的错,是看到查不到数据就直接怀疑查询语法或时间索引。我们先做了两件事。
一是用 `lts` 的 `QueryLogs` 之外的方式交叉验证——在压测环境的同构集群上,配置完全一致的 Fluent Bit 采集到另一个 LTS 的日志组,那边数据正常。所以采集插件本身和账号权限大概率没问题。
二是回到 LTS 控制台,把日志组的「索引配置」页面截了个图,发现一个细节:**该日志组的日志写入被暂停了**。控制台上的日志组状态显示为「已停止写入」,而正常情况下应该是「正常」。
顺带说明一下排查这类问题时 LTS 的一个常用接口,采集配置类问题的元数据可以从这里拿到:
```
GET /v2/{project_id}/groups/{group_id}
GET /v2/{project_id}/groups/{group_id}/logstreams/{logstream_id}
```
返回里的 `logstream_id`、`log_group_id`、`create_time` 以及采集配置 agent 的 `log_type` 字段,是判断「日志流本身还在不在」的第一手依据。
第二步:真正的原因是配额,不是配置
「已停止写入」这个状态有几种触发原因:配额超限、租户欠费、写入被主动暂停、区域资源不可用。我们按这个顺序查。
配额那条路:打开「用量统计」,当天该日志组的写入量曲线是一条**平直的线**,不是断崖式下跌。这一点很关键——如果是配额打满,通常会看到「写入量贴着上限运行 → 持续失败 → 最终停止」的形状;而平直线意味着**从某个时刻起,一条都没进来**。
再往前翻日志组的生命周期事件,发现一条 8 点 02 分的记录:
```
Log group <group-id> status changed: NORMAL -> STOPPED_WRITE
reason: QUOTA_EXCEEDED (write request quota exhausted for this log stream)
```
时间对上了——8 点 02 分停止写入,8 点 40 告警,中间的 38 分钟正好是日志「攒在节点上但没上来」的窗口。
配额的具体口径是**日志流的写入请求数**,而不是数据量。我们这个日志流为了降低费用,做了一个看起来很省的动作:把采集端的 `buffer_size` 调大、`flush_interval` 调长(30s → 120s),想让节点侧攒够一批再上报。结果适得其反。
第三步:为什么「攒批」反而把配额打爆了
这是这次最值得记下来的一点。
Fluent Bit 的 `Flush` 走的是 LTS 的批量写入接口,单次请求按「日志条数」计一次请求配额。但 LTS 侧的日志流写入配额,是按**每分钟的请求次数**统计的,而且有一个「单次上报条数上限」的约束。
我们的配置组合出了这么个问题:`buffer_size` 设成了 5MB,`flush_interval` 120s,而业务日志的**单条平均长度**因为接了一个新的字段(traceId,68 字节)涨了将近一倍。5MB ÷ 平均单条 1.2KB ≈ 4300 条,这个量已经超过单次上报上限。
于是一个很反直觉的循环出现了:
- 一批 4300 条日志上报 → 服务端判定超过单次条数上限 → **整批拒绝**;
- 2. Fluent Bit 认为写失败,**不丢弃而是重试**,并把这批数据继续留在 buffer 里;
- 3. 到下一个 120s 周期,buffer 里现在是 8600 条(两批叠一起)→ 再次超限 → 再次整批拒绝 → 再次重试;
- 4. 重试本身的请求也计入配额。
结果是配额被**没有任何有效数据的重试请求**迅速吃光,日志流在 8 点 02 分被平台自动置为「已停止写入」。而节点上的 Fluent Bit 日志里,正好是满屏的 `retry: 400 ... too many log entries in one request`。
也就是说:我们为了省钱的那个配置,恰恰是触发限流的元凶。 攒批并不省配额,反而在超限场景下把配额消耗放大了好几倍。
第四步:三个动作恢复,并且把链路改对
动作一,先恢复写入,再谈优化。 在 LTS 控制台把日志组状态从「已停止写入」手动恢复(或者调高该日志流的配额),日志立刻开始重新流动。这一步只花了不到一分钟。
动作二,把采集配置调回合理区间。
```yaml
fluent-bit.conf 调整后的关键段
[SERVICE]
Flush 5
Log_Level info
storage.path /var/log/flb-storage
storage.sync normal
storage.checksum off
storage.backlog.mem_limit 128M
[OUTPUT]
Name huawei
Match app.*
host lts-endpoint
port 443
tls on
# 关键:限制单批条数,别让一次 Flush 触发超限
log_group_id <group-id>
log_stream_id <stream-id>
# 丢弃策略改为丢弃最旧,避免整批被拒后无限重试
Retry_Limit 3
```
两个要点:一是把 `Flush` 从 120s 调回 5s,让单批条数回到安全范围;二是加了 `Retry_Limit 3`,**重试必须有上限**——失败太多就丢弃并继续,而不是把一批坏数据无限重投。日志采集是「尽力而为」链路,为了不丢一条日志把整个日志流打死,是典型的坏取舍。
动作三,跨区域转储配置顺手一起修掉。
这个日志组还配了一条转储到 OBS 的规则,用来长期归档。恢复写入之后我们发现转储没有跟着恢复,控制台里规则状态是「已停止」。查文档才明白:**日志流被置为「已停止写入」时,关联的转储规则会一并停止**,需要手工重新启用。我们把转储规则重新启动,并检查了 OBS 侧的落地情况——归档目录里出现了新分区文件,时间戳连续。
归档的验证不能只看控制台显示「运行中」,建议直接看落地的对象:
```
aws s3 ls s3://<bucket>/<prefix>/ --recursive | tail -20
```
我们在 OBS 上抽查了最近 5 分钟的对象,行数和时间戳都对得上,才算确认转储真的通了。
效果验证与复盘
改完之后,写入量曲线恢复了正常的锯齿状(不再平直),日志流的请求配额使用率从 98% 降到了 15% 左右,因为重试风暴消失了。告警在 9 点 05 分自动恢复。
沉淀下来的四条规则:
第一,**采集端「攒批」配置要同时看两个上限**:单次上报条数上限和每分钟请求配额。只调大 buffer 而不看条数,是把风险从「花钱多」换成「服务被限流」。
第二,**任何重试都必须有上限**。日志采集是可丢弃的链路,`Retry_Limit` 这类参数一定要显式设置,让系统丢数据而不是无限重试。无限重试在任何有配额限制的系统里都是放大器。
第三,**「已停止写入」是一个需要主动告警的状态**。它是平台侧的自动动作,一旦触发就意味着整条链路静默中断,而查询侧看不出来。我们在这次之后加了一条监控:直接抓日志流状态,状态不是「正常」就告警,不要等「查不到数据」这种业务侧的间接信号。
第四,**恢复写入之后,别忘了检查下游**。转储、订阅、告警规则这些依赖日志流的下游动作,都会在日志流停止时被一并挂起,恢复时需要逐个核对。只恢复主链路然后草草收工,很容易留下一个「看起来好了但归档是空的」的状态。
最后提一句成本。转储到 OBS 之后,我们按存储类别做了分层:近 7 天用标准存储,7 天以后转低频存储,再老的直接归档。配合压缩,冷数据成本比之前低了大概六成。这部分和上面的稳定性改造是两件事,但都属于「先把链路搞对,再来省」——顺序反了的话,省下来的钱很快会被一次故障吃掉。
- 点赞
- 收藏
- 关注作者
评论(0)