package services import ( "fmt" "strings" ) // PromtailConfig 主机 Promtail 配置(推送至 lokiEndpoint,采集 logPath) // 生产级配置:持久化位置文件、批处理、重试、CRI日志格式解析 func PromtailConfig(lokiEndpoint, logPath string) string { return fmt.Sprintf(`server: http_listen_port: 9080 grpc_listen_port: 0 log_level: info log_format: logfmt positions: filename: /var/lib/promtail/positions.yaml sync_period: 10s clients: - url: %s/loki/api/v1/push batchwait: 1s batchsize: 1048576 timeout: 10s backoff_config: min_period: 500ms max_period: 5m max_retries: 10 external_labels: job: host-logs limits_config: readline_rate_enabled: false readline_rate: 1000 readline_burst: 10000 max_streams: 0 scrape_configs: - job_name: app static_configs: - targets: [localhost] labels: job: app host: ${HOSTNAME} __path__: %s pipeline_stages: - cri: {} - match: selector: '{job="app"}' stages: - timestamp: source: time format: RFC3339Nano - limit: rate: 1000 burst: 10000 drop: true `, lokiEndpoint, logPath) } // PromtailHelmValues DaemonSet 模式 Promtail Helm values(生产级) // 包含:cri解析、kubernetes标签、批处理配置、资源限制 func PromtailHelmValues(lokiEndpoint string) string { return fmt.Sprintf(`config: clients: - url: %s/loki/api/v1/push batchwait: 1s batchsize: 1048576 timeout: 10s backoff_config: min_period: 500ms max_period: 5m max_retries: 10 external_labels: cluster: ${CLUSTER_NAME:-unknown} positions: filename: /run/promtail/positions.yaml scrape_configs: - job_name: kubernetes-pods kubernetes_sd_configs: - role: pod pipeline_stages: - cri: {} - match: selector: '{app!=""}' stages: - docker: {} - match: selector: '{app!=""}' stages: - labels: app: namespace: pod: container: - limit: rate: 1000 burst: 10000 drop: true relabel_configs: - source_labels: [__meta_kubernetes_pod_controller_name] regex: ([0-9a-z-.]+?)(-[0-9a-f]{8,10})? action: replace target_label: __tmp_controller_name - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name] regex: .+ action: replace target_label: app - source_labels: [__meta_kubernetes_pod_label_app] regex: .+ action: replace target_label: app - source_labels: [__meta_kubernetes_pod_annotation_kubernetes_io_config_hash] regex: .+ action: replace target_label: config_hash - action: replace source_labels: ['__meta_kubernetes__pod_name'] target_label: 'pod' - action: replace source_labels: ['__meta_kubernetes_namespace'] target_label: 'namespace' - action: replace source_labels: ['__meta_kubernetes_pod_container_name'] target_label: 'container' - action: replace source_labels: ['__meta_kubernetes_pod_node_name'] target_label: 'node_name' - replacement: /var/log/pods/*$1/*.log separator: / source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name] target_label: __path__ daemonset: enabled: true resources: limits: cpu: 200m memory: 256Mi requests: cpu: 100m memory: 128Mi extraVolumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: promtail-run hostPath: path: /run/promtail extraVolumeMounts: - name: varlog mountPath: /var/log readOnly: true - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: promtail-run mountPath: /run/promtail `, lokiEndpoint) } // FluentbitConfig Sidecar 模式 Fluent Bit 配置(采集 logPath,推送至 lokiHost:lokiPort) // 生产级配置:kubernetes过滤器、内存缓冲、健康检查 func FluentbitConfig(lokiHost, lokiPort, logPath, app, namespace string) string { return fmt.Sprintf(`[SERVICE] Flush 1 Log_Level info HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2020 Parsers_File parsers.conf storage.path /tmp/flb-storage/ storage.sync normal storage.checksum off storage.max_chunks_pause On storage.backlog.mem_limit 5M Health_Check On HC_Errors_Count 5 HC_Retry_Failure_Count 5 HC_Period 60 [INPUT] Name tail Path %s Tag app.%s Refresh_Interval 5 Mem_Buf_Limit 10MB Skip_Long_Lines On Read_from_Head True DB /tmp/flb.db DB.Sync Normal Buffer_Max_Size 1MB storage.type filesystem [FILTER] Name modify Match app.* Add namespace %s Add app %s Add source fluent-bit [FILTER] Name kubernetes Match app.* Kube_URL https://kubernetes.default.svc:443 Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token Merge_Log On Keep_Log Off K8S-Logging.Parser On K8S-Logging.Exclude On Labels On Annotations Off [OUTPUT] Name loki Match * Host %s Port %s Labels job=fluent-bit, app=%s, namespace=%s Drop_Records_Without_Labels On Line_Format json Auto_Kubernetes_Labels On Retry_Limit 10 `, logPath, app, namespace, app, lokiHost, lokiPort, app, namespace) } // FluentbitConfigMapYAML 包装 fluent-bit.conf 为 ConfigMap YAML(部署到 namespace) func FluentbitConfigMapYAML(conf, namespace string) string { return fmt.Sprintf(`apiVersion: v1 kind: ConfigMap metadata: name: fluent-bit-config namespace: %s data: fluent-bit.conf: | %s `, namespace, indentBlock(conf, " ")) } // SidecarPatchYAML strategic merge patch:为 Deployment 注入 Fluent Bit sidecar 容器 // 生产级配置:资源限制、健康检查、安全上下文 const SidecarPatchYAML = `spec: template: spec: volumes: - name: app-logs emptyDir: {} - name: fluent-bit-config configMap: name: fluent-bit-config containers: - name: fluent-bit image: cr.fluentbit.io/fluent/fluent-bit:3.1.1 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 2020 protocol: TCP volumeMounts: - name: app-logs mountPath: /var/log/app readOnly: true - name: fluent-bit-config mountPath: /fluent-bit/etc/ resources: limits: cpu: 200m memory: 256Mi requests: cpu: 100m memory: 128Mi securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL livenessProbe: httpGet: path: / port: http initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: / port: http initialDelaySeconds: 5 periodSeconds: 5 ` // SidecarRemovePatchYAML strategic merge patch:从 Deployment 移除 Fluent Bit sidecar(卸载) const SidecarRemovePatchYAML = `spec: template: spec: containers: - name: fluent-bit $patch: delete volumes: - name: app-logs $patch: delete - name: fluent-bit-config $patch: delete ` // indentBlock 给每行加前缀(ConfigMap 嵌入配置需缩进) func indentBlock(s, pad string) string { lines := strings.Split(s, "\n") for i, l := range lines { if l != "" { lines[i] = pad + l } } return strings.Join(lines, "\n") } // parseLokiURL 从 http://host:port 提取 host 与 port(Fluent Bit OUTPUT 需要) func parseLokiURL(endpoint string) (host, port string) { u := strings.TrimSpace(endpoint) u = strings.TrimPrefix(u, "http://") u = strings.TrimPrefix(u, "https://") if i := strings.Index(u, "/"); i >= 0 { u = u[:i] } if i := strings.LastIndex(u, ":"); i >= 0 { return u[:i], u[i+1:] } return u, "3100" }