现象 1. 主机采集器只有 systemd 一条路,跨发行版差异大(这轮已踩过 param 被删、日志路径、 时间戳格式三处);进程存活/开机自启也没有统一保证。 2. 「采集器日志」在主机模式下无法从平台查看,排「起没起来/在报什么错」必须 SSH。 3. 实测发现:Loki 部署把 listenIP 填成 0.0.0.0 时,部署记录 endpoint 是 http://0.0.0.0:3100,采集器照抄去推 -> connect: connection refused, 而容器/systemd 都是 running、平台 status=active —— 日志静默丢失。 根因 1. 托管方式写死在代码里(promtail 二进制 + systemd 单元),没有能力探测。 2. 没有对外暴露采集器自身日志的接口。 3. 渲染 promtail 配置时直接用 deployment.Endpoint,没有校验它是不是「可连接的推送地址」; 另外容器用 bridge 网络,127.0.0.1 在容器里指向容器自身,同机 Loki 也会推失败。 修法 - services/collector_host_runtime.go(新增) * detectHostCapabilities:一次 SSH 往返探测 架构/init/systemd/docker可用/开机自启/SELinux/journald * chooseHostRunMode:docker 优先 -> systemd 兜底 -> 两者都没有则明确报错(不裸跑进程) * installHostDocker:grafana/promtail:3.4.0 容器,--restart=always --memory=256m --cpus=1 --log-opt max-size=10m,max-file=3,挂 /etc/promtail(:ro) /var/lib/promtail /var/log(:ro) (+journald 相关),SELinux Enforcing 时 --security-opt label=disable; 先 docker run --rm -check-syntax 校验配置,再替换正式配置 * waitContainerRunning:看 Status + RestartCount,起来就崩立即报错并带回 docker logs * hostPushEndpoint:0.0.0.0/:: 换真实主机 IP;容器模式下 127.0.0.1/localhost 也换; 换不了明确报错 * CollectorLogs:主机 docker logs / journalctl,K8s DaemonSet Pod 日志、Sidecar fluent-bit 容器日志 - collector_deployer.go:installHost 探测->选模式->渲染配置->分发;systemd 路径从 docker 迁回时 清理同名容器;卸载时容器与 systemd 两种方式都清(RunMode 对老记录是空的) - 拉不到镜像(内网无外网)时降级 systemd 二进制托管,其它错误原样抛出 - models/collector.go:新增 runMode 并纳入 Sanitized;services.Update 白名单同步 - 接口 GET /api/v1/collectors/:id/logs?tail=N;前端卡片加「运行方式」+「日志」弹窗 - build.sh:detect_go 的 INFO 走 stderr(原来被打进 GO_CMD,构建必失败) 验证(172.19.16.8 / pre-cex-ops-jenkins,collector id=2) - 安装后 runMode=docker,容器 Up,RestartPolicy=always,systemd promtail 自动停用 - 推送地址自动纠正为 http://172.19.16.8:3100,Loki 近 10 分钟查到 4919 条 job=system/job=app、host=pre-cex-ops-jenkins - docker 垫片模拟「docker 不可用」-> 自动回退 runMode=systemd、unit active+enabled、 日志接口 source=systemd;移除垫片重装 -> 切回 docker - 卸载:容器 0 个、unit 文件不存在、/etc/promtail 不存在、runMode 清空;重装恢复 running/0 重启 - K8s 采集器日志接口 source=kubernetes(顺带暴露 429 限流,见后续日志量治理) - go vet 干净;go test ./... 全绿(新增 chooseHostRunMode / hostPushEndpoint 等 6 组用例); tsc -b 通过 - 镜像 lghost/lokistack:latest = :v0.1 = sha256:906a6d5369d7,Pod imageID 一致
103 lines
5.1 KiB
Go
103 lines
5.1 KiB
Go
package models
|
||
|
||
import "time"
|
||
|
||
// Collector 日志采集器(部署客户端),关联一个已部署的 Loki 作为推送目标
|
||
type Collector struct {
|
||
ID uint `json:"id"`
|
||
Name string `json:"name"`
|
||
Type string `json:"type"` // host-promtail | k8s-daemonset | k8s-fluentbit-sidecar
|
||
LokiDeploymentID uint `json:"lokiDeploymentID"`
|
||
LokiDeployment *LokiDeployment `json:"lokiDeployment,omitempty"`
|
||
RefType string `json:"refType"` // host | cluster
|
||
HostID *uint `json:"hostID,omitempty"`
|
||
Host *Host `json:"host,omitempty"`
|
||
ClusterID *uint `json:"clusterID,omitempty"`
|
||
Cluster *Cluster `json:"cluster,omitempty"`
|
||
TargetDeployment string `json:"targetDeployment"` // sidecar 专用:目标 deployment 名
|
||
Namespace string `json:"namespace"` // 采集器自身部署所在的 k8s 命名空间
|
||
// AppLabelSource 决定 app 标签从哪里取值(可填私有标签,便于按自己的规范匹配)。
|
||
// 留空 = 默认:app.kubernetes.io/name → app → Pod 的 controller_name(去 hash) → pod 名
|
||
// 可填:controller_name / app.kubernetes.io/name / app / k8s.kuboard.cn/name 等任意 Pod 标签
|
||
AppLabelSource string `json:"appLabelSource"`
|
||
// AppInclude 只采集这些 app 的日志(白名单,按 AppLabelSource 解析出的 app 值匹配)。
|
||
// 留空 = 不限制。配合 TargetNamespaces 可显著收敛日志量。
|
||
AppInclude []string `json:"appInclude"`
|
||
// TargetNamespaces 只采集这些命名空间的 Pod 日志;为空表示采集全集群。
|
||
// 对应 promtail 里 kubernetes_sd_configs.namespaces.names
|
||
TargetNamespaces []string `json:"targetNamespaces"`
|
||
LogPath string `json:"logPath"` // 采集日志路径
|
||
Labels string `json:"labels"` // 额外标签(JSON)
|
||
// ScrapeJobs 主机模式的结构化采集项(多 job / 多路径 / 多行合并 / 丢弃 / 限流)。
|
||
// 为空时按 LogPath 生成默认的两个 job(app + system),保持旧行为。
|
||
ScrapeJobs []ScrapeJob `json:"scrapeJobs,omitempty"`
|
||
// ResolvedHostname / SyslogFormat 是上一次安装到目标机时探测到的真实值。
|
||
// 用途:① host 标签写真实主机名(不再写 ${HOSTNAME});② 系统日志按目标机
|
||
// 实际格式(ISO8601 vs 传统 syslog)选正则 —— 这两条是安装期才能知道的信息,
|
||
// 存下来才能让「配置预览」和线上真实配置一致。
|
||
// SystemLogPaths 安装时探测到的系统日志绝对路径(Ubuntu/Debian 与 RHEL 系不同),
|
||
// 存下来让「预览配置」和实际下发保持一致。
|
||
SystemLogPaths []string `json:"systemLogPaths,omitempty"`
|
||
ResolvedHostname string `json:"resolvedHostname,omitempty"`
|
||
SyslogFormat string `json:"syslogFormat,omitempty"` // iso8601 | syslog
|
||
// RunMode 主机采集器的托管方式:docker | systemd。
|
||
// 安装时由目标机能力探测决定(docker 优先、systemd 兜底),必须落库:
|
||
// 卸载/升级要知道该删容器还是删 systemd 单元,否则会留孤儿进程继续采日志。
|
||
RunMode string `json:"runMode,omitempty"`
|
||
Status string `json:"status"` // pending | active | error
|
||
StatusMessage string `json:"statusMessage"`
|
||
Config string `json:"config"` // 生成的配置快照
|
||
CreatedAt time.Time `json:"createdAt"`
|
||
UpdatedAt time.Time `json:"updatedAt"`
|
||
}
|
||
|
||
func (c *Collector) Sanitized() map[string]interface{} {
|
||
var loki map[string]interface{}
|
||
if c.LokiDeployment != nil {
|
||
loki = map[string]interface{}{
|
||
"id": c.LokiDeployment.ID,
|
||
"name": c.LokiDeployment.Name,
|
||
"endpoint": c.LokiDeployment.Endpoint,
|
||
"status": c.LokiDeployment.Status,
|
||
}
|
||
}
|
||
var host map[string]interface{}
|
||
if c.Host != nil {
|
||
host = c.Host.Sanitized()
|
||
}
|
||
var cluster map[string]interface{}
|
||
if c.Cluster != nil {
|
||
cluster = c.Cluster.Sanitized()
|
||
}
|
||
return map[string]interface{}{
|
||
"id": c.ID,
|
||
"name": c.Name,
|
||
"type": c.Type,
|
||
"lokiDeploymentID": c.LokiDeploymentID,
|
||
"lokiDeployment": loki,
|
||
"refType": c.RefType,
|
||
"hostID": c.HostID,
|
||
"host": host,
|
||
"clusterID": c.ClusterID,
|
||
"cluster": cluster,
|
||
"targetDeployment": c.TargetDeployment,
|
||
"namespace": c.Namespace,
|
||
// 采集范围配置:必须一起输出,否则前端编辑框读不到(显示为空),
|
||
// 用户一保存就会把现有配置写空 —— 这是重构加 Sanitized() 时漏字段导致的回归。
|
||
"targetNamespaces": c.TargetNamespaces,
|
||
"appLabelSource": c.AppLabelSource,
|
||
"appInclude": c.AppInclude,
|
||
"logPath": c.LogPath,
|
||
"labels": c.Labels,
|
||
"scrapeJobs": c.ScrapeJobs,
|
||
"systemLogPaths": c.SystemLogPaths,
|
||
"resolvedHostname": c.ResolvedHostname,
|
||
"syslogFormat": c.SyslogFormat,
|
||
"runMode": c.RunMode,
|
||
"status": c.Status,
|
||
"statusMessage": c.StatusMessage,
|
||
"createdAt": c.CreatedAt,
|
||
"updatedAt": c.UpdatedAt,
|
||
}
|
||
}
|