Files
LokiStack/backend/services/collector_sidecar_mount.go
logan 2d73661dd1 feat: complete Loki query, insights, settings and deployment workflows
Add the Loki query UI, access policies, ingress insights, alert and registry settings, and their backend APIs. Include the related Kubernetes deployment and collector updates required by the integrated application.

Validated with go test ./..., npm run build, and npm run lint.
2026-10-25 21:28:32 +08:00

151 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package services
import (
"encoding/json"
"fmt"
"path"
"strings"
)
type sidecarLogMount struct {
containerName string
volumeName string
mountPath string
subPath string
createVolume bool
mountApplication bool
}
type sidecarDeployment struct {
Spec struct {
Template struct {
Spec struct {
Containers []struct {
Name string `json:"name"`
VolumeMounts []struct {
Name string `json:"name"`
MountPath string `json:"mountPath"`
SubPath string `json:"subPath"`
SubPathExpr string `json:"subPathExpr"`
ReadOnly bool `json:"readOnly"`
} `json:"volumeMounts"`
} `json:"containers"`
Volumes []struct {
Name string `json:"name"`
} `json:"volumes"`
} `json:"spec"`
} `json:"template"`
} `json:"spec"`
}
// resolveSidecarLogMount 优先复用业务容器已有的卷;否则仅在专用日志目录创建共享 emptyDir。
func resolveSidecarLogMount(deploymentJSON []byte, target, logPath string, collectorID uint) (sidecarLogMount, error) {
var deployment sidecarDeployment
if err := json.Unmarshal(deploymentJSON, &deployment); err != nil {
return sidecarLogMount{}, fmt.Errorf("读取目标 Deployment 失败: %w", err)
}
logDir := path.Dir(strings.TrimSpace(logPath))
if !path.IsAbs(logPath) || logDir == "/" || strings.ContainsAny(logDir, "*?[]\n\r") {
return sidecarLogMount{}, fmt.Errorf("Sidecar 采集路径需为专用目录下的绝对文件路径或 glob,例如 /var/log/app/*.log")
}
containers := deployment.Spec.Template.Spec.Containers
var appIndex = -1
var candidate = -1
for i, container := range containers {
if container.Name == target {
appIndex = i
break
}
if container.Name != "fluent-bit" {
if candidate >= 0 {
candidate = -2
} else if candidate == -1 {
candidate = i
}
}
}
if appIndex < 0 {
appIndex = candidate
}
if appIndex < 0 {
return sidecarLogMount{}, fmt.Errorf("目标 Deployment 中无法确定业务容器,请使容器名与 Deployment 名一致,或仅保留一个业务容器")
}
app := containers[appIndex]
mount := sidecarLogMount{containerName: app.Name}
longest := -1
for _, item := range app.VolumeMounts {
prefix := strings.TrimSuffix(item.MountPath, "/")
if (logDir == prefix || strings.HasPrefix(logDir, prefix+"/")) && len(prefix) > longest {
if item.ReadOnly || item.SubPathExpr != "" {
return sidecarLogMount{}, fmt.Errorf("业务容器的日志卷必须可写,且不能使用 subPathExpr")
}
mount.volumeName = item.Name
mount.mountPath = item.MountPath
mount.subPath = item.SubPath
longest = len(prefix)
}
}
if mount.volumeName != "" {
if mount.mountPath == "/tmp" {
return sidecarLogMount{}, fmt.Errorf("日志卷不能挂载在 /tmp:Fluent Bit 用此目录保存读取位置;请改用专用日志目录")
}
return mount, nil
}
// 自动挂载会遮蔽目录原有内容,仅对明确的专用日志目录执行。
if logDir != "/var/log/app" && path.Base(logDir) != "logs" && path.Base(logDir) != "log" {
return sidecarLogMount{}, fmt.Errorf("业务容器未共享 %s;请将采集路径改为 /var/log/app/*.log 或已有日志卷中的文件", logDir)
}
volumeName := fmt.Sprintf("loki-collector-logs-%d", collectorID)
for _, volume := range deployment.Spec.Template.Spec.Volumes {
if volume.Name == volumeName {
return sidecarLogMount{}, fmt.Errorf("卷 %s 已存在但业务容器未挂载,请先检查目标 Deployment", volumeName)
}
}
mount.volumeName = volumeName
mount.mountPath = logDir
mount.createVolume = true
mount.mountApplication = true
return mount, nil
}
// sidecarRemovePatchYAML 只清理该采集策略注入的卷,保留业务原有的日志卷。
func sidecarRemovePatchYAML(deploymentJSON []byte, collectorID uint) (string, error) {
var deployment sidecarDeployment
if err := json.Unmarshal(deploymentJSON, &deployment); err != nil {
return "", fmt.Errorf("读取目标 Deployment 失败: %w", err)
}
injectedName := fmt.Sprintf("loki-collector-logs-%d", collectorID)
appPatch := ""
for _, container := range deployment.Spec.Template.Spec.Containers {
if container.Name == "fluent-bit" {
continue
}
for _, mount := range container.VolumeMounts {
if mount.Name == injectedName {
appPatch += fmt.Sprintf(" - name: %s\n volumeMounts:\n - mountPath: %q\n $patch: delete\n", container.Name, mount.MountPath)
}
}
}
volumePatch := ""
for _, volume := range deployment.Spec.Template.Spec.Volumes {
if volume.Name == injectedName {
volumePatch = fmt.Sprintf(" - name: %s\n $patch: delete\n", injectedName)
break
}
}
return fmt.Sprintf(`spec:
template:
spec:
containers:
- name: fluent-bit
$patch: delete
%s volumes:
- name: fluent-bit-config
$patch: delete
- name: fluent-bit-state
$patch: delete
%s`, appPatch, volumePatch), nil
}