feat(collectors): Promtail DaemonSet 的部署命名空间固定为 log,表单隐藏该字段
CI / Backend (go vet + test) (push) Waiting to run
CI / Frontend (lint + build + test) (push) Waiting to run

=== 需求 ===
创建 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
This commit is contained in:
opsx committed 2026-10-31 01:56:15 +00:00
1 parent 2e97c04f84
commit 9448868a71
4 files changed
+26 -2

No files matched your search

+12
View File
@@ -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
@@ -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',
@@ -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: '全集群',
@@ -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/<uid>/*.log,不需要用户填;
// 真要限制范围用 targetNamespaces(空 = 全集群)
logPath: values.type === 'k8s-daemonset' ? '' : values.logPath,
@@ -368,6 +369,9 @@ export const CollectorFormDialog = memo(function CollectorFormDialog({
)}
/>
<div className="grid grid-cols-2 gap-4">
{/* DaemonSet 的部署命名空间固定跟随 Loki(通常在 log),
与 LokiStack 放在一起,用户不需要选,所以整块隐藏。 */}
{!isDaemonsetType && (
<FormField
control={form.control}
name="namespace"
@@ -390,7 +394,7 @@ export const CollectorFormDialog = memo(function CollectorFormDialog({
placeholder={!clusterID ? t('form.nsPickCluster') : namespacesLoading ? t('form.nsLoading') : t('form.nsPick')}
/>
) : (
<Input placeholder={collectorType === 'k8s-daemonset' ? 'logging' : 'default'} {...field} onChange={(event) => {
<Input placeholder="default" {...field} onChange={(event) => {
field.onChange(event)
if (isSidecarType) form.setValue('targetDeployment', '')
}} />
@@ -416,6 +420,12 @@ export const CollectorFormDialog = memo(function CollectorFormDialog({
</FormItem>
)}
/>
)}
{isDaemonsetType && (
<div className="col-span-2 rounded-md border border-dashed px-3 py-2 text-xs text-muted-foreground">
{t('form.namespaceFixedDaemonset')}
</div>
)}
{isSidecarType && (
<FormField
control={form.control}