From 55d6a74eb37a5b5c91323f97a185bd00fdb87e0a Mon Sep 17 00:00:00 2001 From: opsx Date: Wed, 28 Oct 2026 18:11:41 +0000 Subject: [PATCH] =?UTF-8?q?fix(collectors):=20=E4=B8=BB=E6=9C=BA=E9=87=87?= =?UTF-8?q?=E9=9B=86=E5=99=A8=E4=BB=8E=E6=9C=AA=E7=9C=9F=E6=AD=A3=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E8=BF=87=20=E2=80=94=E2=80=94=20systemd=20=E7=94=A8?= =?UTF-8?q?=E4=BA=86=20Promtail=203.x=20=E5=B7=B2=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit === 一、端到端验证发现的两个阻断性问题 === 在 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 --- backend/services/collector_deployer.go | 42 +++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/backend/services/collector_deployer.go b/backend/services/collector_deployer.go index 582d247..959c96f 100644 --- a/backend/services/collector_deployer.go +++ b/backend/services/collector_deployer.go @@ -136,7 +136,7 @@ After=network-online.target Type=simple User=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 Restart=always RestartSec=10 @@ -155,12 +155,52 @@ WantedBy=multi-user.target 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.log("success", "Promtail 部署完成", "") 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 { host := d.collector.Host if host == nil {