- 品牌: 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 未翻译、词典双逗号等
250 lines
5.7 KiB
Go
250 lines
5.7 KiB
Go
// LokiDeployer — k8s-yaml 模式 manifest 生成(StatefulSet/PVC/ConfigMap/Service)。
|
||
package services
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"fmt"
|
||
|
||
"lokistack/pkg/crypto"
|
||
)
|
||
|
||
func (d *LokiDeployer) generateManifest(ns string) string {
|
||
version := d.effectiveVersion()
|
||
image := ResolveImageFor("loki", "grafana/loki", version)
|
||
replicas := d.deployment.Replicas
|
||
if replicas < 1 {
|
||
replicas = 1
|
||
}
|
||
storageSize := d.deployment.StorageSize
|
||
if storageSize == "" {
|
||
storageSize = "20Gi"
|
||
}
|
||
|
||
cpu := d.deployment.ResourcesCPU
|
||
mem := d.deployment.ResourcesMem
|
||
if cpu == "" {
|
||
cpu = "1000m"
|
||
}
|
||
if mem == "" {
|
||
mem = "1Gi"
|
||
}
|
||
|
||
configData := d.generateLokiConfig()
|
||
secretManifest := ""
|
||
configExpandArg := ""
|
||
storageEnvBlock := ""
|
||
if sc := d.deployment.StorageConfig; sc != nil {
|
||
switch sc.StorageType {
|
||
// 注意:必须包含 cos / obs —— 腾讯云 COS、华为 OBS 同样走 S3 兼容协议,
|
||
// 也需要生成 loki-storage-credentials Secret、注入 LOKI_STORAGE_* 环境变量、
|
||
// 并给 Loki 加 -config.expand-env=true,否则配置里的 ${LOKI_STORAGE_ACCESS_KEY}
|
||
// 无法展开 → AK 为空 → 对象存储 403 InvalidAccessKeyId。
|
||
case "s3", "oss", "cos", "obs", "minio", "azure":
|
||
secretManifest = fmt.Sprintf(`---
|
||
apiVersion: v1
|
||
kind: Secret
|
||
metadata:
|
||
name: loki-storage-credentials
|
||
namespace: %s
|
||
type: Opaque
|
||
data:
|
||
accessKey: %s
|
||
secretKey: %s`, ns,
|
||
base64.StdEncoding.EncodeToString([]byte(crypto.DecryptField(sc.AccessKey))),
|
||
base64.StdEncoding.EncodeToString([]byte(crypto.DecryptField(sc.SecretKey))))
|
||
configExpandArg = "\n - -config.expand-env=true"
|
||
storageEnvBlock = `
|
||
env:
|
||
- name: LOKI_STORAGE_ACCESS_KEY
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: loki-storage-credentials
|
||
key: accessKey
|
||
- name: LOKI_STORAGE_SECRET_KEY
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: loki-storage-credentials
|
||
key: secretKey`
|
||
}
|
||
}
|
||
// ConfigMap/Secret 更新本身不会重建 Pod。把内容摘要写入 Pod 模板以触发滚动更新。
|
||
configHash := sha256.Sum256([]byte(configData + "\n" + secretManifest))
|
||
|
||
manifest := fmt.Sprintf(`apiVersion: v1
|
||
kind: Namespace
|
||
metadata:
|
||
name: %s
|
||
---
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: loki-config
|
||
namespace: %s
|
||
data:
|
||
loki.yaml: |
|
||
%s
|
||
%s
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: loki
|
||
namespace: %s
|
||
labels:
|
||
app: loki
|
||
spec:
|
||
type: ClusterIP
|
||
ports:
|
||
- name: http
|
||
port: 3100
|
||
targetPort: 3100
|
||
protocol: TCP
|
||
- name: grpc
|
||
port: 9095
|
||
targetPort: 9095
|
||
protocol: TCP
|
||
selector:
|
||
app: loki
|
||
---
|
||
apiVersion: v1
|
||
kind: PersistentVolumeClaim
|
||
metadata:
|
||
name: loki-data
|
||
namespace: %s
|
||
spec:
|
||
accessModes:
|
||
- ReadWriteOnce
|
||
resources:
|
||
requests:
|
||
storage: %s
|
||
---
|
||
apiVersion: apps/v1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: loki
|
||
namespace: %s
|
||
labels:
|
||
app: loki
|
||
spec:
|
||
serviceName: loki
|
||
replicas: %d
|
||
selector:
|
||
matchLabels:
|
||
app: loki
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: loki
|
||
annotations:
|
||
checksum/loki-config: "%x"
|
||
spec:
|
||
securityContext:
|
||
runAsUser: 0
|
||
runAsGroup: 0
|
||
fsGroup: 0
|
||
containers:
|
||
- name: loki
|
||
image: %s
|
||
imagePullPolicy: IfNotPresent
|
||
args:
|
||
- -config.file=/etc/loki/loki.yaml%s%s
|
||
ports:
|
||
- name: http
|
||
containerPort: 3100
|
||
protocol: TCP
|
||
- name: grpc
|
||
containerPort: 9095
|
||
protocol: TCP
|
||
resources:
|
||
limits:
|
||
cpu: "%s"
|
||
memory: "%s"
|
||
requests:
|
||
cpu: "500m"
|
||
memory: "512Mi"
|
||
securityContext:
|
||
allowPrivilegeEscalation: false
|
||
readOnlyRootFilesystem: false
|
||
capabilities:
|
||
drop:
|
||
- ALL
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /ready
|
||
port: http
|
||
initialDelaySeconds: 30
|
||
periodSeconds: 10
|
||
timeoutSeconds: 5
|
||
failureThreshold: 6
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /ready
|
||
port: http
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 5
|
||
timeoutSeconds: 3
|
||
failureThreshold: 3
|
||
startupProbe:
|
||
httpGet:
|
||
path: /ready
|
||
port: http
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 10
|
||
timeoutSeconds: 5
|
||
failureThreshold: 30
|
||
volumeMounts:
|
||
- name: config
|
||
mountPath: /etc/loki
|
||
readOnly: true
|
||
- name: data
|
||
mountPath: /var/lib/loki
|
||
- name: wal
|
||
mountPath: /var/lib/loki/wal
|
||
volumes:
|
||
- name: config
|
||
configMap:
|
||
name: loki-config
|
||
- name: data
|
||
persistentVolumeClaim:
|
||
claimName: loki-data
|
||
- name: wal
|
||
emptyDir: {}
|
||
`, ns, ns, indentYAML(configData, 4), secretManifest, ns, ns, storageSize, ns, replicas, configHash, image, configExpandArg, storageEnvBlock, cpu, mem)
|
||
|
||
// 多副本:追加 Headless Service(clusterIP: None + selector app:loki),
|
||
// 使 memberlist 的 join_members (loki-headless.<ns>.svc.cluster.local) 能解析到所有 Pod IP。
|
||
if replicas >= 2 {
|
||
manifest += fmt.Sprintf(`
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: loki-headless
|
||
namespace: %s
|
||
labels:
|
||
app: loki
|
||
spec:
|
||
clusterIP: None
|
||
ports:
|
||
- name: http
|
||
port: 3100
|
||
targetPort: 3100
|
||
protocol: TCP
|
||
- name: grpc
|
||
port: 9095
|
||
targetPort: 9095
|
||
protocol: TCP
|
||
- name: memberlist
|
||
port: 7946
|
||
targetPort: 7946
|
||
protocol: TCP
|
||
selector:
|
||
app: loki
|
||
`, ns)
|
||
}
|
||
return manifest
|
||
}
|
||
|
||
// ==================== Loki 本地配置生成 (text/template) ====================
|