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.
opsx-loki v2 — 安全的 K8s Loki 部署平台
一个轻量级的 Gin + React + JSON 文件存储 平台,用于通过 SSH 或 kubeconfig 把 Grafana Loki 日志聚合系统部署到 Kubernetes 集群或裸金属主机上。
v2 安全增强:非 root 容器、运行时强制校验密钥、健康检查、资源限制、日志轮转、优雅关停。
面向 AI 编码代理:本仓库的
CLAUDE.md与AGENTS.md记录了架构约定与常用命令,供 Claude Code、 Cursor、Aider 等工具参考。两份文件内容一致,修改其一请同步另一份。
功能特性
- K8s 集群管理 — 通过 kubeconfig 添加集群,自动校验连通性
- SSH 主机管理 — 支持密码或密钥认证
- 存储配置 — 支持 S3、OSS、MinIO、RustFS 等后端
- 一键部署 Loki — Helm Chart / YAML 清单 / 二进制 三种模式
- 部署日志 — 实时日志流
- Web 终端 — 在浏览器中执行远程命令
- 单体二进制 — 约 13MB,零外部依赖
- 安全 — Argon2 密码哈希、AES 凭证加密、JWT 鉴权、非 root 容器
快速开始
前置条件
- Docker Engine 24.0+ 与 Docker Compose v2+
- 宿主机上装有
kubectl和helm(用于向外部集群部署 K8s 资源) openssl(用于生成密钥)
第 1 步:生成密钥(必需)
启动前必须生成所需的安全密钥:
# 必需: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 启动(推荐)
git clone https://github.com/your/opsx-loki.git
cd opsx-loki
# 设置密钥(见第 1 步)
export JWT_SECRET=$(openssl rand -hex 32)
export ENCRYPTION_KEY=$(openssl rand -hex 32)
# 启动
docker compose up -d
# 查看健康状态
docker compose ps
docker compose logs -f
# 打开 http://localhost:8080
# 默认账号:admin / admin123
第 3 步:验证健康状态
# 容器级健康检查(内部)
docker inspect --format='{{.State.Health.Status}}' opsxloki
# 期望输出:"healthy"
# 手动检查
curl http://localhost:8080/index.html
生产部署
方式 A:带密钥文件的 Docker Compose(推荐)
# 创建密钥文件
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
# 初始化 swarm(如果尚未初始化)
docker swarm init
# 创建 secrets
echo "$(openssl rand -hex 32)" | docker secret create opsxloki_jwt -
echo "$(openssl rand -hex 32)" | docker secret create opsxloki_key -
# 部署
docker stack deploy -c docker-compose.prod.yml opsxloki
安全清单
JWT_SECRET至少 32 字符且为密码学随机ENCRYPTION_KEY至少 32 字节且为密码学随机- 密钥存放在 Docker Secrets 或外部保险柜中(而非 env 文件)
.env与secrets/已加入.gitignore- 启用了
no-new-privileges:true(docker-compose 默认启用) - kubeconfig 以只读方式挂载(
:ro) - SSH 密钥以只读方式挂载(
:ro) - 已合理配置资源限制
- 健康检查通过
- 已配置日志轮转
从源码构建
# 构建前端 + 后端
bash build.sh
# 用本地脚本启动(开发)
./run.sh
开发模式(热重载)
# 终端 1 — 后端
cd backend
go run . # 监听 :8080
# 终端 2 — 前端
cd frontend
npm install
npm run dev # 打开 http://localhost:3000,API 代理到 localhost:8080
直接
go run启动后端前,必须先export JWT_SECRET与ENCRYPTION_KEY,否则backend/config/config.go会log.Fatal拒绝启动。./run.sh会自动注入开发用默认值。
测试与 lint
cd backend && go test ./... # 所有 Go 测试(utils、repository)
cd backend && go test ./utils/ -run TestName -v # 运行单个测试
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)
后端分层
请求流转: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),由单一串行化的saveWorkergoroutine 执行。常规写入用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 (读写) |
| | (opsxloki | | +---------------------+
| | 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 |
是 | 统计信息 |
健康检查
容器对静态文件路径暴露健康检查:
# 容器级(内部)
wget --spider http://localhost:8080/index.html
# 宿主级
curl http://localhost:8080/index.html
# Docker inspect
docker inspect --format='{{.State.Health}}' opsxloki
健康检查参数:
- 间隔:30 秒
- 超时:5 秒
- 启动宽限期:15 秒(允许启动)
- 重试次数:3
部署模式
| 模式 | 目标 | 说明 |
|---|---|---|
k8s-helm |
K8s 集群 | 官方 Grafana Helm Chart |
k8s-yaml |
K8s 集群 | 原生 Kubernetes 清单 |
host-binary |
Linux 主机 | 通过 SSH 部署二进制 + systemd |
存储后端
| 类型 | Endpoint 示例 |
|---|---|
| S3 | s3.amazonaws.com |
| OSS | oss-cn-hangzhou.aliyuncs.com |
| MinIO | http://minio:9000 |
| RustFS | http://rustfs:8080 |
环境变量
必需
| 变量 | 示例 | 说明 |
|---|---|---|
JWT_SECRET |
$(openssl rand -hex 32) |
JWT 签名密钥(必需,>= 32 字符) |
ENCRYPTION_KEY |
$(openssl rand -hex 32) |
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 密钥的挂载源路径 |
Docker Secrets 支持
entrypoint 支持基于文件的 Docker Secrets 注入:
| 变量 | 文件变量 | 说明 |
|---|---|---|
JWT_SECRET |
JWT_SECRET_FILE |
含 JWT 密钥的 secret 文件路径 |
ENCRYPTION_KEY |
ENCRYPTION_KEY_FILE |
含加密密钥的 secret 文件路径 |
配合 Docker Compose secrets 的示例:
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
安全
鉴权与授权
- Argon2 密码哈希
- AES-GCM 凭证加密(密钥由
ENCRYPTION_KEY派生) - JWT token 鉴权
- RBAC 基于角色的访问控制
- API 响应经过脱敏(不暴露密码、kubeconfig、密钥)
容器安全
- 非 root 用户:
lokideploy:1000 - 禁止提权:
no-new-privileges:true - 丢弃能力:除必需外丢弃所有 capabilities
- 只读挂载:kubeconfig 与 SSH 密钥只读挂载
- 资源限制:CPU 与内存受限
- 日志轮转:防止磁盘写满
密钥管理最佳实践
- 绝不 把密钥提交到版本控制
- 生产环境使用 Docker Secrets(Swarm/Kubernetes)
- 定期 轮换密钥:
openssl rand -hex 32 - 企业部署可使用密钥保险柜(HashiCorp Vault、AWS Secrets Manager 等)
- 设置文件权限:对密钥文件执行
chmod 600
资源配置
默认限制(docker-compose.yml)
| 资源 | 上限 | 预留 |
|---|---|---|
| CPU | 2.0 核 | 0.25 核 |
| 内存 | 512 MB | 64 MB |
| PIDs | 100 | - |
按规模调整
# 更大规模部署(10+ 集群)
deploy:
resources:
limits:
cpus: '4.0'
memory: 1G
reservations:
cpus: '1.0'
memory: 256M
故障排查
容器立即退出
# 检查密钥是否已设置
docker compose logs
# 期望错误信息:"Missing required security environment variables"
# 修复:
export JWT_SECRET=$(openssl rand -hex 32)
export ENCRYPTION_KEY=$(openssl rand -hex 32)
docker compose up -d
健康检查失败
# 查看容器状态
docker inspect --format='{{.State.Health}}' opsxloki
# 查看日志
docker compose logs --tail=50
kubeconfig 不可访问
# 确保宿主侧权限正确
chmod 600 ~/.kube/config
# 核对挂载路径
docker exec lokideploy ls -la /home/lokideploy/.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 文件