fix(collectors): 容器模式首次安装日志全被 Loki 拒绝(entry too far behind)
=== 现象 ===
容器化采集器(docker 优先托管)上线后实测:10 分钟只进 47 条,
容器日志刷满:
msg="final error sending batch" status=400
error="entry too far behind, entry timestamp is: 2026-10-21T20:01:16Z,
oldest acceptable timestamp is: 2026-10-28T19:..."
=== 根因 ===
Promtail 在 positions 里没有某文件记录时,会从**文件开头**开始读。
主机 /var/log/messages 有 7 天历史(约 1.35 亿字节),而 Loki 只接受最近
reject_old_samples_max_age(默认 1h)的条目。
关键危害:promtail 把新旧日志混在**同一个 batch** 里推送 → 整批 400 被拒,
**连新日志也发不出去**,不只是丢历史。
systemd 模式此前看着正常,只是因为跑了很久、positions 已建立;
全新主机(或容器换了数据目录、positions 为空)必然复现。
=== 修复 ===
安装时(容器与 systemd 两条路径都加)预置 positions 到文件末尾:
- 只补 positions 里【还没有】的路径 → 重装/升级不破坏已有进度、不重复采集
- 偏移用 stat -c %s 取当前文件大小
- 路径来源:系统日志探测结果 ∪ logPath ∪ 每个 ScrapeJob 的 paths
新增 services/promtail_positions.go:
seedPositionsCmd(patterns) → 生成预置脚本
collectorPositionPatterns(c, sys) → 汇总该采集器涉及的所有路径
预置失败只 warn 不阻断启动(不影响老环境升级)。
=== 实测(172.19.16.8 / TencentOS 4)===
修复前:final error 持续刷、400 持续、10 分钟 47 条
修复后:final error 0 次、400 0 次、restarts=0
positions 已预置(cron=71042092 / cloud-init=286805 …)
日志正常入库 295 条/15 分钟(真实新增速率,不再回填历史)
时间戳与源文件一致:20:27:01.314716+00:00 CROND[1445228]
测试:TestSeedPositionsCmd / TestCollectorPositionPatterns 通过
镜像:lghost/lokistack:latest + v0.1 = sha256:d5dd05dca465
This commit is contained in:
1 parent
7e3356e32b
commit
f6af12a731
3 files changed
+143
No files matched your search
@@ -238,6 +238,17 @@ func (d *CollectorDeployer) installHostDocker(ctx context.Context, client *SSHCl
|
||||
if strings.EqualFold(caps.SELinux, "Enforcing") {
|
||||
secOpt = " --security-opt label=disable"
|
||||
}
|
||||
// 启动前预置 positions 到文件末尾,否则 promtail 会从文件头回填历史,
|
||||
// 超过 Loki reject_old_samples_max_age 的条目让整批 400,新日志也发不出去。
|
||||
if cmd := seedPositionsCmd(collectorPositionPatterns(d.collector, d.collector.SystemLogPaths)); cmd != "" {
|
||||
d.log("info", "预置采集进度(跳过历史日志,避免 entry too far behind)", "")
|
||||
if out, stderr, err := client.Execute(ctx, cmd); err != nil {
|
||||
d.log("warn", fmt.Sprintf("预置 positions 失败(不影响启动,但首次采集可能因历史日志被拒):%v %s", err, strings.TrimSpace(stderr)), "")
|
||||
} else if t := strings.TrimSpace(out); t != "" {
|
||||
d.log("info", t, "")
|
||||
}
|
||||
}
|
||||
|
||||
runCmd := fmt.Sprintf(
|
||||
"docker run -d --name %s --restart=always --memory=256m --cpus=1 "+
|
||||
"--log-opt max-size=10m --log-opt max-file=3%s %s %s -config.file=%s",
|
||||
@@ -319,6 +330,15 @@ WantedBy=multi-user.target
|
||||
if err := uploadDockerFile(ctx, client, "/etc/systemd/system/"+promtailServiceName+".service", service, "644"); err != nil {
|
||||
return fmt.Errorf("创建 systemd 服务失败: %w", err)
|
||||
}
|
||||
// 与容器模式同因:positions 为空会从文件头回填历史 → 整批 400
|
||||
if cmd := seedPositionsCmd(collectorPositionPatterns(d.collector, d.collector.SystemLogPaths)); cmd != "" {
|
||||
if out, stderr, err := client.Execute(ctx, cmd); err != nil {
|
||||
d.log("warn", fmt.Sprintf("预置 positions 失败(不影响启动):%v %s", err, strings.TrimSpace(stderr)), "")
|
||||
} else if t := strings.TrimSpace(out); t != "" {
|
||||
d.log("info", t, "")
|
||||
}
|
||||
}
|
||||
|
||||
if _, _, err := client.Execute(ctx, "systemctl daemon-reload && systemctl enable "+promtailServiceName+" && systemctl restart "+promtailServiceName); err != nil {
|
||||
return fmt.Errorf("启动 Promtail 失败: %w", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user