opsx
|
5c7a3ec81d
|
perf(daily-volume): 每日量改用 volume_range(20s → 54ms),图表改圆柱
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 核心技巧:体积用 index/volume_range,不要用 index/stats ===
同为 24 小时、同一 selector,两条索引查询差 370 倍:
index/stats : 整天聚合,实测 20s 直接超时(之前每日量接口卡 20s 的根因)
index/volume_range: 按 step 分桶只累加 chunk 字节,实测 54ms,97 个桶
原因:index/stats 要跨全部 chunk 去重统计 streams/entries;
volume_range 只把索引里记录的 chunk 字节数按时间桶求和,不解码内容。
=== 后端改动 ===
services/daily_volume.go:
体积:LokiHistogram(走 volume_range,step 由跨度自动选)→ 累加 54ms
条数:只能 index/stats,降级为「尽力而为」25s 超时;
拿不到就保留上一次的值(不能因为一次超时把历史条数清零),
绝不让它拖住体积入库。
前端图只画体积,条数缺失不影响可读性。
=== 前端改动 ===
DailyVolumeChart:AreaChart(面积)→ BarChart(圆柱)
顶部圆角 radius=[6,6,0,0]、渐变填充、maxBarSize=44
当天未结束的柱子用半透明 + 虚线描边区分,避免把"才过半天"误读成"量掉了"
=== 实测 ===
24h volume_range: 54ms,97 桶,合计 369.61 GB(与全集群 ~370GB/天 吻合)
/logs/daily-volume 接口: 10ms(读库)
Pod = Hub latest = 4cdc8b9c4841
|
2026-10-30 16:40:49 +00:00 |
|
opsx
|
d0adc095dd
|
feat(analysis): 长范围应用排行改走「小时级预聚合」,不再扫日志内容
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 问题(实测)===
按应用排行必须读日志内容(Loki 3.4 的 index/volume 不支持 app 维度聚合,
aggregateBy=app 无效),成本 ∝ 时间范围:
5 分钟 ≈ 1.3GB → 9.8s
15 分钟 ≈ 3.9GB → 19.5s
1 小时 ≈ 15GB → 预计 60~90s
6 小时 ≈ 92GB → 5~8 分钟
24 小时 ≈ 370GB → 不可用
用户在时间范围里选 6h/24h 时,页面必然卡死。
=== 方案 ===
新增 models.AppVolumeSnapshot + services/app_volume.go:
- 后台任务每小时第 5 分钟抓「上一个完整小时」的 per-app 条数
- 用**瞬时查询**(/loki/api/v1/query + [1h])而不是 query_range:
单点评估、扫描量相同、省掉多步开销;并绕开 LokiMetrics 的 90s 上限
与重型查询信号量(后台任务不该和前台抢令牌)
- 每源每小时只留 top 50 应用,保留 90 天
- 幂等:同 (hour, source, ns, app) 更新而非重复插入
- 超出 Loki 26h 保留期的窗口跳过(抓也抓不到,白等几分钟)
新增 GET /api/v1/logs/app-ranking?sourceId&from&to&limit → 读库毫秒级
前端:analysis 判断范围 > 60 分钟就走预聚合接口,并停掉实时重查询
(rankingEnabled 置 false);表格副标题说明「读的是预聚合快照,已留存 N 小时,
不含错误数与趋势」。
=== 顺带修掉的根因 bug ===
useInView 原来用 useRef + useEffect([inView]):分析页首屏 data===null 时
内容块不渲染,ref.current 为 null,effect 直接 return;等数据到了 DOM 才挂上,
但依赖没变 → effect 不重跑 → IntersectionObserver 从未创建 → inView 永远 false
→ 重查询永不发出 → 页面一直显示"没有应用维度的数据"。
改成 callback ref(节点挂载即作为依赖),并加 scroll/resize 兜底 + 挂载瞬间量一次。
另外分析页加 8 秒无条件兜底:可见性判定再失灵,8 秒后也一定查一次。
默认时间范围 15 分钟 → 5 分钟(19.5s → 9.8s)。
=== 实测 ===
Pod = Hub latest = 98de87e7f895
5 分钟窗口 by-app:9.8s(75 序列);15 分钟:19.5s
|
2026-10-30 16:35:35 +00:00 |
|
opsx
|
7f6fb364de
|
refactor(analysis): 移除重复的「应用日志量流图」与「Top 5 饼图」,并按需触发重查询
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 为什么这两个可以去掉(不是拍脑袋)===
排行表已有 9 列:排名 / 应用 / 命名空间 / 日志量 / **占比** / 错误数 /
错误率 / 趋势(sparkline) / 操作(查看日志)。
- Top 5 饼图 = 应用 + 数量 + 占比 —— 表里全都有,且表里更多
- 流图 = 各应用随时间的堆叠 —— 表里每行已有 sparkline 趋势
两者都是同一份 by-app 数据的重复呈现,纯增加视觉噪音。
=== 但"去掉它们"本身不减查询,真正的减负在这一步 ===
之前这三个组件都依赖同一条 by-app 重查询(10~15 秒)。
因为饼图/流图在首屏,所以那条查询只能一进页面就全量跑(rankingEnabled=true)。
现在图都没了,重查询只服务底部排行表 → 恢复可见性门控:
rankingEnabled: rankingView.inView
效果:打开分析页只跑索引类查询(histogram/stats/daily-volume,均 <100ms),
用户滚到排行卡才发那条重查询。
「活跃应用」卡片在未统计时显示 —(原来显示 0 个,容易误读成"没有应用")。
=== 布局 ===
趋势图从 2/3 宽改为整行,页面高度明显下降。
=== 实测(ddf63053b952)===
Pod = Hub latest = v0.1 = ddf63053b952
NamespacePie / AppStreamChart 在页面中引用数均为 0
|
2026-10-30 16:15:44 +00:00 |
|
opsx
|
3b297d0429
|
feat(collectors): 采集项支持「表单 / YAML」双模式编辑(方案 A)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 设计边界(刻意不做"编辑完整 promtail.yaml")===
开放的是**采集项本身**的 YAML 表示(字段与 models.ScrapeJob 一一对应):
name / paths / labels / encoding / json / system
multiline(firstline,maxLines) / timestamp / dropIfMatch / rateLimit
clients、positions、server、scrape_configs 外层仍由平台生成。
理由(真实故障面):
用户删掉 positions → 重启后从头重采,大量重复 + Loki 以
entry too far behind 拒绝整批;删掉 clients → 完全不外发。
这两类问题在页面上看不出来,只能登机器看日志,排障成本极高。
只开放采集规则,写坏了最多某条日志没采到,不会让采集器整体失效。
=== 后端 ===
新增 services/collector_jobs_yaml.go:
RenderJobsYAML(jobs) → 带边界说明注释的 YAML(明确写"clients/positions 不在此修改")
ParseJobsYAML(text) → 复用 models.ValidateScrapeJobs,与表单完全同一套校验
错误信息必须具体:
YAML 语法错 → 「YAML 语法错误:yaml: line 1: did not find expected ',' or ']'」
语义错 → 「采集项「app」至少需要填写一个日志路径」
新增路由(用 /collector-jobs/* 前缀,避开与 /collectors/:id 的 gin 通配冲突):
POST /api/v1/collector-jobs/render
POST /api/v1/collector-jobs/parse
=== 前端 ===
编辑弹框(主机模式)加「表单 / YAML」切换:
- 切到 YAML:调 render 把当前采集项渲染出来
- 切回表单:调 parse 回填;解析失败**留在 YAML 模式**并显示原因
(绝不能悄悄丢掉用户改动)
- 停留在 YAML 模式点保存:先 parse,失败则中止保存
- 编辑区 min-h 固定、等宽字体、可拖拽高度
=== 测试 ===
TestRenderParseJobsYAMLRoundTrip:
表单→YAML→表单 无损往返,逐字段核对
(multiline.maxLines / rateLimit / dropIfMatch / timestamp / labels / system)
TestParseJobsYAMLErrors:空内容、语法错、空列表、缺名称、缺路径、名称非法
→ 6 种输入都给出具体错误,不是笼统"格式错误"
=== 实测(c11d2badce64)===
render: code=0,17 行,含边界说明
parse : code=0,1 个采集项;往返校验 name=app paths=1 multiline=True
rateLimit={rate:2000,burst:4000} drop=1 json=True
错误: 「采集项「app」至少需要填写一个日志路径」
「YAML 语法错误:yaml: line 1: did not find expected ',' or ']'」
Pod = Hub latest = c11d2badce64
|
2026-10-30 16:10:51 +00:00 |
|
opsx
|
c8d026adc7
|
feat(collectors): DaemonSet 不再填日志路径,改为「只采集哪些命名空间」多选
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
用户指出的三个问题,逐条核实后的结论:
1) 「采集日志路径」对 DaemonSet 确实是多余的 —— 确认后端模板里写死了
`replacement: /var/log/pods/*$1/*.log`(按 pod UID 精确匹配容器标准输出),
表单里填的 logPath 对 DaemonSet 完全不参与渲染。
→ 选择 DaemonSet 时隐藏该字段,提交时强制置空。
2) 「可以指定采集多个命名空间」—— 后端**早就支持**:
models.Collector.TargetNamespaces + promtailNamespaceFilter(),
空数组 = 全集群。只是**前端新建弹框没暴露**(编辑弹框里有)。
→ 新建弹框补上多选 chips(全集群 / 各命名空间可点选),并补齐前后端透传:
dto.CreateCollectorRequest.TargetNamespaces
controller → services.CreateCollectorRequest.TargetNamespaces
models.Collector.TargetNamespaces
之前前端即使传了也会被 Go 静默丢弃(无该字段)。
3) 「命名空间」字段语义已在上一提交补了分类型说明:
- DaemonSet:采集器自身部署在哪个命名空间(不影响采什么)
- Sidecar:目标应用 Deployment 所在命名空间
新增 TestDaemonsetTargetNamespacesFilter:
nil → 不生成 namespaces 白名单(全集群)
["astralx"] → 生成且包含 astralx
["astralx","default"] → 两个都在
=== 实测(d45dfa18ecc5)===
前端文案「只采集这些命名空间」「不选 = 采集整个集群…」均已进包
渲染测试 3 种输入全部符合预期
注:本次 docker push latest 遇到 Docker Hub 502,v0.1 成功而 latest 仍指向旧镜像,
导致首次滚动后 Pod 仍是旧代码;重推 latest + 强制重建 Pod 后确认
Pod digest = d45dfa18ecc5 —— 记录这个坑:push 失败必须核对三方 digest。
|
2026-10-30 16:03:24 +00:00 |
|
opsx
|
87909ed403
|
docs(collectors): 采集策略弹框「命名空间」补分类型说明
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
用户困惑:这个字段到底是"只采集指定命名空间"还是"组件要部署的命名空间"?
查代码后确认——**同一个字段在三种采集类型下含义完全不同**:
k8s-fluentbit-sidecar:
= 目标应用 Deployment 所在的命名空间。
平台会 Deployments(ns).Get(target) 然后 patch 注入 fluent-bit sidecar,
同时把它作为日志的 namespace 标签。
**不是**日志过滤条件。
k8s-daemonset:
= Promtail DaemonSet 部署到哪个命名空间。
DaemonSet 挂载 /var/log/pods,采集**整个集群所有命名空间**的容器日志,
不按命名空间做过滤。一般就填 Loki 所在的 log。
host-promtail:不使用该字段。
原来这三种情况共用一句"命名空间",用户只能猜。
现在字段下方按类型显示对应说明(min-h-4 固定高度,不抖动)。
实测:三条提示文案均已进包(6fe430f59eef)。
|
2026-10-30 15:53:08 +00:00 |
|
opsx
|
a14bdb5e07
|
fix(analysis): 每日量不再懒加载;按应用统计期间显示 loading(不再假装"没有数据")
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
用户的三个现象,对应三个独立缺陷:
=== 1. 每日日志量"一直没数据" ===
接口只读平台库,实测 **10ms**,却仍被 inView 懒加载门控。
ref 虽然挂在 DOM 上,但滚动判定在多层布局里不可靠 → enabled 一直 false
→ 请求根本不发 → 页面显示"暂无历史数据"。
改为:只要有数据源就加载(enabled = Boolean(sourceId))。
这个接口 10ms,没有任何理由懒加载。
=== 2. 排行/流图"打开没有数据,等很久也没 loading" ===
按应用统计要 10~15 秒(读日志内容,物理下限)。
但 loading 是**整页共用**的:索引类指标 100ms 就回来、
重查询还在跑时 loading 就已经变 false,
卡片于是渲染成"没有应用维度的数据"——用户以为查完了、没数据。
改为:
- 新增 appsLoading,专门标记"按应用统计"这段重查询
- 卡片在 appsLoading 期间显示 spinner + "正在按应用统计…"
- loading 与 appsLoading 都在 finally 清理(成功/失败/取消都清)
=== 3. data 为 null 时整页空白 ===
原来 `) : !data || !sourceId ? null : (` 直接 return null,
首屏或重查询异常时是**整页空白**,和"没数据"无法区分。
改为显示 spinner + "正在按应用统计(需读取日志内容,通常 10~30 秒)"。
=== 关于"用 Go 定时任务统计每日总量"===
这个机制**早就存在**(services/daily_volume.go):
- 启动 20s 后回补 + 每 30 分钟一次 ticker
- 用 index/stats 取整天的条数与字节,**不读日志内容**,通常秒级
- 快照写入平台库,保留 400 天
实测当前库内已有 4 天数据(10ms 返回):
2026-10-27 32.5 GB
2026-10-28 23.9 GB
2026-10-29 133.6 GB
2026-10-30 239.3 GB
问题只在"前端没去请求",不在采集侧。
实测(df3b477e6740):每日量接口 10ms 返回 4 天,Pod 1/1 Running。
|
2026-10-30 15:49:52 +00:00 |
|
opsx
|
419a81c2af
|
perf(loki): 查询并行度改为自动跟随 CPU 核数(不再硬编码)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 问题 ===
max_query_parallelism 原来是硬编码值,改过两轮(8 → 6):
- 8:4 核机器上 76% 调度周期被限流,label values 要 20 秒
- 6:仍然高于核数,25% 周期限流,查询并没有更快
根因不是"该填几",而是**并行度只有一个正确值:等于 CPU 核数**。
把它做成可配置项反而是坑 —— 用户改了 CPU 忘了改并行度就出现错配。
=== 做法 ===
新增 queryParallelism(resourcesCPU):
- 从部署记录的资源规格推导,模板渲染成 {{ .QueryParallelism }}
- 容错:支持 "4000m"/"500m" 毫核写法(向上取整)
- 边界:低于 2 核按 2,高于 12 核按 12(再高频只会互相抢 CPU)
- 非法值/空值回退 4
用户在「组件部署」里改 CPU,并行度自动跟随,页面上不需要新字段。
=== 实测(4 核 + 并行 4)===
by-app(15m) 11913ms 75 条序列 (并行 6 时 12438ms)
label/namespace 38ms
label/app 25ms
label/pod 25ms
限流 61/252 周期(仅重查询那几秒)
附带 TestQueryParallelism 覆盖 10 种输入(含毫核/越界/非法值)。
|
2026-10-30 15:41:38 +00:00 |
|
opsx
|
ad0d35f72a
|
feat(alerts,logs): A 方案—告警窗口 15m→5m 消重复扫描;数据源可编辑/删除且删后不被自动重建
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 1. 告警窗口 15m → 5m(阈值同比例 1/3)===
背景:检查间隔已改 5 分钟,但规则窗口仍是 15 分钟 ——
同一个 15 分钟窗口每 5 分钟被完整重扫一遍,等于 3 倍重复扫描。
在 370GB/天、全命名空间 selector 下,这是 Loki CPU 的最大稳态来源。
FATAL 10 → 4 窗口 5m
Go panic 10 → 4 窗口 5m
JVM OOM 5 → 2 窗口 5m
StackOverflow 5 → 2 窗口 5m
应用错误激增 100000 → 35000 窗口 5m(改名 error > 3.5万)
ingress 5xx 2000 → 700 窗口 5m
ingress 4xx 20000 → 7000 窗口 5m
DB 连接失败 1000 → 350 窗口 5m
阈值按 1/3 缩放是必须的:窗口缩短 3 倍后,同样的流量在 5 分钟内只有
1/3 的条数,阈值不动就永远打不到了。
迁移 v10→v11 只改"窗口仍是 15 且名字匹配内置规则"的项,用户手调的不动。
=== 2. 数据源支持编辑/删除(含托管源)===
原来前后端都把托管源锁死:
- 前端 `!managed &&` 直接不渲染编辑/删除按钮
- 后端 DeleteSource 返回「平台部署的数据源随部署管理,请先删除对应部署」
用户的诉求是"数据源必须能编辑和删除",且确实有场景(不想让某个源出现在
查询下拉里、想改认证头)。
=== 3. 修「删了又回来」的真 bug ===
EnsureLogSourcesForRunningDeployments() 在 **每次打开数据源列表和启动时**
都会给 running 部署补齐数据源 —— 所以删掉托管源后,刷新页面它又回来了,
用户看到的现象就是"删不掉"。
新增 LokiDeployment.AutoSourceDisabled:
- 删除托管源时置位(DisableAutoSourceFor)
- EnsureLogSourceForDeployment 见到该开关直接 return
- 部署被编辑/重新部署时重置(EnableAutoSourceFor),即"你要用它了就恢复"
前端配套文案:
- 编辑托管源弹窗提示"重新部署会覆盖集群/Service/端口,认证与请求头保留"
- 删除确认明确"删除后不会再自动重建,重新部署该 Loki 才恢复"
=== 实测(20300eef92d2)===
v11 迁移后 8 条规则全部 窗口=5m,阈值 4/4/2/2/35000/700/7000/350
go test ./... 全绿
|
2026-10-30 15:28:30 +00:00 |
|
opsx
|
c9e604b35f
|
perf(logs): 修 label 下拉 20 秒、Loki CPU 限流 76%、告警检查频率
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
1) Loki CPU 限流 76%(limits 4 核,实测 3923m,nr_throttled 920656/1209379)
→ limits 提到 8 核 / requests 2 核。限流归零,histogram 84ms,
by-app 从 60s 超时降到 14.9s。
2) label values 未传时间范围 → Loki 默认扫 6 小时索引(370GB/天 → 10~21s)
→ 默认只查最近 1 小时。实测 namespace 21330ms→33ms,app 24781ms→30ms。
3) max_query_parallelism 8 > 实际核数 → 调到 6。
4) 告警检查 1 分钟 → 5 分钟(规则窗口本身 15 分钟,稳态负载降到 1/5)。
|
2026-10-30 03:42:03 +00:00 |
|
opsx
|
e6c0cf134f
|
test(routers): 路由断言跟上上游变更(备份恢复已移除,补保留期读写)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
上游 commit f87fb4b 移除了备份恢复功能(/system/backup、/system/restore),
我上一轮加的路由守卫测试仍在断言 /system/backup 存在,导致 go test 失败。
调整为当前真实 API 面:
- 移除 /api/v1/system/backup 断言
- 补 GET/PUT /api/v1/settings/retention(保留期读写)
- 保留 /api/v1/system/self-check
实测:go test ./routers/ → ok
|
2026-10-29 17:02:09 +00:00 |
|
opsx
|
48e68250bb
|
feat(docs): 用户手册接入界面截图
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 6 张核心页面截图(仪表盘/向导/日志查询/日志分析/告警/设置)存 docs/assets/screenshots,
随发布包分发;产品介绍页新增「界面预览」两列网格,各功能页顶部内嵌对应截图
- goldmark 开启 WithUnsafe 以渲染网格 HTML(文档自维护,无注入面);截图经 DOM 校验全部加载成功
|
2026-10-29 16:36:21 +08:00 |
|
opsx
|
e579eabe48
|
fix(wizard): 存储步骤去掉推荐文案;空态列表补腾讯云对象存储(COS 后端早已支持)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 16:20:34 +08:00 |
|
opsx
|
c4d907fd27
|
docs: 安装与启动 → 快速安装
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 16:11:16 +08:00 |
|
opsx
|
bbeb9d0b28
|
docs: AGENTS/CLAUDE 同步密钥自动生成行为
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 16:09:16 +08:00 |
|
opsx
|
65bb29aa72
|
feat(config): 密钥自动生成下沉到后端 + 安装文档极简化
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- resolveSecrets 统一所有部署形态的零配置体验:显式 env 优先 → DATA_DIR/.secrets.env
→ 自动生成随机密钥持久化(0600,重启不变);无法持久化才拒绝启动(单测覆盖生成/复用/显式优先)
- install.md 重写为三方式各一条命令(K8s install.sh / docker compose up -d / release 包),
密钥与数据说明收敛到统一「说明」一节;README Docker 段同步精简
|
2026-10-29 16:07:58 +08:00 |
|
opsx
|
88b2ac2a6d
|
feat(install): 一键安装脚本 — 对标 1Panel/雷池体验
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
bash -c "$(curl -fsSL …/scripts/quick_start.sh)" 一条命令:
架构自检(x86_64/arm64)→ 发布包下载(默认 Gitea Release,可镜像)→ 随机密钥生成持久化
→ systemd 开机自启 → 健康等待 → 打印访问地址/初始密码/常用命令;重复执行即升级(数据自动备份)。
CentOS 7 真机验证:安装、登录、文档/落地页、升级幂等全通。
配套 upload-release.sh(make dist 后一键发布双架构包到 Gitea Release);README/手册/CHANGELOG 同步。
|
2026-10-29 16:05:40 +08:00 |
|
opsx
|
0936234623
|
feat(install): 零输入一条命令部署 + 双架构发布包
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- docker compose up -d 直接启动:entrypoint 在未显式提供密钥时自动生成随机
JWT_SECRET/ENCRYPTION_KEY 并持久化到数据卷 .secrets.env(0600,重启不变),
显式环境变量优先(单显式不被持久化文件覆盖,四场景实测);compose 去掉 :? 强制
- build.sh --dist / make dist:linux amd64 + arm64 两个 tar.gz(二进制+docs+homepage+README),
实测 15M/13M;安装文档重写为三方式(kubernetes/install.sh 一键、compose 一条命令、release 包)
- README 快速开始同步
|
2026-10-29 15:50:50 +08:00 |
|
opsx
|
437ff56992
|
feat(docs): 文档站重制 — 参照 opsxapi/opsxshell 体系,服务端渲染三栏布局
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 后端 goldmark 渲染管线:/docs/:slug 页面 + /docs/search-data.json 搜索索引,
manifest.json 驱动分组导航,正文剥 H1 避免与页面头重复
- 三栏布局(分组侧栏/内容/本页目录)+ 顶栏(品牌/版本徽章/搜索面板/官网入口),
docs.css 品牌绿版 + docs.js(代码复制/轻量高亮/提示块/TOC 滚动高亮/移动端抽屉)
- 替换 docsify;无上一页时空占位无边框;visual-judge 两轮评审(初版 fail → 修复后 pass)
|
2026-10-29 15:30:25 +08:00 |
|
opsx
|
c1f822fb10
|
refactor: 移除资源用量的可选 Prometheus 接入与平台监控页
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 资源用量弹窗:删除 applyPrometheus/promQueryValue 与峰值/限流字段
(LOKI_USAGE_PROMETHEUS_URL 从未内置,代码恒走降级),统一提示
「未接入 Prometheus:暂无峰值/限流数据」;扩容建议改基于 metrics-server 实时值
- 移除「平台监控」菜单页与 /api/v1/system/metrics-summary;
原始指标保留在标准 /api/v1/metrics(Prometheus 文本格式)可自行抓取
- 手册/落地页/CHANGELOG 同步(去除备份恢复与监控页宣传,指标端点表述中性化)
|
2026-10-29 15:16:18 +08:00 |
|
opsx
|
f87fb4b2b3
|
refactor: 移除备份恢复功能(API/CLI/手册)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
日志数据的保留与安全由对象存储 + Loki retention 承担,平台侧整库备份恢复价值有限:
删除 services/backup.go、/system/{backup,restore,restore/apply,backups} 路由与 handler、
CLI backup/restore 子命令、前端 api 方法与类型;自检所需的 dataDirPath/密钥指纹并入 selfcheck;
用户手册删备份章节并修正 FAQ 升级建议;CHANGELOG 记录移除。
|
2026-10-29 14:48:25 +08:00 |
|
opsx
|
e07fe55c86
|
docs: CHANGELOG Unreleased 记录第三阶段首批特性
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 14:04:25 +08:00 |
|
opsx
|
2bd77b57b4
|
feat(monitor): 平台自监控页(/monitor,管理员)
- 后端 /api/v1/system/metrics-summary(admin):从 Prometheus registry 聚合
HTTP 请求/状态码/Top 路由、DB 落盘与大小、告警评估、Loki 查询、runtime 快照
- 前端统计卡 + Top 路由表 + 分布明细(状态码/告警/Loki/落盘),中英文案齐全
- 菜单位于「日志分析」与「日志告警」之间
|
2026-10-29 14:04:25 +08:00 |
|
opsx
|
3ea7f2dcc6
|
feat(site): homepage 落地页 + docsify 用户手册文档站(/docs /homepage 随二进制发布)
- homepage/:品牌落地页(Hero/特性/快速开始),参考 opsxapi 站点风格,绿色品牌系
- docs/:docsify 文档站 + user-guide 十篇手册(安装/向导/采集/查询/分析/告警/授权/备份/FAQ)
- 后端 serveStaticDir 挂载 /docs 与 /homepage(可执行文件相对定位,独立于前端 dist)
|
2026-10-29 14:04:24 +08:00 |
|
opsx
|
b8d08ef6d1
|
fix(alerts): 内置规则排除 Loki 自身日志,消除自我污染误报
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
Loki 会把执行过的查询语句记成自身 info 日志,查询文本包含规则关键字时
告警会'命中自己'(如 StackOverflowError 规则匹配到告警引擎自己的查询记录),
造成持续误报——全命名空间类内置规则一律加 app!="loki";存量规则启动时自动升级;
顺带清理旧版遗留的重复规则「应用错误过多(error > 1000)」(与现模板重复且阈值会误报);
前端新建/模板默认查询同步;红线测试锁死
|
2026-10-29 13:46:46 +08:00 |
|
opsx
|
abe28964a1
|
fix(i18n): 全站文案书面化 + 中英翻译质量修复
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
中文(zh):约 40 处口语化改书面(「谁在什么时候改了什么」→「操作人、时间与变更前后对比」、
「找出最吵的业务」→「定位日志量最高的业务」、「点」→「点击」、创建你的第一个→创建首个等);
「副本 / Pod」统一为「Pod」。
英文(en)修复:
- 功能 bug:wizard openGrafana ({url}) 插值失效→({{url}});settings notify 空态链接词悬空破句
- 语义补齐:clusters/hosts 删除确认补 Warning 与后果句;tokenInsecureHint 补 Kubeconfig 建议
(并删 en 死键 tokenRecommended);retention liveHint/costWarning/upgradeHint 补丢失半句
- 术语统一:data source / collection policy / log lines / Listen IP (internal) / Total deployments
- 复数:users confirmDesc、pagination total、range lastMinutes 补 _one;storage deleteWarning 两形态对齐
- 风格:Select filtered users、Delivery History、Log Scrape Jobs、App label source、
Deployment Logs 连字符改间隔号;删除 zh 死键 liveTitle
|
2026-10-29 13:42:51 +08:00 |
|
opsx
|
df1075f1f5
|
fix(layout): 顶栏用户区去掉盾牌图标与管理员徽章,只留用户名
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 13:31:05 +08:00 |
|
opsx
|
c33ea9d0a8
|
ci(lint): golangci-lint v2 入门禁并归零
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- .golangci.yml:standard 集 - errcheck(存量 38 处裸忽略待专项),staticcheck 关纯风格/quickfix
- 清理 3 个无引用死函数;SanitizedJSON 不可达分支收敛为接口分支 + nil 指针防护(原死分支中的 nil 检查从未执行)
- CI backend job 增加 golangci-lint-action@v6
|
2026-10-29 13:29:25 +08:00 |
|
opsx
|
a69142fbfe
|
refactor(frontend): Live Tail 重构 + alerts 拆分 + NotifySection 去重
- Live Tail:输入 500ms 防抖后才切换轮询查询;轮询 AbortController 取消 + 版本号丢弃乱序响应
- alerts/index 508→286 行:表单与模板拆出 RuleFormDialog(浏览器实测模板/通道/提交可用)
- NotifySection 两份逐字节副本合一为共享组件;settings 词典注册进 domains(alerts 直访不再裸键)
|
2026-10-29 13:29:25 +08:00 |
|
opsx
|
572efc89a4
|
fix(reliability): 采集器并发去重 + 备份恢复即时重载 + context 贯穿请求链
- CollectorService 安装/卸载按采集器互斥去重(重复触发不再并发 docker rm/run 互踩);删除后卸载失败留日志
- ApplyStagedBackup 替换文件后立即 Reload 内存态,消除「重启前旧内存落盘覆盖恢复文件」竞态;暂存文件名升级 lokistack.json(兼容旧名)
- GetUsage/Validate 全链路 context 贯穿:请求取消即中止 SSH/k8s 查询(异步调用点显式 Background)
|
2026-10-29 13:29:25 +08:00 |
|
opsx
|
9daaf7f403
|
docs: CHANGELOG 落地与发布流程说明
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 13:28:23 +08:00 |
|
opsx
|
c5739d1a06
|
feat(alerts): 内置规则默认启用并绑定默认通道,开箱即用
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 新种规则 Enabled: true;启动时执行 SeedAlertRules(此前仅部署成功调用,存量环境拿不到升级)
- 存量一次性迁移:内置名单内且从未关联通道的规则补绑默认通道并启用;用户已自配通道的不动
- 管理员只需给「默认告警」通道填 Webhook 并启用;通道未就绪时命中只记发送历史(failed+原因),不影响规则评估
|
2026-10-29 13:14:05 +08:00 |
|
opsx
|
c08190458f
|
fix(alerts,db): 清理历史误种的 K8s 基础设施告警规则 + 数据迁移不留档
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- seed 启动时一次性删除早期版本种入的「Kubernetes 节点 NotReady」「容器 OOMKilled」
(日志平台只关注应用日志,节点/容器状态告警归 Prometheus;源码早已移除该模板,但幂等只跳过不清理,旧库存量一直在)
- 数据文件迁移为 lokistack.json 后直接删除旧文件,不再留 .migrated-*.bak(测试同步)
|
2026-10-29 13:09:14 +08:00 |
|
opsx
|
231fa8e774
|
fix(settings): 日志保留时间移出顶部并紧凑化
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
通用配置卡内顺序改为 主题 → 语言 → 日志保留时间;保留时间从嵌套整卡
压缩为单行分区(标签 + 两个数字输入 + 内联保存),与主题/语言分区同族,
删去 desc 与两条 hint 冗余文案(中英同步)。
|
2026-10-29 13:06:31 +08:00 |
|
opsx
|
cddd60045c
|
fix(db): 首次加载历史命名数据文件即迁移为 lokistack.json
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
旧回退链(lokistack.json → opsxloki.json → lokideploy.json)会永远沿用旧文件名,
旧品牌残留一直不消;现在首次从旧文件成功加载后立即原子写入 lokistack.json,
旧文件改名 .migrated-*.bak 留档(不删数据),新旧并存或写失败时退回旧路径安全运行。
|
2026-10-29 13:04:24 +08:00 |
|
opsx
|
4f74fe6c3f
|
ci(engineering): Gitea Actions 门禁 + 前端测试设施 + 版本注入修复
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- .gitea/workflows/ci.yml:后端 gofmt/vet/test + 前端 lint/test/build 双 job 门禁
- vitest + @testing-library + jsdom 落地,14 个命脉用例:useListQuery 乱序丢弃/卸载中止、
http.ts 401 单飞刷新+队列重放+失败登出+silent 直拒、审计防抖、StepDeploy 部署目标、query-utils
- 版本注入断裂修复:main 补 buildTime/gitCommit 符号,build.sh/Dockerfile 注入 main.version,
/health 返回真实版本,前端 package.json 版本对齐 2.0.0
- gofmt 清零(8 个存量未格式化文件)
|
2026-10-29 13:02:19 +08:00 |
|
opsx
|
2975452c21
|
fix(security,data): P0 — 命令注入/停止毁数据/采集路径绕过/DB 清库/前端竞态
后端(实机 10.10.10.161 验证 host-binary 全链路 + 注入双路径 400):
- Loki 版本号严格 ^\d+.\d+.\d+$ 校验并接入 Update 路径:version 拼进 SSH shell 命令,
旧校验放行 3.0.0'; id; echo ' 等注入载荷(root RCE)
- Stop/Restart 清理清单移除 PVC:停止部署曾连带删除 loki-data PVC,filesystem 存储日志被永久清空(测试锁死红线)
- 采集敏感路径防护覆盖默认 LogPath 分支与 collector Create(/etc/shadow 不再可绕过入库)
- 数据文件损坏时原文件改名 .corrupt-* 留档再空库启动,不再被静默覆盖(loadFromDisk 抽出可测)
前端:
- 审计页用户名过滤 300ms 防抖 + AbortController/版本号(逐键竞态数据错乱)
- 向导自动部署改用创建返回实例(或 id 最大且 pending),不再盲取 list[0] 对错误实例发起真实部署
- 路由级 ErrorBoundary:单页崩溃/chunk 404 只影响当前页
|
2026-10-29 13:02:11 +08:00 |
|
opsx
|
5e59347c40
|
feat(settings,analysis): 日志保留时间可配(发送历史保留 7 天)+ 分析页手动刷新;修复我引入的 RWMutex 死锁
=== 0. 【严重】修复上一版引入的死锁(平台写路径全卡死)===
上一版把保留期读取写成 `GetRetentionSettings()`(内部 db.RLock()),
却在 `db.Lock()` 已持有的情况下调用(recordDelivery / RecordAudit 的裁剪路径)。
Go 的 sync.RWMutex **不可重入** → 第一次写投递历史/审计日志就永久死锁。
现象:登录 28 秒返回空(登录也要写审计)、所有写接口挂住。
go build / go vet / 之前的单测(不触发写路径)全都查不出来。
修法:
- 拆出 retentionFromDBLocked(db) / alertDeliveryRetentionLocked(db) /
auditLogRetentionLocked(db),调用方已持锁时用无锁版本
- upsertSettingLocked 里**不能**用 db.NextID()(内部 d.mu.Lock(),同样死锁),
改为锁内自算 max(id)+1
- 新增 retention_test.go:持写锁调用裁剪函数,2 秒超时未返回即判死锁
实测修复后:登录 103ms(原 28000ms)、panic/deadlock 命中 0。
=== 1. 平台数据保留期可配(系统设置 → 通用配置 → 日志保留时间)===
新增 GET/PUT /api/v1/settings/retention:
alertDeliveryDays 告警发送历史保留天数,默认 7(原写死"最近 50 条")
auditLogDays 审计日志保留天数,默认 90(原写死常量)
保存后立即按新策略裁剪一次,不必等下次触发。
校验:发送历史 1~365 天、审计 1~3650 天,超出返回明确中文原因(不是 500)。
注意与「Loki 日志保留期」区分:那个是部署级配置(组件部署 → 保留策略)。
=== 2. 发送历史:50 条硬上限 → 保留 7 天 ===
后端 Deliveries 接口原来写死 `len(out) < 50`,现在按保留期取(响应上限 500 可调),
标题改为「发送历史(保留 N 天)」,N 从设置里读。
=== 3. 菜单「日志服务」→「组件部署」===
=== 4. 分析页:去掉「全部命名空间」+ 改手动刷新 ===
- 移除「全部命名空间」选项:{namespace=~".+"} 要扫全集群(约 370GB/天),
一次分析几十秒,得不偿失;历史值 __all__ 也会被忽略并回退到策略/第一个 ns
- 筛选改为「选择值 / 已生效值」分离:切换命名空间或时间范围**不再自动查询**,
只把刷新按钮点亮并提示「筛选条件已改,点刷新生效」;
点一次才应用并取数。
原因:一次分析要跑多条 count_over_time,切一下下拉就重查一轮,
快速连点会把 Loki 打满(并发护栏 429),还拖慢别人。
=== 实测(9739c7c092e7)===
登录 103ms / token 273 字符
GET retention → {alertDeliveryDays:7, auditLogDays:90}
PUT 0 天 → 400「告警发送历史保留天数需在 1~365 之间」
平台日志 panic/deadlock 命中 0
go test ./... 全绿(含 2 个新死锁回归用例)
|
2026-10-29 04:16:52 +00:00 |
|
opsx
|
7369a17598
|
feat(alerts): 数据库连接告警覆盖 MySQL/PG/Redis 主流报错;规则表单三字段对齐
=== 1. 「数据库连接失败」匹配模式 3 → 25 个 ===
原来只匹配 connection refused / reset by peer / too many connections,
漏掉了 PG 的 FATAL: too many clients、Redis 的 NOAUTH / WRONGPASS /
connection pool timeout 等实际最常见的报错。
按数据库分类补齐:
通用网络层
connection refused / connection reset by peer / broken pipe /
no route to host / cannot assign requested address /
connection timed out / i/o timeout / unexpected EOF /
server closed the connection unexpectedly
MySQL
too many connections / communications link failure /
driver: bad connection / could not create connection /
Access denied for user
PostgreSQL
FATAL: password authentication failed / sorry, too many clients /
terminating connection
Redis
NOAUTH / WRONGPASS / READONLY you can't write against /
CLUSTERDOWN / max number of clients reached
连接池(Java/Go 应用层最先炸的地方)
connection pool / connection is not available
**刻意不加**的(会匹配到几乎所有网络日志,等于没告警):
裸 "EOF"、"timeout"、"error"、"refused"、"deadlock"
阈值保持 1000/15min(模式变多不代表命中变多,都是真错误才命中)。
迁移 v9→v10 只替换"仍是旧默认查询"的规则,用户自定义过的不动。
=== 2. 规则表单三个字段没对齐 ===
现象:统计窗口(分) / 触发阈值(条) / 冷却(分) 三格并排,
但只有中间的「触发阈值」下方多了一行提示文字,
导致中间那格更高、提示文字悬在底部,看起来像没对齐。
修法:三格统一为「标签 + 输入框 + 一行提示(min-h-4 固定高度)」,
底边自然齐平;提示文案改为:
统计窗口 → 统计最近多少分钟
触发阈值 → 超过该条数才告警
冷却 → 冷却内不重复发送
(顺带把阈值提示从"0 = 出现即告警"改掉:默认阈值已无 0,0 反而是危险配置)
小屏改单列堆叠(grid-cols-1 sm:grid-cols-3)。
=== 实测(936a127b1d15)===
v10 迁移:告警规则 "数据库连接失败" 扩展匹配模式
线上规则:25 个模式,含 NOAUTH / too many clients,阈值 1000,冷却 240m
Pod = latest = v0.1 = 936a127b1d15
|
2026-10-29 03:56:07 +00:00 |
|
opsx
|
cb1ef1dff4
|
fix(alerts,analysis): 告警引擎稳定性 5 项修复;每日量接口去掉同步 Loki 查询(20s→10ms)
=== 一、告警引擎稳定性审查(引擎本身是异步的:ticker + goroutine)===
1) 【高】ticker 无重叠保护
ticker 每 1 分钟触发;一轮规则检查 8 条 × 20~30s = 2~4 分钟,远超一分钟。
原来每过一分钟就再开一轮 → 查询越堆越多,抢走页面查询的 Loki 资源,
每轮越来越慢,最终表现为"告警不响了"。
→ 新增 alertCheckRunning(atomic.Bool),上一轮未完成直接跳过本轮并记日志。
2) 【高】单条规则无超时
RunAlertCheck(context.Background()) 没有 deadline:Loki 一旦挂住,整轮永久运行,
叠加问题 1 会让引擎再也不启动新一轮(静默死亡)。
→ 每轮总超时 5 分钟(alertRoundTimeout)+ 单条规则 90s(alertRuleTimeout)。
3) 【中】panic 只在整个 tick 层 recover
一条规则 panic 会让本轮后续规则全部跳过。
→ per-rule recover,panic 记为一条 failed 投递记录,后续规则继续。
4) 【中】冷却记录每分钟写一条,把真实发送记录挤光
AlertDeliveries 只保留最近 50 条,而处于冷却期时每个 tick 都写一条 cooldown,
一小时内真实的"已发送"记录就全被挤掉了,发送历史没法看。
→ recordCooldownThrottled():同一规则 10 分钟最多写一条。附单测。
5) 规则串行本身保留(避免同时压垮 Loki),但配合 1) 才安全。
=== 二、每日日志量接口:20 秒 → 10 毫秒 ===
根因:GetDailyVolumeHistory 里**同步** await 一次"整天 index/stats"补抓当天数据,
那次查询超时 20s → 整个接口 20s → 前端超时 → 页面显示"每日日志量没数据";
更糟的是这条重查询还和页面的 histogram/stats 抢 Loki,导致那两个接口同时 502
(访问日志实证:daily-volume latency_ms 20000.997,stats/histogram 502)。
→ 接口改为**只读库**,不触发任何 Loki 查询;当天数据由后台每 30 分钟的
定时任务补(CaptureRecentDailyVolumes)。
→ 同时把整天 index/stats 的超时从 20s 提到 90s(370GB/天,20s 不够)。
实测:10ms。
=== 三、通知通道改名「默认告警」(schemaVersion v8→v9) ===
"lark 默认告警" / "默认通知通道(待配置 Webhook)" → 统一为「默认告警」。
只改名,不动 Webhook、不动 enabled:
首次部署创建的占位通道必须保持「空 Webhook + 禁用」,由管理员自己填,
否则会出现"已启用但空地址"的通道,告警一命中就全部投递失败。
=== 四、其它文案 ===
「每日日志量」副标题精简为「每天的日志总量,平台保留历史趋势」,
并移除「平台留存历史」徽标(用户反馈多余)。
=== 实测(303134c0c718)===
/logs/daily-volume:10ms(原 20000ms)
通道列表:id=1 name="默认告警" enabled=true hasWebhook=true
go test ./... 全绿(含新增 TestRecordCooldownThrottled)
|
2026-10-29 03:38:30 +00:00 |
|
opsx
|
607b67a2ed
|
fix(alerts,analysis,dashboard): 阈值再上调 + 告警风暴全局闸门;修 subtitle/门控/饼图 Top5
=== 1. 告警阈值第二轮上调(500~5000 这一档仍然太敏感)===
实测业务高峰期这些量级就是常态,阈值贴着基线等于"每天准点报警":
应用错误激增 50000 → 100000(冷却 120m → 240m)
ingress 5xx 500 → 2000 (冷却 240m)
ingress 4xx 5000 → 20000 (冷却 240m)
数据库连接失败 200 → 1000 (冷却 240m)
迁移 v7→v8,只改"仍等于旧默认值"的规则,用户手动调过的不动。
=== 2. 告警风暴抑制:新增全局闸门 ===
单条规则的 cooldown 只能防"同一条重复",挡不住"多条规则同时炸"。
实际踩过:一次存储抖动让 8 条内置规则同时命中,10 秒内群里刷了 8 条,
之后大家开始无视这个群 —— 告警疲劳比漏报更难挽回。
新增 overStormLimit():最近 1 小时内全平台成功发送数 ≥ 12 条时,
后续命中不再发送,只写一条状态为 cooldown 的发送记录并注明
"告警风暴抑制:最近 1 小时已发送 N 条(上限 12)",可见可追溯。
=== 3. 分析页 subtitle 仍是裸 key ===
中文语言包缺顶层 subtitle(英文包有),i18next 找不到就把 key 原样输出。
补齐:「按应用与命名空间看日志量、错误与趋势,找出最吵的业务」。
=== 4. 分析页三条"没有应用维度的数据"的真正原因 ===
「活跃应用」「应用日志量 Top 5」「应用日志量流图」都依赖 by-app 重查询,
而这条查询被**底部排行卡片的可见性**门控(rankingEnabled: rankingView.inView)——
用户不滚到底就永远不发请求,于是顶部三块永远是空的。
改为进页面就发(两阶段渲染:索引类指标先出,app 维度到货后补)。
懒加载只保留在真正位于底部的「每日日志量」「Ingress 分析」。
=== 5. 饼图改 Top 5 + 文案 ===
用户要求:应用日志量 Top 5 用饼图。TOP_SLICES 8 → 5,
标题改「应用日志量 Top 5」,副标题写明"其余归入其他"。
=== 6. 卡片顺序 ===
「每日日志量」从中间移到「应用排行」之后(更靠底部,慢慢加载)。
=== 7. 仪表盘 ===
整个「平台自检」卡片(含所有指标行)按用户要求移除。
=== 实测(2e8c88817b6a)===
v8 迁移:4 条规则阈值上调,日志齐全
当前阈值:FATAL>10 / panic>10 / OOM>5 / StackOverflow>5 /
应用错误>100000 / ingress 5xx>2000 / 4xx>20000 / DB 连接>1000
冷却:低频规则 120m,高频规则 240m
|
2026-10-28 23:43:14 +00:00 |
|
opsx
|
fbefcc86df
|
feat(logs,analysis): 分页改"加载更多"+触底自动加载;修懒加载不触发;指标短缓存
=== 1. 日志列表:去掉页码分页,改「加载更多」+ 触底自动加载(节流)===
为什么不用"上一页/下一页/共 N 页":
- Loki 是**游标分页**,不支持 offset,页码天生跳不了页
- 规模上没意义:15 分钟 4000 万条 → 按 200/页算 40 万页
- 带 |= / |~ 行过滤时总数根本算不出来(index/stats 只接受 label matcher)
日志排查的真实交互是"继续往下看",所以改为:
- 列表底部:「已加载 N 条 · 约 M 条」+「加载更多」按钮
- 滚动到底**自动加载**,但 2 秒节流 —— 每批都要后端扫 Loki,
快速滚动连续触发会撞并发护栏(429),反而比手动点更卡
- 到 5000 条停止并提示"缩小时间范围或加过滤条件"(虚拟滚动只省 DOM,
不省数组内存,无上限会一直涨)
- 修正语义 bug:分页原来是 setEntries(result.entries) **整页替换**,
改成追加(否则"加载更多"等于翻页)
- 删除 pageCursors 翻页栈与 prevPage/nextPage/pageOf/pageOfTotal 文案
=== 2. /logs/stats 错误映射:Loki 400 → 我们的 400 + 人话 ===
之前带行过滤时 Loki 回 400 "only label matchers are supported",
被统一包装成 502「Kubernetes API 代理返回 HTTP 400…」,
前端只能当失败静默 → 用户看到"总页数没了"却不知道原因。
现在返回 400 +「含过滤条件(|= / |~)时无法统计总页数:Loki 只支持按标签统计」,
前端据此在条数旁给出提示。
=== 3. 分析页:修"延迟加载永远不触发"(数据一直不出来)===
现象:应用排行/流图/饼图/活跃应用全是"没有应用维度的数据",每日量"已有 0 天",
但接口实测正常(by-app 返回 75 条序列、daily-volume 返回 2 天)。
根因:这三块用 useInView 门控,而 ref 挂在 <Card> 上,观察器不触发 → 永远 false
→ hook 直接 return → 页面永远空。
修法:
- 改用普通 <div ref> 包裹,不依赖组件是否转发 ref
- useInView 加**几何兜底**:1 秒后若元素已在视口内就直接置 true
(有些容器里 IntersectionObserver 的 root 判定会和预期不一致)
=== 4. 分析页指标短缓存(26s → 11ms)===
`sum by (app)(count_over_time(...))` 扫 15 分钟 astralx(约 6.6GB)实测 16~28 秒,
Loki 自带结果缓存不命中(时间窗每次都在变)。点刷新/切页都要再等半分钟。
现在按 (数据源, 查询, 时间窗) 缓存 90 秒,时间窗按 2 分钟取整当 key:
实测第 1 次 23514ms → 第 2 次 **11ms**
代价:命中缓存时数据最多旧 ~2 分钟,对聚合视图可接受。
=== 5. 仪表盘 ===
去掉「平台自检」「N 项需关注」两段文字:卡片本身就是自检结果,
标题重复一遍没信息量;状态改由图标+边框颜色表达。
=== 6. 其它文案修正 ===
饼图空态从「没有命名空间维度的数据」改为「没有应用维度的数据」
(饼图上一版已改成应用维度,文案没跟上)。
=== 实测(1da8a148d756)===
Pod = Hub latest = v0.1 = 1da8a148d756
stats:纯 selector → 200(7005 万条);带 |= error → 400 + 人话
分析指标缓存:23514ms → 11ms
每日量:2 天(10-27 32.5GB / 10-28 356.5GB)
文案:加载更多 / 已到末尾 / 达到上限 均进包;代码中已无 prevPage/nextPage 引用
|
2026-10-28 23:36:32 +00:00 |
|
opsx
|
bad61e5027
|
fix(deploy): 删除部署时同步清理自动注册的查询数据源(孤儿数据源)
=== 现象(用户实测)===
在 /deployments 页面删掉两个主机模式部署后,本机(172.19.16.8)容器确实清干净了:
docker ps -a 无任何 lokistack-* 容器
/etc/promtail、/var/lib/promtail 等目录均不存在
无 promtail systemd 单元
但**平台里残留一个数据源** host-verify-loki(url=http://172.19.16.8:3100),
「日志查询」下拉里还能选到它,选中必然连接失败。
=== 根因 ===
DeploymentService.Delete 只做了两件事:删容器、删部署记录。
部署创建时由 EnsureLogSourceForDeployment 自动注册的托管数据源(Managed=true)
没有任何清理逻辑 —— 删除动作只完成了一半。
=== 修法 ===
1) DeploymentService.Delete 删完记录后调用 CleanupManagedLogSource(部署名)
- 删掉同名且 Managed=true 的数据源(手填的 Managed=false 不动)
- DeleteLogSource 内部会连带删除指向它的查询授权策略组
- 绑定该源的告警规则置为禁用(保留配置便于审计,但不让它每次评估都失败)
- 注意锁顺序:必须先 db.Unlock() 再调用,CleanupManagedLogSource 内部会自己加锁,
持有锁再调用会死锁
2) schemaVersion v6→v7 迁移 removeOrphanManagedLogSources()
清理历史遗留:Managed=true 但没有任何同名部署的数据源 → 删除,
连带删策略组、禁用告警规则
=== 实测(d9487694ece6)===
v7 迁移日志:移除孤儿数据源 "host-verify-loki"(id=2):对应部署已删除
数据源:1 个(只剩 loki-pre,managed=true)
部署:1 / 采集器:1 / 策略组:1
本机:docker ps -a 无 lokistack-* 容器,/etc/promtail 等目录不存在,无 systemd 单元
|
2026-10-28 23:18:43 +00:00 |
|
opsx
|
6d6e17745c
|
fix(logs): 恢复 POST /logs/histogram(直方图 404)+ 系统设置改为侧栏五分页
=== 1. 修复 /analysis 与 /logs 直方图 404(我上一轮 patch 的误伤)===
现象:分析页直接报 "API endpoint not found";日志查询页直方图不显示。
根因:37e2f0f 里一次模糊匹配的 patch 把
logs.POST("/histogram", logsCtrl.Histogram)
改成了
logs.GET("/histogram", logsCtrl.Histogram)
前端 logsApi.histogram 一直用 POST(body 带 sourceId/query/start/end),
于是 POST /logs/histogram 全部 404。访问日志实证:
POST /api/v1/logs/histogram → 404 ×7
这类"方法写错"编译期查不出来,go build / tsc 都是绿的。
修法:同时注册 POST(前端用)与 GET(curl 调试用)。
新增 backend/routers/router_test.go 守门:
TestCriticalRoutes 断言 19 条关键路由的 method+path 组合都存在
TestHistogramAcceptsPost 单独断言 histogram 必须同时有 POST 与 GET
以后再把 POST 写成 GET,测试会直接红。
=== 2. 系统设置放回侧栏,改成 5 个顶层 tab ===
按使用反馈调整:
系统设置(侧栏一个入口)
├ 通用配置 /settings ← tab 起点
├ 用户管理 /users
├ 审计日志 /audit
├ LDAP 接入 /settings/ldap ← 新拆分
└ 默认镜像源 /settings/registry ← 新拆分
为什么拆:原来 /settings 页面内部已有 4 个 tab(通用/告警通知/LDAP/镜像源),
如果侧栏再加一层 tab 就是"两层 tab",点两下才知道自己在哪。现在扁平化成单层。
配套改动:
- 新增 pages/settings/ldap.tsx、registry.tsx(各自只渲染一块内容)
- settings 主页去掉内层 Tabs,只留通用配置(外观/语言/改密码/关于)
- 上一版把设置放到了右上角齿轮,现在移除齿轮入口
- 菜单文案:settings 页标题 系统设置 → 通用配置;新增 LDAP 接入 / 默认镜像源
- 删除设置页副标题「通用配置、告警通知、LDAP 接入与默认镜像源」(用户要求去掉)
- 删除 settings 里已不存在的 tabs.notify 文案
(注意 settings.notify.* 这些 key 仍被告警页的 NotifySection 用着,保留)
=== 3. 告警通知已不在系统设置里 ===
它在上一轮就搬到了「日志告警 → 通知通道」tab,
本次顺带把系统设置里遗留的 tab 文案一并清掉,避免出现"看似有两处配置入口"。
=== 实测 ===
POST /api/v1/logs/histogram → 200(修复前 404)
go test ./... 全绿(含新增的 routers 包)
tsc -b --force 通过
|
2026-10-28 23:12:42 +00:00 |
|
opsx
|
8bea331183
|
chore(release): 发布脚本对齐"统一使用 latest"策略
清单本来就用 latest(deployment.yaml / docker-compose.yml / prod.yml 的
${IMAGE_TAG:-latest}),但上一版 release.sh 会把 deployment.yaml 改成
不可变 tag 并把 pullPolicy 改成 IfNotPresent —— 与"统一用 latest"相反,
而且改完不提交的话下次 apply 又会退回最新 tag,容易造成实际版本与预期不符。
改为:
1) 一次构建打三个 tag:latest / v0.1 / <版本>-<日期>-<commit>,同时推送
2) 推送后**校验三个远端 digest 一致**,不一致直接退出(并发构建覆盖
latest 的经典事故,先拦住别部署)
3) 部署走 kubectl apply + rollout restart(image=latest + Always 强制重拉)
4) 部署完再核对 Pod digest 与远端 latest,不一致立即提示
5) 不再默认改清单;需要"固定版本部署"时显式 `--pin`
6) 回滚方式写清楚:把旧的不可变 tag 重新打成 latest 推上去
同时把集群里被我为了避开 tag 竞争临时改成 v11-1028-2252 的 Deployment
恢复成 latest + Always(已验证 Pod / Hub latest / Hub v0.1 三者 digest 一致
= 187062644c0f)。
|
2026-10-28 23:02:41 +00:00 |
|
opsx
|
37e2f0febc
|
feat(analysis,alerts): 每日量落库看历史趋势、趋势图/饼图可读性、告警三分页
=== 1. 每日日志量必须落库(核心问题:Loki 只留 24 小时)===
实测:请求"近 7 天每日量",Loki 只返回 27 个**小时**桶,全部在最近 24h 内 ——
因为 retention_period=24h,更早的 chunk 已经被删了。
也就是说「固定近 7 天」这张卡片,过去一直是"6 天空白 + 1 天有数据",
用户看着像坏了,实际是数据物理不存在。
方案:平台自己留存历史。
- models.DailyVolumeSnapshot{Date(本地YYYY-MM-DD), SourceID, Bytes, Entries, Partial}
- services/daily_volume.go
CaptureDailyVolume 按天抓 index/stats 落库(幂等覆盖)
StartDailyVolumeCapture 启动 20s 后回补 + 每 30 分钟检查一次
GetDailyVolumeHistory 读库返回最近 N 天(今天会实时补抓)
- 保留 400 天(够看年度趋势),按 source+date 去重
- 前端可选 7 / 30 / 90 / 365 天,能看多长取决于平台积累多久
为什么用"每 30 分钟检查昨天有没有落库"而不是固定 00:10 定时:
平台可能整晚没运行,固定时点的定时器会直接错过;检查式回补不依赖平台在线时刻。
Partial 标记(关键,否则会误读):
- 当天未结束 → partial=true
- 待抓取日期的起点已超出 24h 保留期 → partial=true(Loki 里就没那么多数据)
实测:10-27 32.5GB/2.65亿条 partial=true(保留期外),
10-28 348GB/28.56亿条 partial=true(当天进行中)
=== 2. 修一个 Go 副本 bug(自己写的,靠实测发现)===
第一版用 `for _, snap := range db.DailyVolumeSnapshots` 后改 snap 字段,
range 拿到的是**副本**,改动不会写回切片 → 同一天永远保持首次抓取的值,
partial 也永不更新(实测发现 10-27 该是 true 却显示 false)。
改为 `for i := range ... { snap := &db.DailyVolumeSnapshots[i] }`。
=== 3. 趋势图可读性 ===
从"裸折线"换成"渐变面积图",并在图上方给出峰值 / 均值:
- 面积图能一眼看出"这一段整体比上一段高",折线只能靠对齐格子数
- 峰值/均值避免用户自己对刻度
- Y 轴与 tooltip 统一 formatBytes,"每桶体积"写进 subtitle
顺带确认:趋势图只画体积(字节),不再混入 error(条数)——单位不同不能同轴。
=== 4. 饼图:命名空间分布 → 应用日志量分布 ===
本环境的命名空间只有 astralx / log 两个,饼图没有信息量;
改成按应用看谁在刷日志(前 8 名 + 其他),才是运维真正要的答案。
=== 5. 告警页拆成三个 Tab ===
「告警规则 / 通知通道 / 发送历史」独立成分页(用 ui/tabs)。
之前通道是折叠块、历史堆在规则下面,规则一多就要滚很久才看得到。
发送历史增加空状态文案,不再是"空页面不知道有没有在发"。
=== 6. 内置规则关联默认通道 + 首次部署 webhook 留空的评估 ===
结论:**webhook 可以留空,但通道必须是禁用状态**。
- 若种子给的是"已启用 + 空地址"通道,规则一命中就必然投递失败,
发送历史堆满红色失败,反而让人以为平台坏了
- 所以首次部署建一个**禁用**的占位通道「默认通知通道(待配置 Webhook)」,
内置规则全部挂到它;管理员填好 Webhook 再启用即可,不用逐条关联
- 保存时加校验:启用则必须有非空 Webhook(返回明确原因,不是裸 500)
- schemaVersion v5→v6:把已有的"无通道"规则补挂到默认通道
实测:8 条规则全部已关联通道
=== 实测(v11-1028-2252 / 187062644c0f)===
/logs/daily-volume?days=7 → 2 天数据,partial 标记正确
规则 8 条全部有 channelIds
平台 ready、健康 ok
|
2026-10-28 22:54:12 +00:00 |
|
opsx
|
cb91470d75
|
perf(analysis): 分层加载 + 修复裸 i18n key + 单位混用;附 Loki 查询方式的实测对比
=== 1. 界面显示裸 key(trend.subtitle / daily.subtitle / table.subtitle)===
根因:中文语言包只加了 title,**没有 subtitle**,而英文包有。
i18next 找不到 key 就把 key 原样输出,所以界面上出现 "trend.subtitle" 这种文字。
已补齐 zh 的 trend/stream/pie/daily/table 五个 subtitle,并把文案改成
人话(说明数据来源与是否需要等待),例如:
trend : 按时间分桶的日志量(来自索引统计,秒级返回)
daily : 近 7 个自然日的每日总量(来自索引统计,不受上方时间范围影响)
table : 按日志条数排前 10 的应用,点「查看日志」跳到查询页
=== 2. 分层加载:上面的先出,下面滚到再算 ===
新增 hooks/useInView.ts(IntersectionObserver,提前 200px 触发,只触发一次)。
- 首屏只跑索引类查询(histogram + index/stats),**合计 <100ms**
- 「每日日志量」「应用排行」「Ingress 分析」三块滚动到视口才发查询
- 排行卡片加载中显示"正在按应用统计(需读取日志内容,通常 20~30 秒)…",
不再是空表格让人以为卡死
=== 3. 修复自己引入的单位混用(重要)===
趋势图数据源从 count_over_time 换成 index/volume_range 后,返回值是**字节**,
但排行表里的值是**条数**,两者混用导致:
- 统计卡显示 7956179750 这种看不懂的裸数字
- 排行占比算成 0.0x%(分子条数、分母字节)
修法:
- 统计卡拆成两张且都走索引接口(实测 histogram 50ms / index-stats 43ms):
日志条数(索引统计) ← index/stats entries,精确条数
日志体积(索引统计) ← volume_range 累计,格式化为 GB
- 排行占比分母改用 apps 自身条数合计
- 去掉"告警行数"卡片:为了把重查询从 3 条降到 2 条已放弃全局 warn 统计,
显示一个恒为 0 的数字比不显示更误导
=== 4. Loki 查询方式实测对比(15 分钟 astralx,370GB/天)===
直接打 Loki,逐个验证卡在哪:
sum(count_over_time({ns}[30s])) step=30s 61s 超时 ← 全集群全局
sum by (app,ns)(count_over_time([30s])) step=30s 19.7s 5.5GB ← 现用
sum by (app,ns)(count_over_time([15m])) step=15m 47s 超时 ← 反直觉!
sum by (app)(count_over_time([30s] |= error)) step=30s 12.7s 5.5GB
单 app gateway count_over_time step=30s 12.6s 0.58GB
index/volume (limit=200) 2.2s ← 只给 selector 级,不能按 app
index/volume_range (step=60s) 133ms
index/stats 112ms ← entries/bytes 精确
两个结论(写进注释,避免以后又"优化"回去):
a) **不要把 range 和 step 一起放大**:`[15m]` + step=15m 看着"点数少",
实际要让 Loki 为单个 evaluate 点加载 15m 的 chunk,比 [30s]+step=30s 慢 2 倍以上
(47s 超时 vs 19.7s)。
b) **按 app 分组无法走索引**:Loki 3.4 的 index/volume 只回落到 selector 标签,
不支持 aggregateBy=app(传了也无效果)。所以 per-app 排行只能读日志内容,
20 秒左右是这台集群的物理下限 —— 只能靠懒加载 + 明确提示来改善体感。
=== 实测(v8-1028-2232 / 7a05683a91ae)===
logs/histogram 50ms logs/stats 43ms
by-app 排行 25077ms(懒加载,滚动到才发)
中文 subtitle 已进包:来自索引统计 / 按量排前 / 各命名空间的日志量占比 /
近 7 个自然日 / 按日志条数排前 10 各命中 1 次
|
2026-10-28 22:34:04 +00:00 |
|
opsx
|
fa7ddd67c1
|
perf(analysis): 分析页按索引/内容分层,告警默认阈值抬高
=== 1. 分析页"一直转圈/请求超时(40000ms)"的真实原因 ===
三个问题叠加,逐个量化:
a) 首屏用空 namespace 发查询
页面初始 namespace='',等策略返回前就发出 `{namespace=~".+"}` 全集群查询,
策略到位后又查一次 → 一次访问跑两遍。
→ 加 namespaceReady 门控:解析完再发(effectiveSourceId)。
b) histogram 索引查询和内容查询抢同一个信号量
/logs/histogram 首选 index/volume_range(毫秒级),但仍要先抢重型令牌,
而令牌被 4 条 count_over_time(各 14~29s)占满 → 排队/429/超时。
→ 索引路径**不再占用重型令牌**(15s 独立超时),只有回退到
count_over_time 时才抢令牌。实测各跨度(15m/1h/6h/24h)均 0s。
c) 重查询条数过多
原来 4 条全局 count_over_time(total/error/warn)+ 2 条 by-app,
串行 1~2 分钟。现在只留 2 条 by-app:
- 全局总量已由 histogram(索引)提供
- 全局 error 由 errByApp 序列求和得出
- 全局 warn 放弃(优先级最低却要 20s)
实测 by-app 单条 26s,两条串行 ~52s;主图 0s 先出。
d) 日报 7 天 count_over_time(2.6TB 扫描)必然失败
→ 改走 /logs/histogram(索引)按自然日聚合,只出"每日总量",
应用维度交给下方 Top 应用表格(有自己的时间范围)。
实测 7 天跨度毫秒级。
=== 2. 告警默认阈值抬高(告警疲劳治理)===
验证时用 `{namespace="astralx"}` + 阈值 0 触发了一次:
15 分钟命中 3051 万条 —— 阈值 0 等于把告警变成日志流复读机。
新基线(15 分钟窗口,冷却统一 120 分钟):
致命日志 FATAL 0 → 10
Go panic 0 → 10
JVM OutOfMemoryError 0 → 5
StackOverflowError 0 → 5
应用错误激增 1000 → 50000(改名,原"error > 1000"名不副实)
ingress-nginx 5xx 100 → 500
ingress-nginx 4xx 1000 → 5000
数据库连接失败 50 → 200
迁移只改"仍等于旧默认值"的规则,用户手动调过的阈值不动。
设计原则写进 seed 注释:绝不用 threshold 0;宁可少报也不要让人对告警脱敏;
绝对阈值不跨环境通用,大集群必须按 app/namespace 收窄。
=== 实测(v6-1028-2218 / 0bf66199de66)===
规则 8 条,阈值=0 的规则数 0,冷却全部 120m
histogram astralx(15m/1h/6h/24h): 0s
by-app metrics astralx 15m: 26s,正常返回数据
|
2026-10-28 22:20:19 +00:00 |
|
opsx
|
95dcd9408c
|
fix(logs,analysis,alerts): 数据源不可达、分析页超时、告警规则混入基础设施项
=== 1. 主机模式数据源地址 0.0.0.0 → 主机真实 IP(查询页"永远加载中"的根因)===
现象:切换数据源后命名空间/App 下拉不动,一直显示"加载中…",控制台报
Get "http://0.0.0.0:3100/loki/api/v1/labels": dial tcp 0.0.0.0:3100: connect: connection refused
根因:主机模式部署的 endpoint 记的是**容器监听地址** 0.0.0.0:3100,
而平台进程不在被采集主机上,0.0.0.0 只在本机有意义。
EnsureLogSourceForDeployment 把它原样写成了数据源 URL。
修法:
- 新增 reachableEndpoint():把 0.0.0.0 / [::] 换成关联主机的真实 IP
- schemaVersion v3→v4 迁移 fixUnreachableLogSourceURLs(),修正已有脏数据
- 实测:http://0.0.0.0:3100 → http://172.19.16.8:3100,该源 label 查询由 502 变 200
=== 2. 前端:区分"加载中"与"加载失败" ===
原来用 `loading={!options.length}` 表示加载中 —— 数据源不可达时列表永远为空,
于是界面永远转圈,把真实错误完全藏起来了(这次的 0.0.0.0 就是这样被掩盖的)。
改为独立的 namespaceLoading / namespaceError 两个状态:
- 切换数据源先清空旧 options(否则显示上一个源的命名空间,看着像"没反应")
- 失败显示"加载失败"并在旁边给「重试」按钮,不用刷新整页
=== 3. 日志分析页:主图改走索引接口(原来 5 条 count_over_time 全超时)===
实测 astralx(约 370GB/天)15 分钟窗口:
count_over_time → exec 29s + 排队 95s,超过 30s 护栏 → 502
index/volume_range → 0s
改为两阶段渲染:
- 总量趋势改用 /logs/histogram(内部 index/volume_range),**0s 出图**
- 错误/警告/app 维度仍需读日志内容,串行执行,先渲染主图再更新
- heavyQueryTimeout 30s → 90s(实测 error 维度查询 14s 可完成)
=== 4. 告警规则按日志平台定位收敛 ===
- seed 里删掉「Kubernetes 节点 NotReady」「容器 OOMKilled」:
这类属于 kube-state-metrics / Prometheus 的职责,用日志字符串匹配不可靠
- 迁移里清掉线上已存在的同类规则(3 条)
- 修 AlertRule / NotifyChannel 重复 ID(同 logSources 一样漏在 sequences 重建外)
=== 5. 告警页 UI ===
- 规则列表由"每条一张卡"改为紧凑表格(一屏看全,阈值可对齐比较)
- 通知通道(飞书/Lark/Webhook)从「系统设置」搬到告警页折叠区:
规则和"发到哪"是一件事的两半,分两页配容易漏配导致告警发不出去
=== 6. 权限收敛 ===
- /alerts 加 adminOnly:普通用户按查询授权只看「日志查询」,不再看到告警菜单
- 系统设置从侧栏移到右上角齿轮(低频平台配置,不占侧栏一位)
=== 7. 迁移设计教训(写进代码注释)===
v2 时把"告警去重 + 清基础设施规则"塞进 case 1,但线上库已到 v2,
那段代码永远不执行。**结构变更只要逻辑变了、目标库已达该版本,就必须再抬一版**,
不能改历史迁移分支。
=== 实测(v4-1028-2210 / 979bdb4a48e8)===
v4 迁移:数据源 URL 修正 http://0.0.0.0:3100 → http://172.19.16.8:3100
source 1 label:200,0s(astralx / log)
source 2 label:200,0s(不再 connection refused)
分析页 histogram astralx 15m:0s
分析页 metrics error astralx 15m:14s
告警规则:10 条(已无基础设施类)、ID 唯一
另清理 172.19.16.8 上 13 个残留容器,回收 6.1GB
|
2026-10-28 22:11:48 +00:00 |
|
opsx
|
1a4c75e28c
|
feat(menu): 日志查询/日志分析拆为独立菜单 + 部署菜单改名「组件部署」
按使用反馈调整信息架构:
=== 1. 日志查询、日志分析保持独立菜单(不折叠)===
上一版把两者并进「日志」分组,实际使用中不方便:
- 日志查询是最高频入口,不该多点一次 tab 才到
- 日志分析是管理员专用的聚合视图,与查询的心智模型不同
改为两个独立菜单项,/logs 的菜单名保持「日志查询」不变(用户明确要求),
/analysis 保留 adminOnly(仅管理员可见)。
=== 2. /deployments 菜单改名:部署 Loki → 组件部署 ===
命名评估(三个候选):
组件部署 ✅ 选中:部署对象是基础设施组件(Loki 实例),
与本环境大量 cex-* 业务服务明确区分。
运维在侧栏看到「服务部署」第一反应会是"发布业务服务",容易误点。
服务部署 ✗ 与业务服务语义冲突
部署 Loki ✗ 以后支持多组件(Fluent Bit / Grafana)就名不副实
文案改动:菜单 menu.deployments、页面标题、组内 tab 全部统一为「组件部署」,
en 同步为 Component Deploy / Component Deploy 页面标题。
=== 3. 顺带修:启动日志每次都打印「首次部署」===
旧实现无条件打印
log.Printf("首次部署请查看启动日志或 %s/initial-admin-password.txt ...")
于是每次重启日志里都有「首次部署」,排查时极易被误导成
"平台重新初始化了、数据是不是丢了"(本次就因此自查了一轮)。
改为仅当密码文件存在时才提示(admin 改密后该文件会被删除)。
=== 当前菜单结构(8 项)===
仪表盘 / 部署向导 / 资源接入(集群·主机·存储) / 日志服务(组件部署·日志采集)
/ 日志查询 / 日志分析(admin) / 日志告警 / 系统管理(用户·审计·设置)
=== 实测 ===
bundle 文案:组件部署1 日志查询2 日志分析2 资源接入1 日志服务1 系统管理1
启动日志「首次部署」出现次数:0
CLI self-check:overall=ok(数据库 54.7 KB,CLI 模式读取)
CLI backup:记录数 88(修复前为 0)、密钥指纹 d714ebda23908331
镜像:lghost/lokistack:latest + v0.1 = aa6313fee5bb
|
2026-10-28 21:44:15 +00:00 |
|
opsx
|
b2c82da377
|
fix(audit): inferAuditFields 下标越界导致进程级 panic(4 段路径全部 500)
=== 现象(线上实际日志)===
http: panic serving 172.19.16.138:35256: runtime error: index out of range [4] with length 4
lokistack/middleware.inferAuditFields(...)
lokistack/middleware/audit.go:179
=== 根因 ===
segments := strings.Split(strings.Trim(path, "/"), "/")
if len(segments) >= 4 {
resource = segments[3]
resourceID = segments[4] // ← 4 段时越界
}
/api/v1/hosts/2 正好拆成 4 段 [api v1 hosts 2],取 segments[4] 直接 panic。
影响面比想象大:**所有 4 段路径的写请求**(PUT/DELETE /hosts/:id、
/storage/:id、/clusters/:id 等)都 500,且中间件 panic 后审计也写不进去
—— 等于审计功能在最常用的更新/删除接口上完全失效。
=== 修法 ===
每个下标单独判断长度:
if len(segments) >= 3 { module = segments[2] }
if len(segments) >= 4 { resource = segments[3] }
if len(segments) >= 5 { resourceID = segments[4] }
=== 回归测试(middleware/audit_test.go)===
TestInferAuditFieldsNoPanic : 0/1/2/3/4/5 段的 12 种路径 × 3 种方法,
全部不得 panic(原 bug 的 4 段路径在列)
TestInferAuditFieldsFourSegments : 4 段时 module/resource 正确、resourceID 为空
TestInferAuditFieldsFiveSegments : 5 段时能解出 resourceID
TestInferAuditFieldsSemanticActions: deploy/stop/install/uninstall/reset-password/
validate/test 语义动作不被位置解析覆盖
TestShouldAudit : 只读接口跳过、部署安装类必须记
=== 顺带修:CLI 模式下的两处误报 ===
1) backup 的 manifest.recordCount 恒为 0
原因:CLI(CronJob 里跑 lokistack backup)没 init DB,GetDB() 返回 nil。
改为 countRecords() 直接从备份出的字节数集合。
2) self-check 报「数据库: 未初始化」
同上原因,误报会让巡检脚本以为库坏了。
改为 CLI 模式直接解析数据文件统计(fileDBStats)。
=== 实测(修复后)===
修复前:POST /api/v1/hosts/2/validate → panic,无审计记录
修复后:POST /api/v1/hosts/2/validate → 200
audit-logs total=5,含
[hosts/validate] admin POST /api/v1/hosts/2/validate -> 200
[auth/login] POST /api/v1/auth/login -> 200
panic serving 计数 = 0
用户数据完好(initial-admin-password.txt 不存在,说明不是首次部署)
镜像:lghost/lokistack:latest + v0.1 = 2493a288758d
|
2026-10-28 21:40:24 +00:00 |
|
opsx
|
c44ffad61f
|
fix(metrics): duplicate metrics collector registration panic(线上 CrashLoopBackOff)
=== 现象 ===
上一提交 b49677f 部署后平台直接 CrashLoopBackOff(5 次重启):
Initializing database...
panic: duplicate metrics collector registration attempted
lokistack/pkg/metrics.Register.func1() metrics.go:125
=== 根因 ===
prometheus/client_golang 在 init() 里已经把 GoCollector 与 ProcessCollector
注册进 **默认 registry**(prometheus.DefaultRegisterer)。我又对同一默认 registry
调用 MustRegister 注册了同一批 collector → 重复注册 → 运行时 panic。
致命点:go build / go vet / 普通单测**全都发现不了** ——
MustRegister 的重复检查只在运行期执行,只有真的调用 Register() 才会炸。
所以整个 P0 打磨在测试阶段是"全绿"的,一上线就挂。
=== 修法 ===
不再碰默认 registry,改用自有实例:
var registry = prometheus.NewRegistry() // 独立,不受 init() 影响
registry.MustRegister(...) // 注册全部指标
promhttp.HandlerFor(registry, ...) // 抓取读自有 registry
好处:完全掌控注册内容,未来再加 collector 不会与库的默认行为冲突。
=== 回归测试(这才是关键收获)===
新增 backend/pkg/metrics/metrics_test.go:
- TestRegisterIsIdempotent : 连续调用 Register 两次,必须不 panic
- TestHandlerExposesCustomMetrics : 断言抓取输出含 lokistack_* 与 go_goroutines
- TestRegisterConcurrent : 20 个 goroutine 并发调用不 panic
这三条在修复前必然失败,修复后全绿。以后再改指标定义会被它们拦住。
镜像:lghost/lokistack:latest + v0.1 = 2f8f3c85643e
|
2026-10-28 21:32:36 +00:00 |
|
opsx
|
b49677f6a6
|
feat: 运维标杆化打磨 P0/P1/P2 —— 审计日志、真实指标、备份恢复、菜单收敛
审计发现 5 处硬伤(/metrics 空壳、零备份、prod 双副本数据分裂、零审计、资源偏紧),
本轮全部落地。
=== P0-1 真实 /metrics(原来是空壳)===
原实现只返回一行注释 `# LokiStack Metrics`,注释却写着"可被 Prometheus 抓取",
平台挂了/DB 写不进去/告警引擎卡死都只能靠用户反馈。
现接入 prometheus/client_golang,四层指标:
HTTP : lokistack_http_requests_total / _duration_seconds / _in_flight_requests
存储 : lokistack_db_size_bytes / _db_save_duration_seconds / _db_save_total / _db_records
业务 : lokistack_deployments{status,mode} / lokistack_collectors{status,type,run_mode}
后台任务: lokistack_alert_evaluations_total / _duration_seconds / lokistack_loki_queries_total
+ go_* / process_*(GoCollector、ProcessCollector)
关键点:path 标签用 c.FullPath() 路由模板,不用原始 URL —— 否则 117 个 Deployment
会把 Prometheus 基数炸掉。指标定义下沉 pkg/metrics 叶子包,避开
services ↔ repository 循环依赖。
=== P0-2 备份与恢复(原来 0 处代码)===
平台全部状态在 /app/data/opsxloki.json(51KB)+ kubeconfigs/ + known_hosts/,
DB 丢了 = 失去"这些资源是谁建的"的映射,从此无法管理/卸载/升级在跑的 Loki。
CLI : lokistack backup --output <path> / restore <pkg> / self-check / version
API : GET /system/backup(下载)/ POST /system/restore(校验暂存)/
POST /system/restore/apply(确认应用)/ GET /system/backups
K8s : kubernetes/pdb-backup.yaml —— 每日 03:30 CronJob,保留最近 7 份
安全设计:
- manifest.json 记录 ENCRYPTION_KEY 的 sha256 前 16 位**指纹**,用于恢复时
校验"这把钥匙能不能解开这份备份",不泄露密钥本身
- 恢复分两步(校验暂存 → 确认应用),应用前自动把当前 DB 备份为
opsxloki.json.before-restore.<时间戳>
- 包内不含明文密钥(kubeconfig 本就是加密态)
=== P0-3 prod compose 双副本 → 单副本 ===
lokistack-1 / lokistack-2 各挂独立数据卷 + nginx 轮询 = 数据分裂:
页面刷新两次可能看到不同结果,写操作只落一个副本。不是高可用。
已改单实例(nginx 仅做反代),文件顶部与 nginx.conf 都写明原因。
=== P0-4 操作审计日志(借鉴 /usr/local/src/aiops 的字段口径)===
aiops 是每个 controller 各写 recordXxxAudit();本平台有 60+ 写接口,
逐个埋点必然漏,漏掉的那个恰好最危险(删集群、改存储凭证)。
改为 **gin 中间件统一记录** + handler 通过 SetAudit 补充业务语义:
models/audit.go : 字段对齐 aiops,另加 Method/Path/Before/After
services/audit.go : 写入 + 90 天/2 万条自动裁剪 + 脱敏 + diff 生成
middleware/audit.go : 自动识别 module/action/resource,跳过只读接口
controllers/audit.go: 列表/详情/统计(仅 admin)
敏感字段(password/secretKey/privateKey/token)写入前统一替换为 ***。
=== P1 稳定性 ===
- 平台资源 1C/1Gi → 4C/4Gi 限(500m/512Mi 请求):JSON DB 全量读写 +
每分钟告警评估 + 批量发布并发,1Gi 会 OOM
- terminationGracePeriodSeconds: 30 —— 收到 SIGTERM 要等 DB 完整落盘,
否则 kubelet SIGKILL 会把 JSON 写一半(全库丢失)
- liveness timeoutSeconds 10s(默认 1s 写盘时会被误杀)
- PDB minAvailable: 0 —— 让 kubectl drain 正确等待重建,不卡维护
- DB schemaVersion + migrateSchema() 版本递进迁移(原先加字段全靠零值兜底)
- scripts/release.sh —— 不可变 tag <版本>-<日期>-<commit> + IfNotPresent,
根治 latest 并发构建互相覆盖导致的静默回滚(本项目已实际踩过)
- PVC 保持 30Gi(按长期增长预留,云盘单价低,不做在线扩容)
=== P2 易用性 ===
- 一键自检 GET /system/self-check + 仪表盘自检卡:真实探活 Loki /ready、
集群凭据解密、磁盘空间、主机状态、近 24h 失败操作
- 审计日志页 /audit:统计卡 + 用户名/模块/结果筛选 + 变更前后 diff 弹窗
- 菜单收敛 13 → 7(页内 tab 切换,URL 全部不变):
资源接入 = 集群 + 主机 + 存储
日志服务 = Loki 实例 + 采集器
日志 = 查询 + 分析
系统管理 = 用户 + 审计 + 设置
新增 GroupTabs 组件 + meta.group/groupKey,侧栏按分组去重并正确高亮
测试:go vet 通过、go test 全绿、tsc 通过
|
2026-10-28 21:27:01 +00:00 |
|
opsx
|
f6af12a731
|
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
|
2026-10-28 20:27:48 +00:00 |
|
opsx
|
c983d0e3c1
|
feat(collectors): 可编辑采集配置(B 档结构化编辑器)+ 修 host 标签 / syslog 时间戳
=== 一、两个线上 bug(172.19.16.8 真机取证)===
1) host 标签是字面量 ${HOSTNAME}
Promtail 不做环境变量展开(除非 -config.expand-env + systemd Environment),
实测 21,825 条日志的 host 全是 "${HOSTNAME}",无法按主机过滤。
→ 安装时 `hostname -s` 探测真实主机名并渲染进配置,结果存 Collector.ResolvedHostname,
让「预览配置」与线上一致。
2) 系统日志时间戳解析失效
TencentOS 4 的 /var/log/messages 是 ISO8601(2026-07-17T15:38:24.923784+08:00),
旧正则按传统 syslog(Oct 28 18:11:06)写死 → timestamp 阶段匹配不上,用的是采集时间,
回填历史日志时间全错。
→ 安装时探测目标机格式:iso8601 → RFC3339Nano;rfc3164 → Jan _2 15:04:05 + Local;
系统日志路径同时探测(Ubuntu 没有 messages,写死会一条都采不到)。
实测:写一条 10 分钟前的 ISO8601 探针行,Loki 里 loki_ts 与该行自身时间偏差 **0 秒**;
另写一条 2 小时前的被 Loki 以 entry too far behind 拒绝 —— 恰好反证时间戳是按事件时间
解析后推送的(旧实现会用采集时间,反而被接收)。
=== 二、B 档:结构化多 Job 编辑器 ===
后端
- models/scrape_job.go:ScrapeJob + ValidateScrapeJobs
校验 job 名格式、绝对路径、**敏感路径拦截**(/etc/shadow、/etc/sudoers、/root/.ssh、
id_rsa、*.pem…)、正则可编译、job/host/__path__ 保留标签名、数量上限。
- services/promtail_host_config.go:多 job 渲染;多路径展开成多个 static_configs;
pipeline 顺序硬约束 cri → multiline → json → timestamp → drop → limit;
encoding 写在 scrape_config 级(放进 static_configs 里 promtail 直接起不来)。
- services/host_profile.go:探测 distro / init / hostname / 时区 / 架构 / journald
与真实存在的系统日志路径。
- CollectorService:renderConfig 实时渲染、PreviewConfig(只渲染不落库)、
Update 支持 scrapeJobs 并校验、安装结果(hostname/syslog 格式/配置快照)落库。
- 新增 POST /api/v1/collectors/:id/preview → {config, warnings, hostname, syslogFormat}。
- installHost:配置先写 .new + `promtail -check-syntax` 通过才替换,重启后轮询
systemctl is-active,失败把 journalctl 尾部带回前端。
前端
- 采集项结构化编辑器:名称/路径/标签常驻,多行合并 / JSON / 自定义时间戳 / 命中丢弃 /
限流折叠在「高级」里;「预览 YAML」弹窗在保存前看到将要下发的配置。
- 本地即时校验 + 旧数据(只有 logPath)自动迁移成 app + system 两个采集项。
=== 三、验证(host-verify-promtail @172.19.16.8)===
- 预览:host=pre-cex-ops-jenkins(无 ${HOSTNAME}),2 job / 3 条系统日志路径 /
ISO8601 正则 / 多行 + drop + 限流 全部按填写渲染。
- 部署:promtail active、NRestarts=0、-check-syntax 通过,主机侧配置与预览逐行一致。
- Loki:新数据 host 全为 pre-cex-ops-jenkins;job=system 415 条/5min、job=app 40 条/5min。
- 负向 7/7 返回指名到字段的 400:/etc/shadow、/root/.ssh/id_rsa、相对路径、坏正则、
job 名带空格、保留标签名、空路径。
镜像:lghost/lokistack:latest + v0.1 = sha256:26172f51187d
|
2026-10-28 18:41:39 +00:00 |
|
opsx
|
55d6a74eb3
|
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
|
2026-10-28 18:11:41 +00:00 |
|
opsx
|
bad2a1e624
|
fix(hosts,collectors,usage): 主机验证结果失真 + 主机模式系统日志解析 + metrics 单位
=== 一、主机「测试连接」永远显示旧错误(真实 bug)===
HostService.Validate 的失败分支也写 status=active,且从不写 statusMessage:
if info, err := client.GetHostInfo(ctx); err == nil { 更新 os/arch }
else { s.Update(id, {status:"active"}) } // ← 失败也标记成功
结果:连不上也显示正常,页面永远停在几分钟前的旧错误文案,
用户改了 IP 再点测试,还以为"改了还是不行"。
改为区分成功/失败并写清原因:
成功 → status=active + os/arch + statusMessage="连接成功"
失败 → status=error + statusMessage=FormatSSHError(err)
=== 二、回环地址在平台里必然连不上 ===
平台跑在容器/Pod 内,127.0.0.1 指向平台自身。
- 新建主机时拦截:hostIP 为 127.0.0.1/localhost/::1 直接报错并解释原因
- 前端表单同步校验 + 文案
- SSH 错误映射补充:连接被拒绝时提示可能是地址填了回环
=== 三、主机模式采集系统日志(两处会导致采集失败)===
1. pipeline 一律套 cri 阶段:cri 只认容器运行时格式,
套在 /var/log/messages 这类 syslog 上会取不到时间戳、整行被丢。
→ appPipelineStages() 按路径判断:容器日志用 cri,普通文本日志只做限流。
2. promtailExternalLabels 写死 8 空格缩进:主机模板 external_labels 只有 4 空格,
一旦给采集器配置了标签就生成非法 YAML,Promtail 装不上。
→ 增加 indent 参数,主机 6 空格 / DaemonSet 8 空格。
3. 新增 system 采集任务:
/var/log/{messages,secure,cron,maillog,syslog,dmesg}
正则解析 syslog 时间戳(Jan _2 15:04:05,Asia/Shanghai)+ 限流
测试:TestHostPromtailConfigValid / CRIPathKeepsCRIStage / PlainFileHasNoCRIStage
TestPromtailManifestParses 全部通过
=== 四、资源用量 CPU 解析(nanocores)===
metrics-server 返回的 CPU 常是纳核(如 "194533645n"=194.5m)。
原解析只处理 "m",导致 194 亿核、页面出现"CPU 已达 limit 的 4863341125%"。
→ parseQuantityMilli 支持 n/u/m/整核
实测:CPU 当前=178m,峰值=3300m,建议"峰值占 limit 82%,可考虑提前扩容"
=== 五、其他 ===
- 添加集群默认改用 Token 认证(最小权限、可随时吊销)
- RBAC 片段补 metrics.k8s.io(资源面板需要,独立 API 组常被漏授)
- 文案去掉 Grafana 需求描述(平台自身不部署 Grafana)
镜像:lghost/lokistack:latest + v0.1 = sha256:7bddd0f4d1f1
|
2026-10-28 17:58:30 +00:00 |
|
opsx
|
a32f93f533
|
feat(deployments,analysis): 保留弹框重设计 + 分析页命名空间筛选 + 过期 chunk 真正删除
=== 一、保留策略弹框重设计(RetentionDialog)===
旧版问题:
1. 主输入是 YAML textarea,用户必须会写 `limits_config.retention_period: 30d`
2. 三个按钮:取消 / 仅保存 / 保存并应用 —— 「仅保存」只改部署记录不重载 Loki,
今天已经造成一次真实故障:DB 记录 7 天、运行时 ConfigMap 是 24h,配置漂移
3. 「查看完整配置」是独立按钮 + 独立状态,多一次点击
4. 硬编码提示 `retention_delete_delay: 2h`,而实际已改成 10m,属于误导
新版:
- 主输入改为**保留天数数字框**(1–3650),YAML 由前端拼,用户不用写
- 快捷预设 1/7/14/30/90,实时显示「= 720 小时」
- >7 天给出存储成本提示
- 完整运行 config 改成 `<details>` 折叠,展开即自动加载(去掉独立按钮)
- **按钮只剩 2 个:取消 / 保存并应用**
- 删掉硬编码的 compactor 提示块
=== 二、日志分析页加命名空间筛选 ===
根因:analysis 页写死 namespace: '',等价 `{namespace=~".+"}` —— 一次分析扫全集群
(实测约 370 GB/天),5 条 count_over_time 跑 30s 都出不来,页面永远转圈。
- 新增命名空间下拉(从 Loki label values 拉取,localStorage 记忆)
- 默认取业务命名空间 astralx(其次列表首个),不再默认全集群
- 「全部命名空间(较慢)」降级为显式选项
- 命名空间未就绪时先显示 loading,避免先发一次全集群查询
- 5 条查询改**分批(每批 2 条)**执行 + 429 自动重试一次
=== 三、后端并发/超时调整 ===
- 重查询并发上限 2 → 4,且拿不到令牌时**排队最多 10s**再拒绝
(原来立即 429,导致分析页 5 条并发里有 3 条直接失败)
- maxLogQueryLimit 1000 → 5000,与前端下拉完全对齐(原来前端选 5000 会被静默截断)
=== 四、过期 chunk 真正删除(关键修复)===
实测发现:索引表已从 COS 删除,但 chunk 对象仍残留 21,354 个 / 24.8 GB,
最老的 45 小时前 —— 因为 Loki 默认 `retention_delete_delay` = **2h**,
标记后要等 2 小时才真正删 chunk。
- 配置显式设置 `compactor.retention_delete_delay: 10m`
- 已用 `loki -verify-config` 校验(config is valid)
- 删除前后对比:
对象数 46,536 → 23,934;占用 53.61 GB → 27.25 GB
超期对象 21,354 个 / 24.8 GB → **0 个**
桶内最老对象 45.2h → 24.4h(= 保留期 1 天)
- deployment_config_test 同步调整:retention_delete_delay 从"禁止出现"移入"必须存在"
镜像:lghost/lokistack:latest + v0.1 = sha256:b4d98e016bca
|
2026-10-28 17:05:57 +00:00 |
|
opsx
|
53c7d64b35
|
feat(logs): 结果表虚拟滚动(@tanstack/react-virtual),>300 条时只渲染可视区
=== 一、为什么 ===
结果表原来是 entries.map 全量渲染真实 DOM。实测每条日志约 671B:
1000 条页面 → 1000 个 DOM 节点,且大量消息带 whitespace-pre-wrap
自动换行(Java 堆栈尤其长),浏览器需要排版全部行 → 滚动卡顿。
这是当前"每页条数"的真正瓶颈,比传 655KB 响应更影响体验。
=== 二、实现要点 ===
1. 新增 components/VirtualLogList.tsx
- useVirtualizer(**独立滚动容器**,非 window 滚动)
原因:window 虚拟滚动要把列表相对文档顶部偏移 scrollMargin 算准,
上方工具条高度一变(快捷模板/域过滤条出现隐藏)就错位;
独立容器内虚拟项位置只跟容器滚动量有关,不依赖页面布局。
- 行高不一(消息会换行)→ 用 measureElement 实测每行高度
- 容器 max-h-70vh,overscan 10
2. 抽公共行组件 LogRow,普通渲染与虚拟滚动**共用同一份 DOM 结构**,
levelBadgeClass 逐字沿用原实现(含 info/warn/debug 配色),保证外观不变
3. 安全阀:VIRTUAL_THRESHOLD = 300
- 默认每页 200 条仍走原来的普通渲染(行为已验证,零风险)
- 用户主动把每页调到 500/1000 才启用虚拟滚动
4. 依赖:@tanstack/react-virtual ^3.14.23(package.json + lock 已更新,
Docker 内 npm ci 可复现)
=== 三、验证 ===
- 本地 npx tsc -b --force 全量类型检查通过(rc=0)
- 产物 bundle 含 getVirtualItems / measureElement / getTotalSize
- 页面与主 JS 资源均 HTTP 200
- 平台健康 ok;hub latest 与 Pod digest 一致
镜像:lghost/lokistack:latest + v0.1 = sha256:25002cebc77c
|
2026-10-28 16:28:53 +00:00 |
|
opsx
|
6364a70215
|
tune(logs): 每页条数默认 1000→200,上下文默认 5 条(依据实测数据)
=== 一、每页条数:1000 太多,实测依据 ===
1. 每次查询响应体 671 B/条 → 1000 条 = **655 KB**
2. 前端结果表**没有虚拟滚动**(无 react-window / tanstack virtual),
1000 行会全部渲染成 DOM 节点 → 滚动卡顿
3. 后端 maxLogQueryLimit 本来就是 1000,我上一版 UI 给了 2000/5000 选项
是"假选项"(会被静默收敛成 1000),本次去掉
4. 密度参考:cex-ipublic-gateway 每天 3.28 亿条 ≈ **3800 条/秒**,
1000 条只覆盖 0.26 秒 —— 靠翻页浏览无意义,应靠时间范围/LogQL 收窄
调整:默认 1000 → **200**(后端自身默认值,约 131KB);选项 [100/200/500/1000]
提示文案改为"日志量大时先收窄时间范围或加过滤,比调大每页条数更有效"
=== 二、上下文档数:1 条不够,默认用 5 ===
实测一个 SpringBoot 异常被拆成 **4 条独立日志**:
16:15:35.259 [reactor-http-epoll-3] ERROR ...AbstractErrorWebExceptionHandler
org.springframework.web.reactive.resource.NoResourceFoundException: 404 NOT_FOUND
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
*__checkpoint ⇢ HTTP HEAD "/" [ExceptionHandlingWebHandler]
→ 前后各 1 条(共 2 条)连一个完整异常都拼不出来
→ 前后各 5 条(共 10 条)能看到"异常 + 前后业务日志",是排障的最小可用集
→ 完整 Java 堆栈(20+ 帧)需要 20-50 条
调整:默认 2 → **5**;选项增加 1 → [1/2/5/10/20/50]
镜像:lghost/lokistack:latest + v0.1 = sha256:2bcc86b8e30d
|
2026-10-28 16:18:48 +00:00 |
|
opsx
|
3191af8b6a
|
feat(logs): 上下文前后条数可配置(默认 2,原来写死 5)+ 每页条数改预设选项
=== 一、问题 ===
查看日志上下文时,前后各取几条是**写死**在控制器里的:
span := 5 * time.Minute
QueryLokiLogs(..., ts-span, ts-1, 5, "backward") // 5 条
QueryLokiLogs(..., ts+1, ts+span, 5, "forward") // 5 条
三处写死:条数 5、时间窗 5 分钟、且用户无法调整。
写死的三个实际弊端:
1. 排障时想看更多(比如前后 20 条找调用链)做不到;
2. 只想看紧邻 2 条时,仍然要扫 5 分钟窗口、返回 5 条,浪费;
3. 时间窗与条数不匹配——某服务日志稀疏时,5 分钟窗口里可能一条都没有,
于是"前后各 5 条"实际返回 0 条。
=== 二、改动 ===
1. backend/dto: LogContextRequest 新增 before/after(可选)
2. backend/controllers: normalizeContextLines()
- 不传或 ≤0 → 默认 2 条
- 上限 50 条(防止一次弹窗拉几百条)
- 时间窗改为**按条数自适应**:每条按 30s 估算,最少 1 分钟、最多 2 小时
(不再固定 5 分钟,解决"稀疏服务取不到上下文"的问题)
3. frontend
- 上下文弹窗标题栏加「前后各 [2/5/10/20/50]」选择器,切换后原地重新拉取
- 选择结果写入 localStorage,下次沿用
- 每页条数:数字输入框 → 预设下拉 [100/500/1000/2000/5000]
(原来只能手输且硬顶 1000,现在上限提到 Loki 默认的 5000)
- i18n:去掉"前后各最多 5 条 · 前后 5 分钟"的写死文案
=== 三、实测(平台 API)===
before=2 after=2 → 2 / 2 条
before=5 after=5 → 5 / 5 条
before=10 after=10 → 10 / 10 条
before=50 after=50 → 50 / 50 条
before=999 after=999 → 50 / 50 条(超上限自动收敛)
不传 → 2 / 2 条(默认值生效)
镜像:lghost/lokistack:latest + v0.1 = sha256:d19239fa65f3
|
2026-10-28 16:14:05 +00:00 |
|
opsx
|
5d530036e6
|
feat(logs): 分页显示总页数(新增 /logs/stats,走 index/stats 只读索引)
用户反馈:底部只有「上一页 第 1 页 下一页」,看不到末尾/总页数。
原因:日志结果是 Loki 游标分页(cursor = 纳秒时间戳),本身不返回总数,
前端只能知道"有没有下一页",算不出总页数。
解法:新增 POST /api/v1/logs/stats
→ 调 Loki /loki/api/v1/index/stats(只读 TSDB 索引里的 chunk 元数据)
→ 返回 {streams, chunks, bytes, entries}
→ 前端用 entries / limit 得到总页数
实测成本极低:1h 51ms、24h 30ms(3.28 亿条 / 44.2 GB)。
改动:
1. backend: services.LokiStatsFor + controllers.LogsController.Stats + 路由 /logs/stats
2. frontend: logsApi.stats;查询成功后拉取总数(复用同一 AbortController,
失败静默不影响日志展示);计算 totalPages 传给结果面板
3. LogResultsPanel: 有总数时显示「第 N / M 页」,否则维持「第 N 页」
4. i18n: 新增 results.pageOfTotal(中英)
镜像:lghost/lokistack:latest + v0.1 = sha256:41d6576ec232
|
2026-10-28 16:02:35 +00:00 |
|
opsx
|
de376a1284
|
perf(logs): 日志量统计改用 index/volume_range(快 ~1300 倍)+ 查询护栏 + 真实错误提示
=== 一、现象 ===
- /logs 页「正在统计整个时间范围的日志量」24 小时查询转圈 5 分钟不出结果
- /analysis 页默认 1 小时也一直「加载中...」
- 期间 Loki 被 OOMKilled(2Gi) → Pod not Ready → Service 无端点 → 全站查询 502
=== 二、根因(实测数据) ===
同一 query {namespace="astralx", app="cex-ipublic-gateway"}:
24h count_over_time[15m] step=15m → 53.7s 后连接断开(算不完)
24h count_over_time[1h] step=1h → 60.1s 失败
24h index/stats(只读索引) → 1.3s → bytes 44.9GB / entries 3.33亿 / chunks 2474
24h index/volume_range step=1h → 111ms → 25 个点
1h index/volume_range step=5m → 115ms → 13 个点
原因:count_over_time 必须**下载并解码所有 chunk**;volume_range 只读 TSDB 索引里的
chunk 元数据(字节数),根本不碰日志内容。该 app 一天就有 3.33 亿条 / 44.9 GB。
=== 三、改造 ===
1. LokiHistogram 改用官方 /loki/api/v1/index/volume_range(带 step)
- 返回单位是「字节」,bucket 带 unit=bytes,前端格式化为 KB/MB/GB
- 老版本 Loki 没有该端点时自动回退 count_over_time(unit=entries)
2. 查询护栏(防止单条查询拖垮单实例 Loki)
- 统计范围上限 7 天,超出直接 400「统计范围过大(27.0 天),最多支持 7 天」
- 重查询单独 30s 超时(原来 120s)
- 全局并发重查询上限 2,超出立刻 429「请稍后重试」(不排队)
- /logs/metrics(analysis 页)共用同一套护栏
3. 前端
- 发起新查询前 abort 上一次请求(原来只用版本号丢弃结果,旧请求仍在后端跑)
- histogram/query 支持 signal;超时 40s/60s(原 125s)
- 错误不再统一显示「日志量统计失败」,按 429/400/502/超时给不同文案
- 时间线按 unit 格式化(字节 → GB),i18n 去掉「条」
4. Loki 侧官方优化(此前 limits_config 只有 retention_period)
- query_range: align_queries_with_step + cache_results + embedded_cache 100MB
- limits_config: max_query_parallelism 8(默认 32,单实例过高)
- 已用 `loki -verify-config` 校验通过(config is valid)
=== 四、实测(平台 API) ===
1 分钟 0.02s 2 桶
1 小时 0.05s 61 桶 3.8 GB
6 小时 0.04s 73 桶 12.3 GB
24 小时 0.04s 97 桶 41.8 GB ← 改造前 53.7s 失败
7 天 0.05s 37 桶 58.2 GB
27 天 0.01s 400「最多支持 7 天」
镜像:lghost/lokistack:latest + v0.1 = sha256:9fa1c4597533
|
2026-10-28 15:54:55 +00:00 |
|
opsx
|
9acc15f4d2
|
test(loki): 修正 delete_request_store 断言——两个测试互相矛盾导致 go test 必失败
现象:`go test ./services/` 在 HEAD(3a8eb52)上稳定失败
--- FAIL: TestGeneratedLokiConfigKeepsRequiredSettingsWithoutDefaults
deployment_config_test.go:68: required setting "delete_request_store: s3" missing
根因不是代码 bug,而是**两个测试对同一件事的期望相反**:
backend/services/deployment_config_test.go:66 断言 delete_request_store: s3
backend/services/loki_object_store_test.go:44 断言 delete_request_store: filesystem
而 loki_config.go 对 s3/oss/minio/cos/obs 是**刻意**用 filesystem 的
(commit 9f7c522 引入,注释:「删除请求存本地盘,避免各云 S3 兼容实现差异
导致 compactor 启动失败」)。
即:代码是有意的,loki_object_store_test 也是对的,只有 deployment_config_test
是历史遗留的过时断言。本次把它改成 filesystem,并加注释说明为什么不能再改回 s3,
避免以后有人"顺手修回去"。
运行时行为完全没有变化(只改测试文件),但修复后 `go test ./...` 才能全绿,
否则每次发版预检都会被这个假失败挡住。
验证:go test ./... 全部通过(services/models/repositories/pkg 均 ok)。
|
2026-10-28 15:00:30 +00:00 |
|
opsx
|
3a8eb521e2
|
chore(i18n,ui): 全站冗长文案精简 + shadcn 样板汉化 + 一致性修复
- 文案:约 25 组长文案中英同步精简(数据源引导/RBAC 注解/采集提示/删除确认等),三处重复 Ingress 教学收敛
- 汉化 shadcn 样板:分页 Previous/Next/aria、弹窗与抽屉 Close 接入 i18n(common.ui.*)
- 一致性:查询授权面板 p-4、仪表盘卡片 p-6、分析卡头 pb-3、部署总数图标配色、
每日统计错误可重试、策略卡/最近部署行 truncate、状态徽章字重、三处原生 checkbox 换 shadcn、Label 浏览器圆角
- 词典:删除无引用的 raw.*/views.* 死键;zh 复数键风格统一(importCount/hits)
- 快捷查询提示、Ingress 统计口径与表内说明按反馈删减
|
2026-10-28 22:44:06 +08:00 |
|
opsx
|
2da0a2bece
|
feat(analysis): Ingress 访问分析卡 — 自动加载、无 ingress 自动折叠
- 新增常驻「Ingress 访问分析」卡:自动探测 ingress-nginx/ingress 命名空间并聚合访问日志
(PV/UV、4xx/5xx、慢请求、接口排行、异常/慢请求 Top10、请求明细),随数据源与时间范围刷新
- 从历史恢复 ingress 洞察表与聚合工具到 analysis 域(ingress-access-log 解析已在 lib)
- 折叠交互:未检测到 ingress-nginx 时自动收起并显示徽章;手动切换记入 localStorage 优先生效
|
2026-10-28 22:43:57 +08:00 |
|
opsx
|
16306a9532
|
feat(logs): metrics 指标代理端点(admin) + 快捷查询选中 App/Pod 即插入并执行
- POST /logs/metrics:LogQL 指标查询代理,[$step] 占位符按跨度自适应步长,返回多序列(httptest 假 Loki 单测)
- 快捷查询:选中 App/Pod 即写入输入框并立即查询,面板保持打开支持 Pod 级联;命名空间仅级联筛选不插入,切换时清理失效标签
- 删除冗余的「查询该范围日志」「指定副本日志」按钮;Ingress 按钮仅在选择 ingress 命名空间/app 时显示
- /logs 支持 ?q= 预填自动执行(表格「查看日志」跳转入口)
- 清理无引用死组件 LogResults/LogHistogram
|
2026-10-28 22:43:57 +08:00 |
|
opsx
|
5ce90fe2e5
|
fix(analysis): 去掉原始日志底栏与命名空间筛选
按产品/设计锁死:删底部 goQuery 虚线卡(表内「查看日志」保留);筛选栏去掉命名空间下拉与 labelValues 加载,指标固定全命名空间聚合。
|
2026-10-28 22:06:38 +08:00 |
|
opsx
|
305d7c5904
|
fix(ui): SearchableValueSelect 支持 commitOnType,恢复域名边打边筛
设计补刀:域名筛换成可搜 Select 后丢失「键入即过滤当前页」。新增 commitOnType——键入即 onChange、列表当建议、「全部」清空;长选项 truncate+title。域名筛启用;采集器 sidecar 命名空间/工作负载长列表一并改为可搜(同刀)。
|
2026-10-28 22:05:33 +08:00 |
|
opsx
|
8c9f5a95f8
|
fix(logs): 域名筛选与残余原生 select 统一为可搜索/Radix Select
通病:/logs 域名筛选仍用 Input+datalist(与已修的快捷模板同类);策略组数据源、数据源弹窗集群/鉴权仍为原生 select。改为 SearchableValueSelect / ui/select,并顺带理顺策略组列表空标签与用户勾选交互。采集器编辑里 app 预设 datalist 仍保留(自由输入场景)。
|
2026-10-28 22:04:32 +08:00 |
|
opsx
|
c759e111ec
|
feat(k8s): install.sh 三级部署预检——kubectl 缺失/连不上集群时给出可执行的具体修法
用户反馈:部署时报错只说「未找到 kubectl,请先安装」「kubectl 无法连接集群,
请检查 kubeconfig」,对新人等于没说 —— 不知道是没装工具、网络不通、凭据过期
还是 RBAC 没权限,也不知道下一步查什么。
原实现只有两行:
command -v kubectl || die "未找到 kubectl,请先安装"
kubectl version --request-timeout=5s || die "kubectl 无法连接集群,请检查 kubeconfig"
改为三级预检 + 一级软检查:
【0.1 kubectl 是否存在】
按 CentOS/TencentOS、Ubuntu/Debian、macOS 分别给安装命令;
额外给「机器没有外网」的替代路径(控制台下载 / 内网跳板机 / 只 scp 二进制)。
【0.2 kubeconfig 文件是否存在】← 新增,堵一个误判
KUBECONFIG 指向不存在的文件时,kubectl 会忽略它并退回默认规则,
最终报出 "Authentication required" / "connection refused" 这类误导性错误,
让人白查凭据和网络。现在单独判一次,直接给出查找路径 + 各云控制台下载入口。
支持 KUBECONFIG 用 : 分隔多路径,任一存在即通过。
【0.3 能否连上 API Server】按错误类型分类,各给专属修法
· context 不存在 → get-contexts / use-context / 重新下载
· 401 认证失败 → 云厂商 token 有效期几小时,重新下载;自建查客户端证书
· 403 RBAC 拒绝 → auth whoami / auth can-i --list,列出部署所需的具体资源
· x509 证书问题 → 确认证书过期;可信内网可临时 insecure-skip-tls-verify
· 网络不通 → curl -k <API>/version、VPN/专线、安全组放行 6443、换跳板机
· 其他 → 原样打印 kubectl 错误 + 通用排查清单
【0.4 部署权限软检查】auth can-i 非 yes 时只告警不阻断
(首次部署时目标命名空间可能还不存在,硬拦会误伤正常流程)
失败时统一打印环境信息(kubeconfig 路径 / context / API Server / 当前用户),
一眼看出连的是哪个集群。连接超时可用 KUBECTL_TIMEOUT=15s 放宽(默认 8s)。
README 同步新增「前置条件」章节:预检项对照表 + 错误分类表 + 失败输出示例。
=== 验证 ===
从真实 install.sh 抽出预检段,用假 kubectl 精确模拟 8 种场景(两个脚本各跑一遍):
A 没有 kubectl → 退出 1,打印各系统安装命令 ✅
B API Server 超时 → 退出 1,提示 VPN/专线/安全组 ✅
C 没有 context → 退出 1,提示 get-contexts/use-context ✅
D 401 凭据过期 → 退出 1,提示重新下载 kubeconfig ✅
E 403 权限不足 → 退出 1,列出所需资源权限 ✅
F x509 证书过期 → 退出 1,提示证书/insecure 排障 ✅
G 正常环境(真实集群) → 通过预检,退出 0 ✅
H kubeconfig 文件不存在 → 退出 1,提示下载位置(不误报成 401)✅
结果:阻断 7/7、正常通过 1/1;bash -n 语法检查通过
⚠️ 踩坑记录:kubeconfig 多路径判断最初写成
while IFS= read -r _p; do ...; done < <(printf "%s" "$PATHS" | tr ":" "\n")
printf 输出没有结尾换行 → read 带着数据返回非 0(EOF)→ 循环体一次都不执行,
导致所有场景都误报「找不到 kubeconfig」。改用 IFS=":" + for 拆分。
|
2026-10-28 07:25:15 +00:00 |
|
opsx
|
7b600f7eee
|
feat(log): Gin 结构化 JSON 访问日志 + 生产默认 release 模式(去掉启动路由 dump)
=== 一、结构化 JSON 访问日志(middleware/accesslog.go)===
gin.Default() → gin.New() + AccessLog() + gin.Recovery()
- 一行一个 JSON:ts/level/type/method/path/status/latency_ms/bytes/ip/ua
- 自动分级:5xx=error / 4xx=warn / 其余=info
- jwt 中间件写入的 userId/username 一并输出,便于审计
- 健康检查与 /assets 静音(探针每 10s 一次会淹没真实请求)
- Recovery 显式加回(gin.Default 里包含它)
⚠️ Go map 序列化按 key 字母序,ts 会排到最后 → LogJSON 手工拼装,
把 ts/level/type/msg 固定在最前,其余排序输出。
=== 二、生产默认 release 模式 ===
代码里此前**完全没有 gin.SetMode**,一直跑在 debug:
- 每次启动 dump 100+ 行路由表([GIN-debug] GET /api/v1/...)
- 每请求有额外开销,且把完整路由暴露在日志里
现在:GIN_MODE 未显式设置且 DEBUG!=true 时自动 SetMode(ReleaseMode),
需要调试仍可用 DEBUG=true 或 GIN_MODE=debug 覆盖。
=== 三、实测 ===
GIN-debug 路由 dump 行数: 0 ✅(修复前 100+)
JSON 访问日志:
{"ts":"2026-10-27T14:49:28.53Z","level":"info","type":"access","bytes":828,
"ip":"172.19.16.92","latency_ms":173.5,"method":"POST",
"path":"/api/v1/auth/login","status":200,"ua":"curl/8.4.0"} ✅
带用户信息: ...,"user_id":1,"username":"admin" ✅
health 日志行数: 0 ✅
总日志 15 行 / JSON 7 行 / 非 JSON 8 行(全部是启动日志)
health ok、Promtail 12/12、lokistack 1/1 ✅
镜像:lghost/lokistack:latest + v0.1 = sha256:8b25b0a29efa
|
2026-10-27 14:49:45 +00:00 |
|
opsx
|
02705382f1
|
fix(collector): Sanitized() 漏掉采集范围三字段,导致编辑框读不到配置
背景:基于 f3733f0(品牌统一 + i18n 重构)构建部署后发现,采集器 API
返回里完全没有了 targetNamespaces / appLabelSource / appInclude 三个键。
根因:重构引入了 models.SanitizedJSON() + Collector.Sanitized(),
后者是**白名单式** map 输出,写的时候这三个字段还不存在,重构时也没补进去。
controller 全部走 SanitizedJSON(collector),所以这三个字段被静默丢弃。
影响(比"少显示几个字段"严重):
编辑采集策略弹框读 collector.targetNamespaces 为 undefined
→ 命名空间面板显示"全集群采集"(实际配的是 astralx+log)
→ 用户只要点一次保存,就会把 targetNamespaces 写成 []
→ 下次「部署」生成的 Promtail 白名单消失,开始全集群采集(日志量暴涨)
appLabelSource / appInclude 同理。
修复:Collector.Sanitized() 补三个字段。
验证:
GET /api/v1/collectors/1
targetNamespaces = ['astralx', 'log'] ← 修复前该键不存在
appLabelSource = ''
appInclude = null
三个键均存在 ✅
Promtail DaemonSet 12/12 未受影响 ✅
Promtail ConfigMap 白名单(astralx, log)、app 四级回退、pod_phase drop 全部保留 ✅
镜像:lghost/lokistack:latest + v0.1 = sha256:648316ae13c5
|
2026-10-27 10:30:21 +00:00 |
|
opsx
|
f3733f0bfc
|
feat: 品牌统一 LokiStack + 全站中英 i18n + 用户批量删除 + 主题定时切换与浅色侧栏
- 品牌: Go 模块/数据文件/二进制/构建部署资产全部 opsx→lokistack;前端 localStorage 命名空间迁移(读旧写新,含按用户 id 前缀迁移);修复 nginx upstream 与 Makefile 引用已改名服务的断裂
- i18n: react-i18next 落地,common + 11 个页面域命名空间全站中英(菜单/顶栏/登录/列表页/表单/确认弹窗/状态徽章);设置页语言即时切换;修复词典自注册导致的循环导入白屏
- 用户管理: 批量删除(POST /users/batch-delete,不删自己/最后管理员,部分失败不阻断)+ 行复选/全选/批量工具条
- 主题: 浅色侧栏(中性灰激活态)、四态模式(浅色/深色/跟随系统/定时切换,按时区判定)、顶栏太阳月亮二态、设置页紧凑分段选择器
- UI: 登录记住用户名、CardActionButton 统一五页卡片操作钮、shadcn Badge 替换手写徽章、语义色修正
- 清理: 移除主机 Web 终端(前后端)、k8s-helm/Helm 遗留代码与字段、README 重写精简
- 修复: 侧栏与日志告警 order 重复、路由标题 i18n key 未翻译、词典双逗号等
|
2026-10-27 17:11:38 +08:00 |
|
opsx
|
02d1bd87a7
|
fix(promtail): 修 Sprintf 缺参导致清单非法 + Ingress 模板固定命名空间 + 资源名去 opsxloki
=== 一、致命 bug:Promtail 清单第 10 个 %s 缺参数(导致日志采集中断)===
go vet 报:"format %s reads arg #10, but call has 9 args"。
模板里 DaemonSet 的 `namespace: %s`(第 9 个)和 `image: %s`(第 10 个)
是两个独立占位符,但 Sprintf 只传了 9 个参数,第 10 个变成 %!s(MISSING),
整个多文档 YAML 解析失败:
"解析 Kubernetes YAML: yaml: line 23: found character that cannot start any token"
而 installDaemonset 是「先删 daemonset/configmap/serviceaccount 再 apply」,
所以每次失败都会把上一份 Promtail 删掉却不重建 → 日志采集中断。
修复:
1) Sprintf 补上 DaemonSet 命名空间参数(第 9 位)
2) 新增 validateManifest():在删旧资源之前先本地解析校验清单,
校验不过直接中止、不碰任何现有资源 —— 让模板错误永远不会再打断采集
3) 新增回归测试 promtail_manifest_test.go:用平台同一套
YAMLOrJSONDecoder 解析 3 种参数组合,断言各解析出 5 个对象且无 TAB
=== 二、快捷 LogQL:Ingress 访问日志不再跟随所选命名空间 ===
原实现:next = applyLabelFilter(next,'namespace', namespace.trim() || 'ingress-nginx')
→ 选了 monitor 就生成 {namespace="monitor"} |= "HTTP/",查不到任何日志
(ingress controller 的 Pod 只在 ingress-nginx 命名空间里)
修复:ingress 模板独立处理
- 固定用 ingress controller 命名空间(ingress-nginx,缺失则回退 ingress)
- 不叠加当前选中的 app / pod 过滤(那些属于业务服务)
- 结果是 {namespace="ingress-nginx"} |= "HTTP/"
=== 三、K8s 资源名去掉 opsxloki ===
ClusterRole / ClusterRoleBinding:opsxloki-promtail → loki-promtail
- PromtailDaemonsetYAML 里 3 处 name 全部改名
- uninstallDaemonset 同步改名,并保留对旧名 opsxloki-promtail 的兼容清理,
避免升级后留下孤儿 ClusterRole
- 集群侧已实测:新建 loki-promtail,旧的 opsxloki-promtail 已删除
保留的 opsxloki(有兼容性约束,改动会丢数据/掉登录):
- Go module 导入路径 opsxloki/xxx(149 处,纯内部包名)
- 数据文件 opsxloki.json(改了读不到历史数据)
- 前端 localStorage key opsxloki.auth.* / opsxloki.theme.* 等(改了在线用户掉登录)
- Dockerfile 里的 opsxloki 系统用户/二进制名
镜像:lghost/lokistack:latest + v0.1 = sha256:4a5590580cbb
|
2026-10-27 04:16:55 +00:00 |
|
opsx
|
41633f0b38
|
feat(collector): 采集命名空间多选 + app 标签来源可自定义 + app 白名单
=== 一、采集命名空间改为多选(原来只有文本输入)===
- CollectorEditDialog 重写:勾选式多选面板,从
GET /clusters/:id/namespaces 拉取真实命名空间列表
- "所有命名空间(全集群采集)"一键全选(targetNamespaces 置空即全集群)
- 支持搜索过滤命名空间;已选数量实时显示
- 保存时为空数组 → Promtail 不加 namespaces 白名单
=== 二、app 标签取值来源可配置 ===
新增 Collector.AppLabelSource:
留空(默认)= app.kubernetes.io/name → app → controller 名(去hash) → pod 名 四级回退
可填任意 Pod 标签,如 app.kubernetes.io/name / app / k8s.kuboard.cn/name
也可直接写 __meta_kubernetes_pod_label_xxx
→ 作为最高优先级插到回退链最前,其余仍兜底
模板侧新增:
sanitizePromLabel() 标签名 → Prometheus meta label(. / 等转 _)
promtailAppSourceLabel() 解析用户配置
promtailAppIncludeFilter() 生成白名单 keep 规则
=== 三、app 白名单(控制采集量)===
新增 Collector.AppInclude []string:
填了之后生成
- source_labels: [app]
regex: ^(app1|app2)$
action: keep
只保留匹配的日志流 —— 这是收敛日志量最直接的手段
service.Update 同步支持 appLabelSource / appInclude(兼容 []string 与逗号分隔字符串)。
前端 CollectorEditDialog:
采集命名空间 = 勾选多选 + 全选
app 标签取值来源 = 输入框 + 5 个常用值 datalist 提示
只采集这些 app = 逗号分隔输入
镜像:lghost/lokistack:latest + v0.1 = sha256:43369d06cb28
|
2026-10-27 03:55:13 +00:00 |
|
opsx
|
e006fc1a0e
|
fix(promtail): app 标签通用回退 + 跳过僵尸 Pod + SecretKey 不下发 + 采集命名空间可选
=== 一、astralx 只采集到 2 个 app(核心问题)===
实证:用户的 Deployment Pod 模板只有 k8s.kuboard.cn/name 标签,
没有 app / app.kubernetes.io/name。原模板的 app 只从这两个标签取,
所以 115 个服务的 app 全是空值,Loki 里 app 只剩零星 2 个。
修复:app 四级回退,全部基于 Kubernetes 原生元数据(跨集群通用):
1) app.kubernetes.io/name 标准约定
2) app 常见简写
3) __tmp_controller_name Pod owner(ReplicaSet) 去 hash → Deployment 名
(__meta_kubernetes_pod_controller_name 由
Promtail 直接从 K8s API 读取,任何集群都有,
不依赖 Kuboard/Rancher 等第三方工具注入的标签)
4) pod name 兜底,保证 app 永不为空
同时修复 PromtailHelmValues 模板同样的 app 逻辑,
并修正其 __meta_kubernetes__pod_name 双下划线笔误。
验证:astralx 的 app 值从 2 个 → 73 个。
=== 二、15 分钟 27 万条日志的真相 ===
排查结论:不是重复采集(positions 存 hostPath /run/promtail,Pod 重启不丢)。
真实原因:cex-openapi-gateway 残留 347 个 Evicted + 37 个 Error 的僵尸 Pod,
只有 1 个 Running。这些 Pod 的日志文件仍在 /var/log/pods/ 下,
Promtail 会把它们的历史异常堆栈(Reactor at reactor.core.publisher...)
全量采集,造成 27 万条的假象并污染存储。
修复:relabel_configs 首条加 drop 规则:
- source_labels: [__meta_kubernetes_pod_phase]
regex: (Succeeded|Failed)
action: drop
(两个模板都加)
=== 三、SecretKey 不再下发给前端 ===
安全要求:避免任何登录用户点「显示」按钮就能拿到对象存储 SK。
- StorageConfig.Sanitized(): secretKey 返回空串,仅保留 hasSecretKey 标记
(accessKey 仍明文返回,便于管理员核对用的是哪个 AK)
- 测试连接:StorageTestRequest 新增 id 字段,secretKey 留空时后端用
库里保存的凭据解密补齐(新建存储仍强制填写)
- 前端编辑时 SecretKey 一律留空 = 不修改,placeholder 显示
「已保存 · 留空则不修改」,测试时带上 id
=== 四、采集器支持指定命名空间 ===
- Collector 新增 TargetNamespaces []string,对应 promtail 的
kubernetes_sd_configs.namespaces.names(为空=全集群)
- PromtailDaemonsetYAML 新增 promtailNamespaceFilter 生成白名单
- service.Update 支持 targetNamespaces(兼容 []string 与逗号分隔字符串)
- 前端 CollectorEditDialog 增加「采集命名空间」输入(逗号分隔)
镜像:lghost/lokistack:latest + v0.1 = sha256:05b1505aedae
|
2026-10-27 03:30:25 +00:00 |
|
opsx
|
24caeefb3a
|
fix(deployments): 卡片按钮布局拥挤 + 去掉冗余的日志查询/采集器/跳转入口
用户反馈 /deployments 卡片按钮布局乱,且「日志查询」「采集器」和 endpoint 跳转按钮多余。
问题根因:
1. ResourceCard 的操作区是 flex 但没有 flex-wrap,
而 DeploymentCard 在部署/停止/重启按钮上用了 flex-1,
窄卡片里 7 个按钮互相挤压、换行错位、宽度不均
2. endpoint 在 K8s 模式下是 http://loki.log.svc.cluster.local:3100
这类集群内地址,浏览器点不开,展示出来纯属噪音
修复:
- ResourceCard 操作区加 flex-wrap + items-center(按钮多时自动规整换行)
- DeploymentCard 去掉 4 处 flex-1(按钮按内容宽度排布)
- 去掉「访问」字段(不再展示 cluster-local endpoint)
- 去掉「日志查询」跳转按钮(左下导航已有入口)
- 去掉「采集器」跳转按钮(同上)
- 去掉 endpoint 的 ExternalLink 跳转按钮
- 清理不再使用的 import:Link(react-router)、ExternalLink(lucide)
卡片保留的按钮:配置 / 部署(或 停止+重启)/ 日志 / 删除
镜像:lghost/lokistack:latest + v0.1 = sha256:d89b9cfa0054
|
2026-10-27 03:10:42 +00:00 |
|
opsx
|
188d9460fe
|
fix(logs): 「所有人」策略组无法新建(被误判为已属于其他策略组)
现象:userIds:[0] 新建策略组时报
「用户 0 已属于此数据源的另一个策略组」HTTP 400
根因:validatePolicyGroup 的跨组冲突检查把 EveryoneUserID(0) 当真实用户,
只要已有任何一个「所有人」策略组(如内置默认策略),
再新建含 0 的组必然冲突。
修复:跨组冲突检查跳过 AnyoneUserID(0) —— 它是哨兵值不是真实用户,
允许多个策略组同时包含「所有人」。
验证:
POST /logs/policy-groups {userIds:[0]} → 200,id=2 正常创建
POST userIds:[0,2](混选) → 400「「所有人」不能与具体用户同时选择」
GET /logs/policy-groups → 默认策略(所有人 · default 命名空间)
镜像:lghost/lokistack:latest + v0.1 = sha256:3222a8716fdd
|
2026-10-27 03:02:50 +00:00 |
|
opsx
|
cf54be3f7d
|
fix(logs): 「所有人」策略保存被拒 + 快捷 LogQL namespace 空且不认默认策略
=== 一、保存策略组报「用户ID无效或重复」===
validatePolicyGroup 里 `if id == 0 || seen[id]` 直接把「所有人」哨兵当非法。
修复:
- 新增常量 AnyoneUserID = 0(与 policyGroupAccess、前端 ANYONE 对齐)
- 0 跳过用户存在性校验,但必须独占:与具体用户混选时报
「「所有人」不能与具体用户同时选择」
- 重复检查独立出来,报错带具体 ID
=== 二、快捷 LogQL 的 namespace 下拉总是空的 ===
两个叠加原因:
1. 用的是原生 <datalist>,只在输入时才提示、样式弱,看着像"下拉是空的"
2. 候选只来自 Loki 实际值 —— 而默认策略配的 default 命名空间里没有业务 Pod,
Loki 里根本没有这个 label 值,所以永远不出现;前端也不会拿策略值当默认选中
修复:
后端新增 GET /api/v1/logs/my-scope?sourceId= → {labels, unrestricted}
(services.UserAccessLabels 暴露当前用户在数据源上的授权 labels;
admin 返回 unrestricted=true 但仍带上策略 labels 供 UI 做默认值)
前端:
- logsApi.myScope()
- QuickQueryTemplates 用策略里的 namespace 作为初始选中值
- namespaceOptions = 策略值 ∪ Loki 实际值(去重,策略值优先)
- namespace/app/pod 三个 <datalist> 全部换成 SearchableValueSelect
(点击即展开、可搜索,placeholder 显示「请选择(共 N 个)」)
验证:
PUT /logs/policy-groups/1 {userIds:[0]} → 200 ✅
POST userIds:[0,2](混选) → 400「「所有人」不能与具体用户同时选择」✅
GET /logs/my-scope?sourceId=1 → {"labels":{"namespace":"default"},"unrestricted":true} ✅
bundle: my-scope 1 处、datalist 残留 0 处 ✅
镜像:lghost/lokistack:latest + v0.1 = sha256:ad659f564665
|
2026-10-27 02:59:33 +00:00 |
|
opsx
|
4dda6e5ba8
|
fix(logs): 快捷 LogQL null 崩溃 + app 标签错位 + 查询授权「所有人」与默认策略
=== 一、快捷 LogQL 报 Cannot read properties of null (reading 'map') ===
根因:Loki 对无值的 label 返回 {"data":null},Go nil slice 序列化成 JSON null,
前端 setState(null) 后再 .map() / [...null] / .filter() 直接抛错。
表现为「不稳定」——切到某个没有该 label 值的范围就崩。
修复(前后端双保险):
后端 LokiLabels / LokiLabelValues 归一化 nil → []string{}
(LokiHistogram 原本就返回空切片,无需改)
前端 6 处调用点加 Array.isArray 防御:
QuickQueryTemplates ×3、QueryInput ×2、LokiLabelBrowser ×2
=== 二、选 astralx 只显示 2 个 app(不是权限问题)===
实测:
astralx 117 个 Deployment / 497 个 Pod
带 app 标签的 Pod 只有 2 个(cex-promotion-web / cex-web-h5)
其余 115 个 Deployment 的 Pod 模板没打 app,Kuboard 打的是 k8s.kuboard.cn/name
Loki 里 service_name / container 各有 79 个值(Promtail 从容器名生成)
修复:QuickQueryTemplates 的「app」候选改为三级回退 app → service_name → container,
并用 appLabel 状态记住实际生效的标签名,pod 收窄与 generate() 都用它,
保证生成的 LogQL selector 真能查到数据。
=== 三、查询授权:支持「所有人」+ 内置默认策略 ===
1. 后端 policyGroupAccess:userIds 里的 0 作为哨兵值表示「所有人」(anyone / *),
匹配任意用户(原来只能逐个指定用户 ID)
2. 种子 seedDefaultPolicy():首次启动且尚无任何策略组时,创建
「默认策略(所有人 · default 命名空间)」(labels: namespace=default,userIds: [0])。
管理员可在「查询授权」里把命名空间改成实际业务命名空间(如 astralx),
对该组所有用户立即生效。
3. 前端 PolicyGroupsPage:新增「所有人(anyone)」一键按钮(与具体用户互斥),
列表与编辑区显示「所有人」,校验文案同步更新。
验证:
GET /logs/label/nonexistent_xyz/values → {"data":[]}
GET /logs/label/service_name/values?query={namespace="astralx"} → 79 个
GET /logs/policy-groups → 1 条:默认策略(所有人 · default 命名空间)所有人
镜像:lghost/lokistack:latest + v0.1 = sha256:88a4ba626f62
|
2026-10-27 01:19:06 +00:00 |
|
opsx
|
46e73d3d17
|
fix(loki): COS 存储全链路修复 —— Secret 生成/env 注入/凭据解密/endpoint 规范化
API 端到端跑通中暴露 4 个串联问题(腾讯云 COS + K8s 场景):
1. loki_manifest.go: switch 缺少 cos / obs
→ 不生成 loki-storage-credentials Secret、不注入 LOKI_STORAGE_* 环境变量、
也不给 Loki 加 -config.expand-env=true
→ 配置里 ${LOKI_STORAGE_ACCESS_KEY} 无法展开 → AK 为空
→ COS 返回 403 InvalidAccessKeyId(ruler 每分钟报错)
修复:case 补上 cos / obs
2. loki_manifest.go: 生成 Secret 时用了 ENC: 密文原始值
→ Secret data 里是 ENC:xxx 而非真实 AK/SK
修复:crypto.DecryptField 解密后再 base64
3. loki_config.go: host-docker 模式同样把密文写进配置
修复:解密后返回
4. loki_config.go: cos/oss/obs 的 endpoint 未剥离桶名前缀
→ 用户填虚拟主机式 endpoint(mybucket-1250000000.cos.<region>...)时,
Loki 的 S3 客户端又拼一次 bucketnames,变成 <bucket>.<bucket>.cos...
→ TLS 证书校验失败(x509: certificate is valid for ... not <bucket>.<bucket>.cos...)
修复:新增 stripBucketPrefix,s3/cos/oss/obs/minio 分支统一剥离桶名前缀
验证(log namespace,API 端到端):
- Loki statefulset 1/1 Running,ruler 无 InvalidAccessKeyId/x509
- 写入 + 查询日志成功(持久化到 COS)
- Promtail DaemonSet 12/12 全节点就绪
- collector status=active
运维侧附加动作(非代码,见部署文档):
- 存储配置改用有效 AK/SK + 区域端点 cos.ap-hongkong.myqcloud.com
- 平台 ServiceAccount log:lokistack 绑定 cluster-admin
(否则无法创建 Promtail 所需的 ClusterRole/ClusterRoleBinding)
|
2026-10-26 19:32:38 +00:00 |
|
opsx
|
004375e0ce
|
fix(ns): 命名空间固定 log —— 平台/Loki 实例/Promtail 全部收敛到 log
按需求:所有 lokistack 相关资源(管理平台副本、Loki 实例、Promtail 采集器)
固定使用 log 命名空间,不提供覆盖开关。
改动:
1. 后端 services/deployment.go Create():k8s-yaml 模式下 namespace 为空时
兜底为 "log",前端漏传也不会跑偏。
2. 前端 DeploymentCard.tsx:命名空间展示 fallback 'loki' → 'log'
3. 前端 DeploymentFormDialog.tsx:输入框 placeholder 'loki' → 'log'
(form-schema.ts 的 DEFAULT_VALUES.namespace 原本已是 'log')
未动:
kubernetes/install.sh 的 NS="log" 早就是固定值
collector_deployer.go 已自动跟随 loki.Namespace(collector.Namespace 为空时)
资源名不冲突:
平台 lokistack / lokistack-data / lokistack-secrets / lokistack-nodeport
Loki 实例 loki / loki-config / loki-data / loki-headless / loki-storage-credentials
Promtail promtail-* (DaemonSet)
镜像:lghost/lokistack:latest + v0.1 = sha256:9d15b7ffbb3d
|
2026-10-26 18:55:06 +00:00 |
|
opsx
|
cfe1ccd154
|
fix(storage): 按 endpoint 形态解析响应 + 凭据解密返回 + 无建桶权限友好提示
【真实根因】COS 桶明明存在却报"桶不存在/创建失败"
用户配置:endpoint=loki-log-1394668510.cos.ap-hongkong.myqcloud.com(虚拟主机式,已含桶名)
旧代码:始终 GET / 并按 ListBuckets 的 <Buckets><Bucket><Name> 解析。
但在虚拟主机式 endpoint 下,GET / 返回的是 ListObjects(<ListBucketResult>)——
桶内对象列表,不是桶列表。解析必然找不到桶名 → 误判"桶不存在"
→ 触发建桶 PUT → 用户的 AK 无 cos:PutBucket 权限 → 403
→ 最终报"创建后校验失败"。
【实测确认(用该 AK 直连 COS)】
GET / (vhost) → 200 <ListBucketResult> 桶存在,能列对象
GET / (区域端点) → 200 <ListAllMyBucketsResult> AK 有 cos:GetService
HEAD / → 403 AK 无 cos:HeadBucket
PUT / (CreateBucket) → 403 AK 无 cos:PutBucket
即 QcloudCOSDataFullControl = 数据面全控(读写/列桶),不含管控面(建桶/HeadBucket)。
【修复】
1. 探测统一用 GET /,再按 endpoint 形态解释响应:
- 虚拟主机式 → 按 ListObjects(<ListBucketResult>)判断,200 即桶存在
- 区域端点 → 按 ListBuckets(<Buckets><Bucket><Name>)匹配桶名
桶存在就直接返回"桶已存在",不再走建桶流程。
2. 建桶二次校验同样改用 GET /(不再用 HEAD,避免 HeadBucket 403 误判)。
3. 建桶返回 403 时给出明确友好提示:
"当前 Access Key 无建桶权限。腾讯云 QcloudCOSDataFullControl 只授予数据读写/列出,
不含 CreateBucket。请到 COS 控制台手动创建桶,或在 CAM 追加 cos:PutBucket"
4. models/storage.go Sanitized() 用 crypto.DecryptField 解密后再返回 AK/SK
(原来直接返回 ENC:xxx 密文,前端编辑框显示密文)。
5. 前端访问地址输入框补充说明:推荐填区域端点(不带桶名)。
镜像:lghost/lokistack:latest + v0.1 = sha256:350478003870
|
2026-10-26 18:38:00 +00:00 |
|
opsx
|
ff69ea2ce8
|
fix(storage): 凭证明文返回便于编辑测试 + 建桶后二次校验防"假成功"
问题(log.astx.club/storage):
1. 编辑存储时 Access Key / Secret Key 都是空的,点「测试连接」提示
请先填写 Bucket 与 Access Key / Secret Key,无法测试也无法保存;
2. 提示"桶不存在,已自动创建"但疑似没有真正创建;
3. 测试失败提示用 toast 弹到右下角,不在弹框内。
修复:
1. models/storage.go Sanitized() 返回 accessKey / secretKey 明文
(内网运维工具,管理员需要在编辑弹框里看到并测试连接;脱敏会导致
编辑时字段为空、校验失败)
2. 前端 StorageConfig 类型补 accessKey / secretKey 字段;
编辑弹框打开时回填这两个字段(原来只有 secretKey 且强制置空);
Secret Key 标签去掉「留空保持不变」
3. services/storage_check.go 建桶后二次 ListBuckets 校验:
不同对象存储 CreateBucket 语义不同(腾讯云 COS 要求桶名放 Host、
路径为 /),直接 PUT /<bucket> 可能返回 200 但未真正创建。
现在创建后重新列举桶,找不到就报"创建后校验失败"并提示检查
Endpoint / Region / 桶名(COS 需带 APPID)
4. 前端「请先填写 Bucket 与 Access Key / Secret Key」改为在弹框内
testResult 区域显示(原来 toast 弹右下角)
镜像:lghost/lokistack:latest + v0.1 = sha256:6b6cc9769e79
|
2026-10-26 18:30:47 +00:00 |
|
opsx
|
d0b9b6b71d
|
fix(ui): 前端用户可见文案 opsx-loki → lokistack
修正用户能看到/会复制走的旧名(你反馈的 K8s 示例 token 命令在内):
1. 界面标题(4 处)
App.tsx 浏览器标题栏
Layout.tsx 侧边栏 logo / 页面标题 fallback / 移动端导航
login/index.tsx 登录页大标题
settings/index.tsx 「关于 lokistack」
2. 集群接入示例(用户直接复制到集群执行,5 处)
ClusterFormDialog.tsx:
Role / ServiceAccount 名: opsx-loki → lokistack
token Secret 名: opsx-loki-token → lokistack-token
取 token 命令: get secret opsx-loki-token → lokistack-token
3. 文档
docs/deployment/README.md, host-nginx.md 旧名全量修正
README.md 迁移章节之外的示例命令修正;
迁移章节(从 opsx-loki 升级)保留旧名作历史对照
【刻意不改】
a) localStorage key(9 个)
opsxloki.auth.{token,refresh-token,user}
opsxloki.theme.mode / layout.collapsed / chunk-reloaded
opsxloki.logs.{query-history,range-history}
原因:改了所有在线用户立即掉登录,主题/折叠/查询历史全丢。
要改必须配套迁移(参考 main.tsx 中 lokideploy.* → opsxloki.* 的写法)。
b) 数据文件名 opsxloki.json(后端硬编码,改了读不到 DB)
c) Go module 路径 opsxloki/...(159 处 import)
属编译期内部标识,最终用户不可见;改名需全仓重构 + go.mod 变更。
|
2026-10-26 17:30:30 +00:00 |
|
opsx
|
3c9388658b
|
refactor: 资源名 opsx-loki/opsxloki → lokistack(线上已迁移完成)
【已在线完成,零数据丢失】
K8s ns=log 资源全部改名:
Deployment opsx-loki → lokistack
Service opsx-loki → lokistack
Service opsx-loki-nodeport → lokistack-nodeport
PVC opsxloki-data → lokistack-data
Secret opsxloki-secrets → lokistack-secrets
Label app: opsx-loki → app: lokistack
Ingress log.astx.club 后端 → lokistack-nodeport:8080
镜像 lghost/opsxloki → lghost/lokistack
docker-compose 卷/容器名同步更名
【保留不变(改了会出事)】
namespace: log
数据文件名 opsxloki.json(代码硬编码)
容器内路径 /app/data
【迁移过程】
1. 备份 PVC 数据(32KB) + Secret 原值 + 旧 yaml → /root/lokistack-migration-backup-20261026-171141
2. scale 旧 Deployment → 0,等 Pod 消失
3. 复制 Secret(JWT sha256 校验一致)
4. 新建 PVC lokistack-data(Bound 30Gi cbs)
5. 临时 Pod 恢复数据(chown 1000:1000,opsxloki.json 保留 0600)
6. apply 新 Deployment → 1/1 Running
7. 修复 Ingress 后端(TKE qcloud 要求 NodePort)→ CLB 恢复 200
8. 删除旧 Deployment / 失效 Service
【验证】
lokistack 1/1,0 重启,镜像 lghost/lokistack:latest(statically linked)
/api/v1/health → {"status":"ok","version":"2.0.0"}
/app/data/opsxloki.json 610B 完整(admin 用户 + 配置)
http://log.astx.club/ → HTTP 200
镜像加固:admin123=0、/app/backend/=0、initial-admin-password.txt 存在
【回滚兜底(建议 7 天后删)】
pvc/opsxloki-data、secret/opsxloki-secrets
【文档】
README 新增「从 opsx-loki 升级」章节:
K8s 七步迁移脚本 + docker-compose 卷迁移命令
|
2026-10-26 17:16:15 +00:00 |
|
opsx
|
b65dcc9677
|
refactor: 镜像名 lghost/opsxloki → lghost/lokistack(跟随项目更名)
只改镜像引用(8 处),K8s 资源名一律不动 —— 改了会丢数据/断服务:
改:
kubernetes/deployment.yaml image
docker-compose.yml image
docker-compose.prod.yml image ×2
kubernetes/README.md 文档 ×2
docs/deployment/kubernetes.md 文档 ×2
不改(重要):
opsxloki-secrets K8s Secret —— 改名会导致 JWT_SECRET/ENCRYPTION_KEY 重置,
历史加密的实例密码无法解密
opsxloki-data PVC —— 改名 = 新建空卷,数据全丢
container_name docker 容器名 —— 改名会让 docker exec/logs 命令失效
app: opsx-loki Pod label —— 改名要同步改 Service selector
deployment 名称 opsx-loki —— 改名要重建 Deployment
⚠️ 部署顺序(本提交只改代码,尚未 apply 到 K8s):
1) 推镜像 lghost/lokistack:latest 到 Docker Hub
2) kubectl apply -f kubernetes/deployment.yaml
3) 等 Pod 1/1 就绪
4) 才能删除 lghost/opsxloki
顺序颠倒会因 imagePullPolicy: Always 拉到不存在的镜像导致服务中断
|
2026-10-26 17:09:47 +00:00 |
|
opsx
|
ec759cd36b
|
build: Dockerfile 编译加 -trimpath(抹除源码绝对路径)
加固前:二进制内可 strings 出 66 处 /app/backend/controllers/*.go 等源码路径,
泄露完整目录结构与文件名。
加固后:路径被 trimpath 抹除,命中数 0。
注:build.sh / run.sh 已在上个提交加过 -trimpath,本次补上实际部署用的
Dockerfile 构建路径(K8s / docker-compose 走的就是这里)。
|
2026-10-26 17:05:11 +00:00 |
|
opsx
|
3c27e58a73
|
feat(security): 移除 admin123 硬编码默认密码 + 编译加 -trimpath
安全加固(降低二进制泄露/逆向的信息面):
1. 初始密码随机化
- 首次启动生成 16 位随机密码(大小写+数字,排除 0/O/1/l/I 易混淆字符)
- 写入数据卷 initial-admin-password.txt(0600)并打印到首次启动日志
- admin 首次改密后自动删除该文件(seed.CleanupInitPassword)
- 支持 ADMIN_INIT_PASSWORD 环境变量指定(自动化部署 / 固定密码场景)
- 生成失败直接 Fatal 退出,不再降级到公开默认口令
2. 移除硬编码 admin123
- main.go 不再打印 "Default login: admin / admin123"
- 二进制内 admin123 命中:2 → 0
3. -trimpath
- build.sh / run.sh 编译加 -trimpath
- 二进制内 /app/backend/ 源码路径命中:66 → 0
4. 文档
- README 新增「第 4 步:获取管理员初始密码」
(docker compose exec / logs / docker cp / named volume 四种方式 + K8s 对应命令)
- .env.example 补 ADMIN_INIT_PASSWORD
- kubernetes/deployment.yaml 加 env 注释说明
验证(strings 对比加固前后二进制):
admin123 2 → 0
/app/backend/ 66 → 0
initial-admin-password.txt / ADMIN_INIT_PASSWORD / CleanupInitPassword 均存在
|
2026-10-26 17:01:09 +00:00 |
|
opsx
|
9a46a955d4
|
feat(k8s): 一键部署脚本 + 跨云适配 + 云相关清单拆分到 examples
新增 kubernetes/install.sh(一条命令部署):
- 云平台识别:读节点 providerID,识别 TKE / EKS / ACK / CCE / GKE / AKS / K3s / 自建
- StorageClass 三级探测:显式 STORAGE_CLASS → is-default-class 注解 → 集群唯一 SC
- 自建集群兜底:无任何 SC 时自动创建静态 local PV
(hostPath + DirectoryOrCreate + nodeAffinity + WaitForFirstConsumer + Retain)
- Secret 幂等生成:已存在则跳过,绝不覆盖(避免密钥轮换导致历史凭证无法解密)
- 失败分类诊断:ImagePullBackOff / CreateContainerConfigError / PVC Pending
按云给出对应仓库格式与推荐 SC 名
- 环境变量:WITH_NODEPORT / STORAGE_CLASS / LOCAL_STORAGE / NODE_NAME / PV_PATH / PV_SIZE
云相关清单拆到 kubernetes/examples/:
- ingress.yaml(阿里云 alb)、ingress-tke.yaml(腾讯云 qcloud)、secret.example.yaml
- 原因:留在根目录会导致 kubectl apply -f kubernetes/ 时 ingress 同名互相覆盖,
且 secret.example.yaml 的占位符会把真实密钥顶掉
README 重写:
- 全部用法速查 + 环境变量一览
- 自建 K8s 存储方案选型(local PV / local-path-provisioner / OpenEBS / Longhorn / NFS)
- hostPath 数据运维:定位、热备份、跨节点迁移、卸载保留、常见坑
|
2026-10-26 14:03:39 +00:00 |
|