From 9448868a714a732b22b2543e345bc7d93b80b485 Mon Sep 17 00:00:00 2001 From: opsx Date: Sat, 31 Oct 2026 01:56:15 +0000 Subject: [PATCH] =?UTF-8?q?feat(collectors):=20Promtail=20DaemonSet=20?= =?UTF-8?q?=E7=9A=84=E9=83=A8=E7=BD=B2=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E4=B8=BA=20log=EF=BC=8C=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E9=9A=90=E8=97=8F=E8=AF=A5=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit === 需求 === 创建 Loki 部署后添加采集策略(Promtail DaemonSet)时,"部署节点命名空间" 不该让用户选 —— 采集器必须和 LokiStack 放在一起(log)。 === 前端 === CollectorFormDialog:选择 k8s-daemonset 时整块隐藏 namespace 字段 (连同分类型提示、手动输入开关一起),改为一条只读说明: 「采集器部署命名空间固定为 Loki 所在命名空间(通常 log),与 LokiStack 放在一起,无需选择。要限制采集范围请用下面的『只采集这些命名空间』。」 提交时 DaemonSet 不传 namespace(原来会传用户填的值)。 顺带修掉隐藏后暴露的 tsc 报错:placeholder 里 `collectorType === 'k8s-daemonset'` 在收窄后的分支里已成不可能比较,改为常量 'default'。 === 后端(强制约束,不只靠前端)=== services/collector.go: Create:type == k8s-daemonset 时强制清空 req.Namespace → 渲染与部署两侧都会回退到 d.loki.Namespace Update:DaemonSet 忽略 updates["namespace"] 原始渲染逻辑本就有"为空则用 LokiDeployment.Namespace,再兜底 log"。 为什么后端也要拦:API 直接调用(脚本/其他客户端)不该能把采集器 部署到任意命名空间——那样会出现"平台显示在 log、实际 DaemonSet 在别处" 的状态漂移。 === 实测(0108617e4180)=== 现有采集器 promtail-pre: type=k8s-daemonset namespace='log' PUT {"namespace":"evil-ns"} → 返回 success,但读回仍是 'log' ✅ 被忽略 tsc 通过;Pod = Hub latest = 0108617e4180 --- backend/services/collector.go | 12 ++++++++++++ frontend/src/i18n/locales/en/collectors.ts | 1 + frontend/src/i18n/locales/zh/collectors.ts | 1 + .../collectors/components/CollectorFormDialog.tsx | 14 ++++++++++++-- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/backend/services/collector.go b/backend/services/collector.go index 3c3b82d..91e1ef6 100644 --- a/backend/services/collector.go +++ b/backend/services/collector.go @@ -274,6 +274,13 @@ func (s *CollectorService) Create(req *CreateCollectorRequest) (*models.Collecto refType = "host" } + // DaemonSet 的部署命名空间固定为 Loki 所在命名空间(通常 log): + // 采集器与 LokiStack 放在一起,用户不需要也不应该选。 + // 这里强制清空,渲染与部署两侧都会回退到 d.loki.Namespace。 + if req.Type == "k8s-daemonset" { + req.Namespace = "" + } + collector := models.Collector{ ID: repository.GetDB().NextID("collectors"), Name: req.Name, @@ -374,6 +381,11 @@ func (s *CollectorService) Update(id uint, updates map[string]interface{}) error if v, ok := updates["labels"].(string); ok { db.Collectors[i].Labels = v } + // DaemonSet 的 namespace 一律不接受修改(固定跟随 Loki 所在命名空间) + if v, ok := updates["namespace"].(string); ok && v != "" && db.Collectors[i].Type != "k8s-daemonset" { + db.Collectors[i].Namespace = v + db.Collectors[i].StatusMessage = "部署命名空间已保存,点击部署以应用新配置" + } // targetNamespaces: 采集哪些命名空间([]string;空=全集群) if v, ok := updates["targetNamespaces"]; ok { var nss []string diff --git a/frontend/src/i18n/locales/en/collectors.ts b/frontend/src/i18n/locales/en/collectors.ts index dc377b2..0d9b3ef 100644 --- a/frontend/src/i18n/locales/en/collectors.ts +++ b/frontend/src/i18n/locales/en/collectors.ts @@ -81,6 +81,7 @@ const collectors = { namespaceHintSidecar: 'The namespace where the TARGET Deployment lives. The platform finds that Deployment there and injects a Fluent Bit sidecar; collected logs also carry this namespace label. It is NOT a log filter.', namespaceHintDaemonset: 'The namespace where the Promtail DaemonSet is deployed. The DaemonSet collects container logs from ALL namespaces in the cluster — it does not filter by namespace (usually just the Loki namespace, e.g. log).', namespaceHintHost: 'Not used in host mode.', + namespaceFixedDaemonset: 'The DaemonSet is always deployed into the Loki namespace (usually log), together with LokiStack — nothing to choose. To limit what gets collected, use "Only collect from these namespaces" below.', targetNamespaces: 'Only collect from these namespaces', targetNamespacesHint: 'Leave empty to collect container stdout from ALL namespaces. Selecting specific ones cuts log volume significantly.', targetNamespacesAll: 'All namespaces', diff --git a/frontend/src/i18n/locales/zh/collectors.ts b/frontend/src/i18n/locales/zh/collectors.ts index 2a643c2..06cf685 100644 --- a/frontend/src/i18n/locales/zh/collectors.ts +++ b/frontend/src/i18n/locales/zh/collectors.ts @@ -85,6 +85,7 @@ const collectors = { namespaceHintSidecar: '填「目标应用(Deployment)所在的命名空间」。平台会在该命名空间里找到目标 Deployment 并注入 Fluent Bit sidecar,采集到的日志也会打上这个 namespace 标签。它不是日志过滤条件。', namespaceHintDaemonset: '填「Promtail DaemonSet 部署到哪个命名空间」。DaemonSet 会采集整个集群所有命名空间的容器日志,不按命名空间过滤(一般就用 Loki 所在的 log)。', namespaceHintHost: '主机模式不使用该字段。', + namespaceFixedDaemonset: '采集器部署命名空间固定为 Loki 所在命名空间(通常 log),与 LokiStack 放在一起,无需选择。要限制采集范围请用下面的「只采集这些命名空间」。', targetNamespaces: '只采集这些命名空间', targetNamespacesHint: '不选 = 采集整个集群所有命名空间的容器标准输出。选了就只采这几个(能显著减少日志量)。', targetNamespacesAll: '全集群', diff --git a/frontend/src/pages/collectors/components/CollectorFormDialog.tsx b/frontend/src/pages/collectors/components/CollectorFormDialog.tsx index 0f2af58..a617a19 100644 --- a/frontend/src/pages/collectors/components/CollectorFormDialog.tsx +++ b/frontend/src/pages/collectors/components/CollectorFormDialog.tsx @@ -212,7 +212,8 @@ export const CollectorFormDialog = memo(function CollectorFormDialog({ : undefined, targetDeployment: values.type === 'k8s-fluentbit-sidecar' ? values.targetDeployment : undefined, - namespace: values.type !== 'host-promtail' ? values.namespace : undefined, + // DaemonSet 的后端会强制用 Loki 所在命名空间(固定 log),这里不传 + namespace: values.type !== 'host-promtail' && values.type !== 'k8s-daemonset' ? values.namespace : undefined, // DaemonSet 的日志路径由平台固定为 /var/log/pods//*.log,不需要用户填; // 真要限制范围用 targetNamespaces(空 = 全集群) logPath: values.type === 'k8s-daemonset' ? '' : values.logPath, @@ -368,6 +369,9 @@ export const CollectorFormDialog = memo(function CollectorFormDialog({ )} />
+ {/* DaemonSet 的部署命名空间固定跟随 Loki(通常在 log), + 与 LokiStack 放在一起,用户不需要选,所以整块隐藏。 */} + {!isDaemonsetType && ( ) : ( - { + { field.onChange(event) if (isSidecarType) form.setValue('targetDeployment', '') }} /> @@ -416,6 +420,12 @@ export const CollectorFormDialog = memo(function CollectorFormDialog({ )} /> + )} + {isDaemonsetType && ( +
+ {t('form.namespaceFixedDaemonset')} +
+ )} {isSidecarType && (