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

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),由单一串行化的 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 (读写)      |
|  | (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 与内存受限
  • 日志轮转:防止磁盘写满

密钥管理最佳实践

  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 -

按规模调整

# 更大规模部署(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 文件
S
Description
No description provided
Readme
3.7 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 50.1%
Go 44.9%
Shell 3.5%
HTML 0.4%
CSS 0.4%
Other 0.7%