feat: 品牌统一 LokiStack + 全站中英 i18n + 用户批量删除 + 主题定时切换与浅色侧栏
- 品牌: 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 未翻译、词典双逗号等
This commit is contained in:
1 parent
02d1bd87a7
commit
f3733f0bfc
201 files changed
+5438
-2388
No files matched your search
@@ -1,101 +1,21 @@
|
||||
# lokistack v2 — 安全的 K8s Loki 部署平台
|
||||
# LokiStack — Loki 日志管理平台
|
||||
|
||||
一个轻量级的 **Gin + React + JSON 文件存储** 平台,用于通过 SSH 或 kubeconfig 把 Grafana Loki
|
||||
日志聚合系统部署到 Kubernetes 集群或裸金属主机上。
|
||||
一个轻量级的 **Gin + React + JSON 文件存储** 单体平台:通过 kubeconfig 或 SSH 把 Grafana Loki
|
||||
部署到 Kubernetes 集群或裸金属主机,并内置日志采集管理、LogQL 查询与告警。单体二进制约 13MB,零外部依赖。
|
||||
|
||||
> **v2 安全增强**:非 root 容器、运行时强制校验密钥、健康检查、资源限制、日志轮转、优雅关停。
|
||||
>
|
||||
> **面向 AI 编码代理**:本仓库的 `CLAUDE.md` 与 `AGENTS.md` 记录了架构约定与常用命令,供 Claude Code、
|
||||
> Cursor、Aider 等工具参考。两份文件内容一致,修改其一请同步另一份。
|
||||
> 面向 AI 编码代理:`CLAUDE.md` / `AGENTS.md` 记录了架构约定与常用命令,两份内容一致,修改其一请同步另一份。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **K8s 集群管理** — 通过 kubeconfig 添加集群,自动校验连通性
|
||||
- **SSH 主机管理** — 支持密码或密钥认证
|
||||
- **存储配置** — 支持 S3、OSS、MinIO、RustFS 等后端
|
||||
- **一键部署 Loki** — Helm Chart / YAML 清单 / 二进制 三种模式
|
||||
- **部署日志** — 实时日志流
|
||||
- **Web 终端** — 在浏览器中执行远程命令
|
||||
- **单体二进制** — 约 13MB,零外部依赖
|
||||
- **安全** — Argon2 密码哈希、AES 凭证加密、JWT 鉴权、非 root 容器
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 从 opsx-loki 升级(资源更名说明)
|
||||
|
||||
本版本把所有资源名从 `opsx-loki` / `opsxloki` 统一改为 **`lokistack`**,包括镜像、Deployment、
|
||||
Service、PVC、Secret、docker-compose 卷名。
|
||||
|
||||
### Kubernetes 用户
|
||||
|
||||
资源名已变,**不能直接 `kubectl apply` 覆盖**(会新建空 PVC)。按下面顺序迁移,**零数据丢失**:
|
||||
|
||||
```bash
|
||||
# 0) 先备份(数据只有几十 KB)
|
||||
kubectl -n log exec deploy/opsx-loki -- tar czf - -C /app/data . > /tmp/data.tar.gz
|
||||
kubectl -n log get secret opsxloki-secrets -o yaml > /tmp/secret-old.yaml
|
||||
kubectl -n log get pvc opsxloki-data -o yaml > /tmp/pvc-old.yaml
|
||||
|
||||
# 1) 停旧服务(Recreate + RWO,必须停)
|
||||
kubectl -n log scale deploy opsx-loki --replicas=0
|
||||
kubectl -n log wait --for=delete pod -l app=opsx-loki --timeout=120s
|
||||
|
||||
# 2) 复制 Secret(值不变,密钥才不会失效)
|
||||
kubectl -n log get secret opsxloki-secrets -o yaml \
|
||||
| sed 's/name: opsxloki-secrets/name: lokistack-secrets/' \
|
||||
| kubectl apply -f -
|
||||
|
||||
# 3) 只建新 PVC
|
||||
kubectl apply -f kubernetes/pvc.yaml
|
||||
kubectl -n log wait --for=jsonpath='{.status.phase}'=Bound pvc/lokistack-data --timeout=180s
|
||||
|
||||
# 4) 恢复数据到新 PVC(起临时 Pod 挂载)
|
||||
kubectl -n log run restore --image=alpine:3.20 --restart=Never \
|
||||
--overrides='{"spec":{"containers":[{"name":"r","image":"alpine:3.20","command":["sleep","600"],"volumeMounts":[{"name":"d","mountPath":"/data"}]}],"volumes":[{"name":"d","persistentVolumeClaim":{"claimName":"lokistack-data"}}]}}'
|
||||
kubectl -n log wait --for=condition=Ready pod/restore --timeout=120s
|
||||
kubectl -n log cp /tmp/data.tar.gz restore:/tmp/
|
||||
kubectl -n log exec restore -- sh -c 'cd /data && tar xzf /tmp/data.tar.gz && chown -R 1000:1000 /data'
|
||||
kubectl -n log delete po restore
|
||||
|
||||
# 5) 启动新服务
|
||||
kubectl apply -f kubernetes/deployment.yaml -f kubernetes/service.yaml -f kubernetes/service-nodeport.yaml
|
||||
kubectl -n log rollout status deploy/lokistack --timeout=240s
|
||||
|
||||
# 6) 若有 Ingress,改后端(TKE qcloud 要 NodePort)
|
||||
kubectl -n log patch ingress <你的ingress名> --type=json \
|
||||
-p='[{"op":"replace","path":"/spec/rules/0/http/paths/0/backend/service/name","value":"lokistack-nodeport"}]'
|
||||
|
||||
# 7) 确认服务正常后再删旧资源
|
||||
kubectl -n log delete deploy opsx-loki
|
||||
kubectl -n log delete svc opsx-loki opsx-loki-nodeport
|
||||
# 旧 PVC / Secret 建议保留 7 天做回滚兜底
|
||||
# kubectl -n log delete pvc opsxloki-data
|
||||
# kubectl -n log delete secret opsxloki-secrets
|
||||
```
|
||||
|
||||
### Docker Compose 用户
|
||||
|
||||
卷名 `opsxloki-data` → `lokistack-data`。**旧数据不会自动迁移**,需要手动拷:
|
||||
|
||||
```bash
|
||||
# 停服务
|
||||
docker compose down
|
||||
|
||||
# 创建新卷并迁移数据
|
||||
docker volume create lokistack-data
|
||||
docker run --rm -v opsxloki-data:/from -v lokistack-data:/to alpine \
|
||||
sh -c 'cp -av /from/. /to/ && chown -R 1000:1000 /to'
|
||||
|
||||
# 重新启动(会用新卷)
|
||||
docker compose up -d
|
||||
|
||||
# 验证数据在
|
||||
docker compose exec lokistack cat /app/data/opsxloki.json | head -c 100
|
||||
```
|
||||
|
||||
> **注意**:数据文件名仍叫 `opsxloki.json`(代码里硬编码,不改),只是目录/卷名变了。
|
||||
- **Kubernetes 集群管理** — kubeconfig/Token 接入,自动校验连通性,节点与工作负载概览
|
||||
- **SSH 主机管理** — 密码或密钥认证,主机信息探测
|
||||
- **存储配置** — S3、OSS、COS、OBS、MinIO、RustFS 等对象存储后端
|
||||
- **一键部署 Loki** — `k8s-yaml` / `host-binary` / `host-docker` 三种模式,部署日志实时查看
|
||||
- **日志采集器** — 主机 Promtail、Kubernetes DaemonSet、Fluent Bit Sidecar,采集命名空间与 app 标签可配
|
||||
- **内置日志查询** — LogQL 高亮补全、直方图、标签过滤、Live Tail,无需 Grafana
|
||||
- **日志告警** — 告警规则 + 飞书(Lark)卡片通知
|
||||
- **用户与 LDAP** — 两级用户角色(admin/普通),支持 LDAP 对接与导入
|
||||
- **安全** — Argon2 密码哈希、AES-GCM(HKDF 派生)凭证加密、JWT 鉴权、非 root 容器
|
||||
|
||||
---
|
||||
|
||||
@@ -104,200 +24,88 @@ docker compose exec lokistack cat /app/data/opsxloki.json | head -c 100
|
||||
### 前置条件
|
||||
|
||||
- Docker Engine 24.0+ 与 Docker Compose v2+
|
||||
- 宿主机上装有 `kubectl` 和 `helm`(用于向外部集群部署 K8s 资源)
|
||||
- `openssl`(用于生成密钥)
|
||||
- `openssl`(生成密钥)
|
||||
|
||||
### 第 1 步:生成密钥(必需)
|
||||
> 镜像内置全部依赖(Kubernetes 操作走 client-go 直连 API Server),宿主机**无需**安装 `kubectl` / `helm`。
|
||||
|
||||
启动前必须生成所需的安全密钥:
|
||||
### 1. 生成密钥并启动
|
||||
|
||||
```bash
|
||||
# 必需:JWT 签名密钥(建议 >= 32 字符)
|
||||
export JWT_SECRET=$(openssl rand -hex 32)
|
||||
|
||||
# 必需:用于凭证存储的 AES 加密密钥(必须 >= 32 字节)
|
||||
export ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
|
||||
# 可选:跨会话持久化
|
||||
echo "export JWT_SECRET=$JWT_SECRET" >> ~/.bashrc
|
||||
echo "export ENCRYPTION_KEY=$ENCRYPTION_KEY" >> ~/.bashrc
|
||||
```
|
||||
|
||||
> **警告**:如果未设置 `JWT_SECRET` 或 `ENCRYPTION_KEY`,容器将 **拒绝启动**,以避免不安全的默认部署。
|
||||
|
||||
### 第 2 步:用 Docker Compose 启动(推荐)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your/lokistack.git
|
||||
cd lokistack
|
||||
|
||||
# 设置密钥(见第 1 步)
|
||||
export JWT_SECRET=$(openssl rand -hex 32)
|
||||
export ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
|
||||
# 启动
|
||||
export JWT_SECRET=$(openssl rand -hex 32) # >= 32 字符
|
||||
export ENCRYPTION_KEY=$(openssl rand -hex 32) # >= 32 字节
|
||||
docker compose up -d
|
||||
|
||||
# 查看健康状态
|
||||
docker compose ps
|
||||
docker compose logs -f
|
||||
|
||||
# 打开 http://localhost:8080
|
||||
# 管理员密码见「第 4 步」(首次启动随机生成,不再是固定 admin123)
|
||||
docker compose ps # 等待 healthy
|
||||
```
|
||||
|
||||
### 第 3 步:验证健康状态
|
||||
未设置这两个变量时容器会**拒绝启动**,避免不安全的默认部署。
|
||||
|
||||
打开 `http://localhost:8080`,管理员初始密码见下一步。
|
||||
|
||||
### 2. 获取管理员初始密码
|
||||
|
||||
首次启动生成**随机密码**(不再是固定 `admin123`),同时打印到启动日志并写入数据卷
|
||||
`/app/data/initial-admin-password.txt`(权限 `0600`)。**日志只打印一次,文件才是可靠来源**:
|
||||
|
||||
```bash
|
||||
# 容器级健康检查(内部)
|
||||
docker inspect --format='{{.State.Health.Status}}' lokistack
|
||||
|
||||
# 期望输出:"healthy"
|
||||
|
||||
# 手动检查
|
||||
curl http://localhost:8080/index.html
|
||||
```
|
||||
|
||||
### 第 4 步:获取管理员初始密码
|
||||
|
||||
首次启动会生成**随机管理员密码**(不再固定 `admin123`),同时:
|
||||
|
||||
- 打印到启动日志(**仅首次**)
|
||||
- 写入数据卷 `/app/data/initial-admin-password.txt`(权限 `0600`)
|
||||
|
||||
**任选一种方式获取**(推荐方式 1 —— 日志会轮转丢失):
|
||||
|
||||
```bash
|
||||
# 方式 1(推荐):从容器内读文件 —— 日志轮转后依然可查
|
||||
# Docker Compose
|
||||
docker compose exec lokistack cat /app/data/initial-admin-password.txt
|
||||
|
||||
# 方式 2:首次启动日志(只打印一次,重启后不会再有)
|
||||
docker compose logs lokistack | grep -A6 "已生成管理员账号"
|
||||
|
||||
# 方式 3:容器已停止时也能用(不要求 running)
|
||||
docker cp lokistack:/app/data/initial-admin-password.txt ./
|
||||
|
||||
# 方式 4:直接读 named volume(宿主机 root)
|
||||
sudo cat /var/lib/docker/volumes/lokistack_lokistack-data/_data/initial-admin-password.txt
|
||||
```
|
||||
|
||||
> ⚠️ **为什么不能只靠日志**:`docker logs` 只在**首次启动**打印一次;日志轮转或容器重启后,
|
||||
> 数据库里已有用户,**不会再打印**。所以**文件才是可靠来源**。
|
||||
>
|
||||
> 💡 容器名说明:本 compose 指定了 `container_name: lokistack`,所以可直接用 `lokistack`。
|
||||
> 若你的部署没有指定,用服务名形式更通用:`docker compose exec lokistack cat ...`
|
||||
|
||||
**Kubernetes 环境(同一个文件)**:
|
||||
|
||||
```bash
|
||||
# 读文件(推荐)
|
||||
# Kubernetes
|
||||
kubectl -n log exec deploy/lokistack -- cat /app/data/initial-admin-password.txt
|
||||
|
||||
# 首次启动日志
|
||||
kubectl -n log logs deploy/lokistack | grep -A6 "已生成管理员账号"
|
||||
|
||||
# 拷到本地
|
||||
kubectl -n log cp <pod-name>:/app/data/initial-admin-password.txt ./
|
||||
```
|
||||
|
||||
**想自己指定密码(跳过随机生成)**:
|
||||
- admin 首次修改密码后,该文件**自动删除**。
|
||||
- 想自己指定密码:启动前设置环境变量 `ADMIN_INIT_PASSWORD`(设置后不生成随机密码、不写文件)。
|
||||
|
||||
### 3. 健康检查
|
||||
|
||||
```bash
|
||||
# Docker Compose:部署前设置环境变量
|
||||
export ADMIN_INIT_PASSWORD='your-strong-password'
|
||||
docker compose up -d
|
||||
|
||||
# Kubernetes:在 deployment 的 env 中加入 ADMIN_INIT_PASSWORD
|
||||
curl http://localhost:8080/api/v1/health # 应用级
|
||||
docker inspect --format='{{.State.Health.Status}}' lokistack # 容器级,期望 healthy
|
||||
```
|
||||
|
||||
**安全说明**:
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 密码文件权限 | `0600`,属主 `lokistack` |
|
||||
| 自动清理 | **admin 首次修改密码后,该文件自动删除**(见 `seed.CleanupInitPassword`) |
|
||||
| 日志内容 | 仅在首次启动打印,且容器日志由运行时轮转 |
|
||||
| 忘了密码 | 数据卷里文件仍在;若文件已删,用 `ADMIN_INIT_PASSWORD` 重新初始化(需清空数据卷) |
|
||||
| 环境变量优先 | 设置了 `ADMIN_INIT_PASSWORD` 时不生成随机密码、不写文件 |
|
||||
|
||||
---
|
||||
|
||||
## 生产部署
|
||||
|
||||
### 方式 A:带密钥文件的 Docker Compose(推荐)
|
||||
推荐使用带密钥文件与 nginx 负载均衡的 `docker-compose.prod.yml`:
|
||||
|
||||
```bash
|
||||
# 创建密钥文件
|
||||
mkdir -p secrets
|
||||
openssl rand -hex 32 > secrets/jwt_secret.txt
|
||||
openssl rand -hex 32 > secrets/encryption_key.txt
|
||||
chmod 600 secrets/*.txt
|
||||
|
||||
# 使用 docker-compose.prod.yml
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### 方式 B:Docker Swarm
|
||||
**上线前清单**:
|
||||
|
||||
```bash
|
||||
# 初始化 swarm(如果尚未初始化)
|
||||
docker swarm init
|
||||
|
||||
# 创建 secrets
|
||||
echo "$(openssl rand -hex 32)" | docker secret create lokistack_jwt -
|
||||
echo "$(openssl rand -hex 32)" | docker secret create lokistack_key -
|
||||
|
||||
# 部署
|
||||
docker stack deploy -c docker-compose.prod.yml lokistack
|
||||
```
|
||||
|
||||
### 安全清单
|
||||
|
||||
- [ ] `JWT_SECRET` 至少 32 字符且为密码学随机
|
||||
- [ ] `ENCRYPTION_KEY` 至少 32 字节且为密码学随机
|
||||
- [ ] 密钥存放在 Docker Secrets 或外部保险柜中(而非 env 文件)
|
||||
- [ ] 两个密钥均为密码学随机、>= 32 字节,存于 Docker Secrets 或外部保险柜(而非 env 文件)
|
||||
- [ ] `.env` 与 `secrets/` 已加入 `.gitignore`
|
||||
- [ ] 启用了 `no-new-privileges:true`(docker-compose 默认启用)
|
||||
- [ ] kubeconfig 以只读方式挂载(`:ro`)
|
||||
- [ ] SSH 密钥以只读方式挂载(`:ro`)
|
||||
- [ ] 已合理配置资源限制
|
||||
- [ ] 健康检查通过
|
||||
- [ ] 已配置日志轮转
|
||||
- [ ] kubeconfig 与 SSH 密钥以只读方式挂载(`:ro`)
|
||||
- [ ] 健康检查通过、日志轮转与资源限制已配置(compose 默认已含)
|
||||
|
||||
---
|
||||
|
||||
## 从源码构建
|
||||
## 从源码构建与开发
|
||||
|
||||
```bash
|
||||
# 构建前端 + 后端
|
||||
bash build.sh
|
||||
|
||||
# 用本地脚本启动(开发)
|
||||
./run.sh
|
||||
bash build.sh # 构建单体二进制 frontend/dist + backend/bin/lokistack
|
||||
./run.sh # 本地一键起前后端(自动注入开发用密钥)
|
||||
```
|
||||
|
||||
### 开发模式(热重载)
|
||||
开发模式(热重载,两个终端):
|
||||
|
||||
```bash
|
||||
# 终端 1 — 后端
|
||||
cd backend
|
||||
go run . # 监听 :8080
|
||||
|
||||
# 终端 2 — 前端
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev # 打开 http://localhost:3000,API 代理到 localhost:8080
|
||||
cd backend && go run . # :8080;直接 go run 需先 export 两个密钥,否则拒绝启动
|
||||
cd frontend && npm install && npm run dev # :3000,/api 代理到 :8080
|
||||
```
|
||||
|
||||
> 直接 `go run` 启动后端前,必须先 `export JWT_SECRET` 与 `ENCRYPTION_KEY`,否则
|
||||
> `backend/config/config.go` 会 `log.Fatal` 拒绝启动。`./run.sh` 会自动注入开发用默认值。
|
||||
|
||||
### 测试与 lint
|
||||
测试与 lint:
|
||||
|
||||
```bash
|
||||
cd backend && go test ./... # 所有 Go 测试(utils、repository)
|
||||
cd backend && go test ./utils/ -run TestName -v # 运行单个测试
|
||||
cd frontend && npm run lint # ESLint
|
||||
cd backend && go test ./...
|
||||
cd frontend && npm run lint
|
||||
```
|
||||
|
||||
---
|
||||
@@ -305,131 +113,32 @@ cd frontend && npm run lint # ESLint
|
||||
## 架构
|
||||
|
||||
```
|
||||
前端(React + TypeScript + Tailwind CSS) [开发 :3000 / 生产 :8080]
|
||||
|
|
||||
| HTTP/REST + JWT Bearer Token
|
||||
v
|
||||
后端(Go + Gin) [:8080]
|
||||
|
|
||||
+---> JSON 文件数据库(data/opsxloki.json(兼容加载旧 lokideploy.json))
|
||||
+---> K8s CLI(kubectl / helm)
|
||||
+---> SSH 客户端(golang.org/x/crypto/ssh)
|
||||
+---> Loki 部署器(Helm Chart / YAML / Binary)
|
||||
前端(React 19 + TypeScript + Tailwind) [开发 :3000 / 生产由 Go 直接服务 :8080]
|
||||
| HTTP/REST + JWT Bearer
|
||||
后端(Go + Gin)[:8080]
|
||||
+---> JSON 文件数据库($DATA_DIR/lokistack.json,自动兼容历史数据文件)
|
||||
+---> client-go 直连 Kubernetes API Server(kubeconfig 认证)
|
||||
+---> SSH(golang.org/x/crypto/ssh)
|
||||
+---> Loki 部署器(YAML 清单 / 二进制 + systemd / Docker)
|
||||
```
|
||||
|
||||
### 后端分层
|
||||
- 请求流转:**router → middleware(JWT)→ controller → service → repository(JsonDB)**。
|
||||
- 数据层为单 JSON 文件 + 内存结构:防抖原子写(临时文件 → fsync → rename)、优雅关停按序落盘,
|
||||
因此**只能单实例运行**。新增数据库代码写到 `repository`(旧副本 `models/db.go` 待清理)。
|
||||
- 所有响应为 `{ code, message, data }`;**禁止**直接返回原始 model,必须经 `SanitizedJSON` 脱敏
|
||||
(剔除密码、kubeconfig、私钥、token)。
|
||||
- 密码 Argon2 哈希;凭证 AES-GCM 加密,密钥由 `ENCRYPTION_KEY` 经 HKDF 派生(>= 32 字节)。
|
||||
- 更细的分层与约定见 `CLAUDE.md` / `AGENTS.md` 与 `docs/web-ui-guide/`。
|
||||
|
||||
请求流转:**router → middleware(JWT)→ controller → service → repository(JsonDB)**
|
||||
|
||||
- `config/` — `GetConfig()` 单例,读取环境变量,密钥缺失或长度不足时直接 `log.Fatal`。
|
||||
- `routers/router.go` — 所有路由注册在 `/api/v1`;注册 SPA 静态资源并用 `NoRoute` 回退到
|
||||
`index.html` 支持前端路由。
|
||||
- `controllers/` — 薄 Gin handler,每资源一个文件;绑定/校验入参、调用 service、用
|
||||
`models.SanitizedJSON(...)` 包装返回。
|
||||
- `services/` — 业务逻辑。部署流水线:
|
||||
`DeploymentService.Deploy()` → `LokiDeployer.Deploy()`,按模式(`k8s-helm` / `k8s-yaml` /
|
||||
`host-binary`)分发到 `kubectl`/`helm` 或 SSH。另有 `K8sClient`、`ssh`、`storage`、
|
||||
`cluster_info` 与较新的 **collector(日志采集器)** 模块。
|
||||
- `middleware/jwt.go` — `JWTAuth()`、`AdminAuth()`。
|
||||
- `utils/` — `response.go`(响应封装)、`crypto.go`(Argon2 + AES-GCM)、`jwt.go`。
|
||||
- `seed/` — 数据库为空时写入默认 admin 用户。
|
||||
|
||||
### 数据层 — JSON 文件数据库
|
||||
|
||||
持久化是 **单个 JSON 文件** `$DATA_DIR/opsxloki.json(兼容加载旧 lokideploy.json)`(默认 `./data/`),**没有 SQL 数据库**。
|
||||
`repository.JsonDB` 是与文件对应的内存结构:
|
||||
|
||||
- 写操作由 `Lock()/RLock()` 保护,`RequestSave()` 触发 **防抖(500ms)原子写**(临时文件 →
|
||||
`fsync` → rename),由单一串行化的 `saveWorker` goroutine 执行。常规写入用 `RequestSave()`,
|
||||
不要在 worker 可能运行时直接 `Save()`。
|
||||
- 优雅关停顺序很关键:停防抖定时器 → 关闭 worker 并等待 → 最后一次 `doSave`。顺序颠倒会丢更新。
|
||||
- 因此应用 **只能单实例运行**,不可水平扩展。
|
||||
- `NextID(table)` 为各集合分配单调递增的整数 ID。
|
||||
|
||||
> 注:`repository/db.go` 是当前在用的实现;`models/db.go` 是正在迁移、尚未清理的旧副本(仍会编译,
|
||||
> 但已无人调用)。新增数据库代码请写到 `repository`。
|
||||
|
||||
### 响应封装与脱敏
|
||||
|
||||
- 所有 API 响应为 `{ code, message, data }`,`code == 0` 表示成功。
|
||||
- **不要直接返回原始 model**,必须用 `models.SanitizedJSON(...)` 包装以剔除密码、kubeconfig、
|
||||
私钥、token 等敏感字段。
|
||||
|
||||
### 凭证安全
|
||||
|
||||
- 密码用 **Argon2** 哈希;凭证用 **AES-GCM** 加密,AES 密钥由 `ENCRYPTION_KEY` 经 **HKDF** 派生。
|
||||
- `ENCRYPTION_KEY` 必须 **>= 32 字节**(早期文档写 16,现在会导致启动失败)。
|
||||
|
||||
### 容器安全模型
|
||||
|
||||
```
|
||||
+-------------------+ +---------------------+
|
||||
| 宿主内核 | | 宿主文件系统 |
|
||||
| | | ~/.kube (只读) |
|
||||
| +-------------+ | | ~/.ssh (只读) |
|
||||
| | 容器 | | | ./data (读写) |
|
||||
| | (lokistack | | +---------------------+
|
||||
| | UID:1000) | |
|
||||
| | no-new-privs| |
|
||||
| | cap_drop:ALL| |
|
||||
| +-------------+ |
|
||||
+-------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 接口
|
||||
|
||||
Base URL:`/api/v1`
|
||||
|
||||
| 方法 | 接口 | 鉴权 | 说明 |
|
||||
|------|------|------|------|
|
||||
| POST | `/auth/login` | 否 | 登录,获取 JWT token |
|
||||
| GET | `/auth/user` | 是 | 当前用户信息 |
|
||||
| POST | `/auth/password` | 是 | 修改密码 |
|
||||
| GET/POST | `/clusters` | 是 | 集群增删改查 |
|
||||
| POST | `/clusters/:id/validate` | 是 | 校验集群连通性 |
|
||||
| GET/POST | `/hosts` | 是 | 主机增删改查 |
|
||||
| POST | `/hosts/:id/execute` | 是 | 远程执行命令 |
|
||||
| GET/POST | `/storages` | 是 | 存储配置增删改查 |
|
||||
| GET/POST | `/deployments` | 是 | 部署增删改查 |
|
||||
| POST | `/deployments/:id/deploy` | 是 | 启动部署 |
|
||||
| POST | `/deployments/:id/stop` | 是 | 停止部署 |
|
||||
| GET | `/deployments/:id/logs` | 是 | 部署日志 |
|
||||
| GET | `/deployments/stats` | 是 | 统计信息 |
|
||||
|
||||
### 健康检查
|
||||
|
||||
容器对静态文件路径暴露健康检查:
|
||||
|
||||
```bash
|
||||
# 容器级(内部)
|
||||
wget --spider http://localhost:8080/index.html
|
||||
|
||||
# 宿主级
|
||||
curl http://localhost:8080/index.html
|
||||
|
||||
# Docker inspect
|
||||
docker inspect --format='{{.State.Health}}' lokistack
|
||||
```
|
||||
|
||||
健康检查参数:
|
||||
- **间隔**:30 秒
|
||||
- **超时**:5 秒
|
||||
- **启动宽限期**:15 秒(允许启动)
|
||||
- **重试次数**:3
|
||||
|
||||
---
|
||||
|
||||
## 部署模式
|
||||
### 部署模式
|
||||
|
||||
| 模式 | 目标 | 说明 |
|
||||
|------|------|------|
|
||||
| `k8s-helm` | K8s 集群 | 官方 Grafana Helm Chart |
|
||||
| `k8s-yaml` | K8s 集群 | 原生 Kubernetes 清单 |
|
||||
| `host-binary` | Linux 主机 | 通过 SSH 部署二进制 + systemd |
|
||||
| `k8s-yaml` | Kubernetes 集群 | 原生清单(StatefulSet/ConfigMap/Service,PVC 持久化) |
|
||||
| `host-binary` | Linux 主机 | SSH 部署二进制 + systemd |
|
||||
| `host-docker` | Linux 主机 | SSH 拉起 Loki 容器,配置与数据落在主机固定目录 |
|
||||
|
||||
## 存储后端
|
||||
### 存储后端
|
||||
|
||||
| 类型 | Endpoint 示例 |
|
||||
|------|---------------|
|
||||
@@ -440,164 +149,70 @@ docker inspect --format='{{.State.Health}}' lokistack
|
||||
|
||||
---
|
||||
|
||||
## API 接口
|
||||
|
||||
Base URL `/api/v1`,除登录/健康外均需 JWT。代表性接口(完整列表见 `backend/routers/router.go`):
|
||||
|
||||
| 方法 | 接口 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/auth/login` | 登录,返回 access + refresh token |
|
||||
| GET/POST | `/clusters`、`/hosts`、`/storages` | 集群 / 主机 / 存储配置管理(含 `:id` 增删改) |
|
||||
| POST | `/clusters/:id/validate`、`/hosts/:id/validate` | 连通性校验 |
|
||||
| GET/POST | `/deployments`、`/collectors` | Loki 部署与采集策略管理 |
|
||||
| POST | `/deployments/:id/deploy|stop|restart` | 部署生命周期 |
|
||||
| POST | `/deployments/:id/retention` | 修改日志保留期并应用 |
|
||||
| GET | `/deployments/:id/logs`、`/deployments/stats` | 部署日志 / 统计 |
|
||||
| POST | `/logs/query|histogram|labels` | 内置日志查询(代理关联的 Loki) |
|
||||
| GET/POST | `/users`、`/settings/ldap/*` | 用户管理与 LDAP 对接(仅 admin) |
|
||||
|
||||
---
|
||||
|
||||
## 环境变量
|
||||
|
||||
### 必需
|
||||
|
||||
| 变量 | 示例 | 说明 |
|
||||
|------|------|------|
|
||||
| `JWT_SECRET` | `$(openssl rand -hex 32)` | JWT 签名密钥(**必需**,>= 32 字符) |
|
||||
| `ENCRYPTION_KEY` | `$(openssl rand -hex 32)` | AES 凭证加密密钥(**必需**,>= 32 字节) |
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `JWT_SECRET` | JWT 签名密钥,>= 32 字符 |
|
||||
| `ENCRYPTION_KEY` | AES 凭证加密密钥,>= 32 字节 |
|
||||
|
||||
### 可选
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `SERVER_PORT` | `8080` | HTTP 服务端口 |
|
||||
| `DATA_DIR` | `/app/data` | 数据存储目录 |
|
||||
| `KUBECONFIG` | `/home/lokideploy/.kube/config` | kubeconfig 文件路径 |
|
||||
| `KUBECONFIG_PATH` | `~/.kube` | 宿主机上 kubeconfig 的挂载源路径 |
|
||||
| `SSH_KEY_PATH` | `~/.ssh` | 宿主机上 SSH 密钥的挂载源路径 |
|
||||
| `SERVER_PORT` | `8080` | HTTP 端口 |
|
||||
| `DATA_DIR` | `/app/data` | 数据目录 |
|
||||
| `ADMIN_INIT_PASSWORD` | 随机生成 | 指定管理员初始密码 |
|
||||
| `KUBECONFIG` | `/home/lokistack/.kube/config` | 容器内 kubeconfig 路径 |
|
||||
|
||||
### Docker Secrets 支持
|
||||
|
||||
entrypoint 支持基于文件的 Docker Secrets 注入:
|
||||
|
||||
| 变量 | 文件变量 | 说明 |
|
||||
|------|----------|------|
|
||||
| `JWT_SECRET` | `JWT_SECRET_FILE` | 含 JWT 密钥的 secret 文件路径 |
|
||||
| `ENCRYPTION_KEY` | `ENCRYPTION_KEY_FILE` | 含加密密钥的 secret 文件路径 |
|
||||
|
||||
配合 Docker Compose secrets 的示例:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
lokideploy:
|
||||
environment:
|
||||
JWT_SECRET_FILE: /run/secrets/jwt_secret
|
||||
ENCRYPTION_KEY_FILE: /run/secrets/encryption_key
|
||||
secrets:
|
||||
- jwt_secret
|
||||
- encryption_key
|
||||
|
||||
secrets:
|
||||
jwt_secret:
|
||||
file: ./secrets/jwt_secret.txt
|
||||
encryption_key:
|
||||
file: ./secrets/encryption_key.txt
|
||||
```
|
||||
支持 Docker Secrets 文件注入:`JWT_SECRET_FILE` / `ENCRYPTION_KEY_FILE` 指向 secret 文件路径即可
|
||||
(用法见 `docker-compose.prod.yml` 与 `docker-entrypoint.sh`)。
|
||||
|
||||
---
|
||||
|
||||
## 安全
|
||||
|
||||
### 鉴权与授权
|
||||
|
||||
- **Argon2** 密码哈希
|
||||
- **AES-GCM** 凭证加密(密钥由 `ENCRYPTION_KEY` 派生)
|
||||
- **JWT** token 鉴权
|
||||
- **RBAC** 基于角色的访问控制
|
||||
- API 响应经过脱敏(不暴露密码、kubeconfig、密钥)
|
||||
|
||||
### 容器安全
|
||||
|
||||
- **非 root 用户**:`lokideploy:1000`
|
||||
- **禁止提权**:`no-new-privileges:true`
|
||||
- **丢弃能力**:除必需外丢弃所有 capabilities
|
||||
- **只读挂载**:kubeconfig 与 SSH 密钥只读挂载
|
||||
- **资源限制**:CPU 与内存受限
|
||||
- **日志轮转**:防止磁盘写满
|
||||
|
||||
### 密钥管理最佳实践
|
||||
|
||||
1. **绝不** 把密钥提交到版本控制
|
||||
2. 生产环境使用 **Docker Secrets**(Swarm/Kubernetes)
|
||||
3. 定期 **轮换密钥**:`openssl rand -hex 32`
|
||||
4. 企业部署可使用密钥保险柜(HashiCorp Vault、AWS Secrets Manager 等)
|
||||
5. 设置文件权限:对密钥文件执行 `chmod 600`
|
||||
|
||||
---
|
||||
|
||||
## 资源配置
|
||||
|
||||
### 默认限制(docker-compose.yml)
|
||||
|
||||
| 资源 | 上限 | 预留 |
|
||||
|------|------|------|
|
||||
| CPU | 2.0 核 | 0.25 核 |
|
||||
| 内存 | 512 MB | 64 MB |
|
||||
| PIDs | 100 | - |
|
||||
|
||||
### 按规模调整
|
||||
|
||||
```yaml
|
||||
# 更大规模部署(10+ 集群)
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '4.0'
|
||||
memory: 1G
|
||||
reservations:
|
||||
cpus: '1.0'
|
||||
memory: 256M
|
||||
```
|
||||
- **鉴权**:JWT(access 2h + refresh 7 天轮换);改密/重置密码递增 `TokenVersion`,旧 token 立即失效。
|
||||
- **用户体系**:两级角色(admin/普通),用户管理仅 admin;登录接口带 IP 限流。
|
||||
- **容器**:非 root 用户(`lokistack:1000`)、`no-new-privileges`、丢弃全部多余 capabilities、
|
||||
资源限制(CPU 2 核 / 内存 512MB / PIDs 100 上限)与日志轮转。
|
||||
- **密钥**:绝不提交版本控制;生产用 Docker Secrets 或密钥保险柜;轮换用 `openssl rand -hex 32`。
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 容器立即退出
|
||||
**容器立即退出** — 多为密钥缺失。`docker compose logs` 应有
|
||||
`Missing required security environment variables`;设置两个密钥后重启。
|
||||
|
||||
```bash
|
||||
# 检查密钥是否已设置
|
||||
docker compose logs
|
||||
# 期望错误信息:"Missing required security environment variables"
|
||||
**健康检查失败** — `docker inspect --format='{{.State.Health}}' lokistack` 查状态,
|
||||
`docker compose logs --tail=50` 查原因。
|
||||
|
||||
# 修复:
|
||||
export JWT_SECRET=$(openssl rand -hex 32)
|
||||
export ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 健康检查失败
|
||||
|
||||
```bash
|
||||
# 查看容器状态
|
||||
docker inspect --format='{{.State.Health}}' lokistack
|
||||
|
||||
# 查看日志
|
||||
docker compose logs --tail=50
|
||||
```
|
||||
|
||||
### kubeconfig 不可访问
|
||||
|
||||
```bash
|
||||
# 确保宿主侧权限正确
|
||||
chmod 600 ~/.kube/config
|
||||
|
||||
# 核对挂载路径
|
||||
docker exec lokideploy ls -la /home/lokideploy/.kube/
|
||||
```
|
||||
**kubeconfig 不可访问** — 核对宿主侧权限(`chmod 600 ~/.kube/config`)与挂载路径:
|
||||
`docker exec lokistack ls -la /home/lokistack/.kube/`。
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v2.0.0
|
||||
|
||||
- **安全**:非 root 容器用户(`lokideploy:1000`)
|
||||
- **安全**:运行时强制校验 `JWT_SECRET`/`ENCRYPTION_KEY`
|
||||
- **安全**:`no-new-privileges` 与 capabilities 丢弃
|
||||
- **安全**:kubectl/helm 的 SHA256 校验和验证
|
||||
- **运维**:健康检查端点
|
||||
- **运维**:资源限制与预留
|
||||
- **运维**:日志轮转(json-file 驱动)
|
||||
- **运维**:优雅关停(SIGTERM)
|
||||
- **运维**:Docker Secrets 支持
|
||||
- **运维**:带 nginx 负载均衡的生产 compose 文件
|
||||
Reference in new issue
Block a user