fix(collectors): 主机采集器从未真正运行过 —— systemd 用了 Promtail 3.x 已删除的参数

=== 一、端到端验证发现的两个阻断性问题 ===
在 172.19.16.8 上真实部署(host-docker Loki + host-promtail)验证:

1) systemd unit 的 ExecStart 带 `-positions.enabled=true`
   Promtail 3.x 已删除该参数 → 启动即退出(status=2),restart counter 一路涨到 65:
       flag provided but not defined: -positions.enabled
   也就是说:**主机模式的采集器此前从未真正跑起来过**。
   → 移除该参数(positions 由 YAML 配置,不需要这个 flag)。

2) 安装后不校验服务真实状态
   `systemctl restart` 对"启动即崩"的服务同样返回 0(只是排队拉起),
   平台据此把"崩溃重启中"报成 status=active / 采集器已部署,用户完全看不出问题。
   → 新增 waitServiceActive():轮询 systemctl is-active 最多 20s,
     失败时返回错误并把 journalctl/日志尾部带回前端。

=== 二、验证结果(修复后)===
  Loki 主机模式:容器 lokistack-loki-2 Up,/ready=ready,labels 正常
  Promtail:active,监听 9080,ExecStart 已去掉非法参数
  系统日志:job=system 17,513 条/10min(/var/log/{messages,secure,cron,...})
  应用日志:job=app 4,311 条/10min(/var/log/*.log)
  样本:pre-cex-ops-jenkins systemd[1]: Started promtail.service ...

=== 三、验证中另外发现、待修的两点(下一轮)===
- host 标签是字面量 "${HOSTNAME}":Promtail 不做环境变量展开
  (需 -config.expand-env + systemd Environment,或生成配置时直接写真实主机名)
  → 21,825 条日志的 host 全是 ${HOSTNAME},无法按主机过滤
- 系统日志时间戳解析失效:TencentOS 4 的 /var/log/messages 是 ISO8601
  (2026-07-17T15:38:24.923784+08:00),当前按传统 syslog(Oct 28 18:11:06)
  写的正则匹配不上 → timestamp 阶段不生效,用的是采集时间(回填历史会错)
  → 计划安装时探测首行格式,自动选择 ISO8601 / 传统 syslog 解析

镜像:lghost/lokistack:latest + v0.1 = sha256:6268d534b20a
This commit is contained in:
opsx committed 2026-10-28 18:11:41 +00:00
1 parent bad2a1e624
commit 55d6a74eb3
1 file changed
+41 -1
+41 -1
View File
@@ -136,7 +136,7 @@ After=network-online.target
Type=simple Type=simple
User=root User=root
Group=root Group=root
ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/promtail.yaml -positions.enabled=true ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/promtail.yaml
ExecReload=/bin/kill -HUP $MAINPID ExecReload=/bin/kill -HUP $MAINPID
Restart=always Restart=always
RestartSec=10 RestartSec=10
@@ -155,12 +155,52 @@ WantedBy=multi-user.target
return fmt.Errorf("启动 Promtail 失败: %w", err) return fmt.Errorf("启动 Promtail 失败: %w", err)
} }
// 必须校验服务真的起来了:
// 原来只判断 systemctl 命令有没有执行错误,结果 Promtail 因配置/参数错误
// 崩溃重启 40+ 次,平台仍然显示"采集器已部署",用户以为采集是好的。
if err := waitServiceActive(ctx, client, "promtail", 20*time.Second); err != nil {
out, _, _ := client.Execute(ctx, "journalctl -u promtail -n 15 --no-pager 2>/dev/null | tail -15")
tail := strings.TrimSpace(out)
if tail == "" {
out, _, _ = client.Execute(ctx, "tail -10 /var/log/promtail.log 2>/dev/null")
tail = strings.TrimSpace(out)
}
return fmt.Errorf("Promtail 启动后未处于运行状态:%v\n最近日志:\n%s", err, truncateForMessage(tail, 900))
}
// 保存配置快照 // 保存配置快照
d.collector.Config = conf d.collector.Config = conf
d.log("success", "Promtail 部署完成", "") d.log("success", "Promtail 部署完成", "")
return nil return nil
} }
// waitServiceActive 轮询 systemctl is-active,直到 active 或超时。
// Promtail 起不来时 systemctl restart 依然返回 0(只是排队拉起),
// 所以必须自己查状态,否则会把"崩溃重启中"误报成部署成功。
func waitServiceActive(ctx context.Context, client *SSHClient, service string, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
var last string
for time.Now().Before(deadline) {
out, _, err := client.Execute(ctx, fmt.Sprintf("systemctl is-active %s 2>&1", service))
last = strings.TrimSpace(out)
if err == nil && last == "active" {
return nil
}
time.Sleep(2 * time.Second)
}
if last == "" {
last = "unknown"
}
return fmt.Errorf("%s 状态=%s", service, last)
}
func truncateForMessage(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max] + "…"
}
func (d *CollectorDeployer) uninstallHost(ctx context.Context) error { func (d *CollectorDeployer) uninstallHost(ctx context.Context) error {
host := d.collector.Host host := d.collector.Host
if host == nil { if host == nil {