feat: K8s 部署清单、Token 接入加固与日志/设置页优化

- kubernetes/: 30Gi PVC、单副本 Recreate、Secret/Service/NodePort/Ingress 清单与部署文档
- 数据安全: kubeconfig/token 统一加密存储,启动迁移历史明文,DB 0600
- Token 接入: 命名空间级最小权限提示、长期 Token、自签证书 TLS 兼容
- 部署: 命名空间级 Token 下跳过 Namespace patch/枚举,节点信息容错
- 日志页: namespace → app → pod 选择顺序与作用域收窄
- 设置页: 拆分为通用/告警/LDAP/镜像源四个 tab
- 弹框: 统一 max-h 滚动与长命令换行,修复内容溢出
This commit is contained in:
logan committed 2026-10-25 23:44:07 +08:00
1 parent 2d73661dd1
commit 9f7c522157
50 files changed
+1346 -149

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
# 部署说明
本文档描述 `opsx-loki` 的两种部署方式,域名与 IP 均为脱敏示例:
- `opsx-loki.xxx.io` —— 平台 UI 域名
- `log.xxx.io` —— Loki 查询域名(Ingress)
- `192.168.x.x` —— 内网地址占位
## 架构
```
浏览器
│ HTTPS(WAF / SLB 终止)
▼
边缘 WAF ──► 主机 nginx(192.168.x.x)
│
├─ 主机部署:proxy_pass http://127.0.0.1:8081
└─ K8s 部署:proxy_pass http://<NodeIP>:30080
│
▼
Service opsx-loki:8080
│
▼
Deployment opsx-loki(1 副本)
│
┌────────────────┴────────────────┐
▼ ▼
PVC /app/data Loki Service log/loki:3100
(JSON DB、kubeconfig 加密) (日志存储:S3/OSS/COS/OBS)
```
## 两种方式的取舍
| | 主机 Docker | Kubernetes |
|---|---|---|
| 适用 | 单机、快速验证 | 多环境、统一调度 |
| 数据 | 本地卷 `/app/data` | PVC(默认 StorageClass) |
| 域名 | 主机 nginx 反代 `127.0.0.1:8081` | NodePort + nginx,或 ALB Ingress |
| 约束 | 单实例 | **1 副本 + Recreate**(JSON DB 单写者) |
## 文档索引
| 文档 | 内容 |
|---|---|
| [host-nginx.md](host-nginx.md) | 主机部署的 nginx 反代配置(含 WAF 真实 IP、白名单) |
| [kubernetes.md](kubernetes.md) | K8s 清单、Ingress(ALB)、PVC、Secret、升级 |
+69
View File
@@ -0,0 +1,69 @@
# 主机部署:nginx 反代
适用于 `docker compose` 单机部署。平台监听 `127.0.0.1:8081`(或容器映射端口),由主机 nginx 对外提供 HTTP/HTTPS。
> 域名、IP 已脱敏;实际使用时替换为你的值。
## nginx vhost
`/etc/nginx/conf.d/opsx-loki.xxx.io.conf`:
```nginx
server {
listen 80;
server_name opsx-loki.xxx.io;
access_log /var/log/nginx/opsx-loki.xxx.io_access.log main;
error_log /var/log/nginx/opsx-loki.xxx.io_error.log;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
client_max_body_size 100m;
}
}
```
## 前置 WAF / SLB 时(可选)
WAF 会把源 IP 改写为内网地址,需要在 `nginx.conf` 的 `http {}` 中还原真实客户端 IP,否则白名单会失效:
```nginx
set_real_ip_from 192.168.x.x; # WAF 回源地址
real_ip_header X-Forwarded-For;
```
## 内网白名单(可选)
建议统一放在 `/etc/nginx/nginx.conf` 的 `http {}`,不要散落在各 vhost(vhost 内一旦出现 `allow/deny` 会切断继承):
```nginx
allow 192.168.0.0/16;
allow 203.0.113.10; # 办公/跳板机出口
deny all;
```
公网站点单独在其 vhost 内写 `allow all;`。
## 生效
```bash
nginx -t && nginx -s reload
```
## 验证
```bash
curl -I -H "Host: opsx-loki.xxx.io" http://127.0.0.1/
curl -s -X POST http://opsx-loki.xxx.io/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<你的密码>"}'
```
+238
View File
@@ -0,0 +1,238 @@
# Kubernetes 部署
清单在仓库 `kubernetes/` 目录,域名与 IP 已脱敏。
## 1. 命名空间与密钥
```bash
kubectl create namespace log # 已存在可跳过
kubectl -n log create secret generic opsxloki-secrets \
--from-literal=JWT_SECRET="$(openssl rand -hex 32)" \
--from-literal=ENCRYPTION_KEY="$(openssl rand -hex 32)"
```
> `ENCRYPTION_KEY` 必须固定保存:DB 里的 kubeconfig、对象存储 AK/SK、SSH 私钥都用它加密,丢失后无法解密。
> 从旧实例迁移时,必须使用**原实例相同的** `JWT_SECRET` / `ENCRYPTION_KEY`。
## 2. 数据卷
`kubernetes/pvc.yaml`:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: opsxloki-data
namespace: log
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 30Gi
```
不写 `storageClassName`,使用集群默认 StorageClass。没有默认 SC 时按云填写:
| 云 | StorageClass |
|---|---|
| 阿里云 ACK | `alicloud-disk-essd` |
| AWS EKS | `gp3` |
| 腾讯云 TKE | `cbs` |
| 华为云 CCE | `csi-disk` |
## 3. 工作负载
```bash
kubectl apply -f kubernetes/namespace.yaml \
-f kubernetes/pvc.yaml \
-f kubernetes/deployment.yaml \
-f kubernetes/service.yaml
```
关键约束(`deployment.yaml` 已内置):
- `replicas: 1`:JSON DB 单写者,多副本会互相覆盖。
- `strategy: Recreate`:滚动更新会同时挂同一个 RWO PVC。
- `DATA_DIR=/app/data`,挂 PVC;`runAsUser/fsGroup: 1000`。
- 镜像 `lghost/opsxloki:latest`,`imagePullPolicy: Always`。
- 探针:`/api/v1/health`。
## 4. Service
```yaml
apiVersion: v1
kind: Service
metadata:
name: opsx-loki
namespace: log
spec:
type: ClusterIP
selector:
app: opsx-loki
ports:
- name: http
port: 8080
targetPort: http
```
需要主机 nginx 反代时,额外创建 NodePort(`kubernetes/service-nodeport.yaml`,30080)。
## 5. Ingress(ALB)
`log.xxx.io` 指向 Loki/平台入口,使用阿里云 ALB IngressClass:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: loki.xxx.io
namespace: log
labels:
app: opsx-loki
spec:
ingressClassName: alb
rules:
- host: log.xxx.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: opsx-loki
port:
number: 8080
```
```bash
kubectl apply -f kubernetes/ingress.yaml
kubectl -n log get ingress
```
ALB 会分配一个地址(如 `alb-xxxx.<region>.alb.aliyuncsslbintl.com`),把域名的 CNAME 指过去即可。
> 其他云把 `ingressClassName` 换成对应实现:TKE `qcloud`、CCE `nginx`/`cce`、自建 `nginx`。
## 6. 数据迁移(从主机/旧实例)
JSON DB 是单文件,迁移时先停旧实例,再写入 PVC:
```bash
# 1) 缩容到 0,避免应用关机时覆盖
kubectl -n log scale deploy/opsx-loki --replicas=0
# 2) 临时 Pod 挂 PVC 拷数据(与业务 Pod 同节点,RWO 盘才能挂上)
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: {name: pvc-loader, namespace: log}
spec:
restartPolicy: Never
securityContext: {runAsUser: 1000, runAsGroup: 1000, fsGroup: 1000}
containers:
- name: loader
image: busybox:1.36
command: ["sleep", "1800"]
volumeMounts: [{name: data, mountPath: /data}]
volumes:
- name: data
persistentVolumeClaim: {claimName: opsxloki-data}
EOF
kubectl -n log cp ./opsxloki.json pvc-loader:/data/opsxloki.json
kubectl -n log cp ./known_hosts pvc-loader:/data/known_hosts
kubectl -n log delete pod pvc-loader
kubectl -n log scale deploy/opsx-loki --replicas=1
```
> 不要在业务 Pod 运行时 `kubectl cp` 覆盖 DB:应用退出时会用内存数据回写,覆盖你拷进去的文件。
## 7. 验证
```bash
kubectl -n log get pods,pvc,svc,ingress
kubectl -n log logs deploy/opsx-loki --tail=20
kubectl -n log exec deploy/opsx-loki -- wget -qO- http://127.0.0.1:8080/api/v1/health
```
## 8. 升级
```bash
kubectl -n log set image deploy/opsx-loki opsx-loki=lghost/opsxloki:v0.2
kubectl -n log rollout status deploy/opsx-loki
```
`Recreate` 会先停旧 Pod 再起新 Pod,期间平台短暂不可用(单写者约束的代价)。
## 9. Token 接入(长期有效,仅 `log` 命名空间)
平台支持 ServiceAccount Token 接入。**推荐使用长期 Token + 命名空间级 Role**:
Token 不过期,但权限只覆盖 `log` 命名空间,泄露影响面同样仅限于 `log`,不会波及其他命名空间。
在集群里执行一次(管理员权限),然后取 Token:
```bash
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: log
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: opsx-loki
namespace: log
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: opsx-loki
namespace: log
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps", "secrets", "persistentvolumeclaims", "events", "serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
resources: ["statefulsets", "daemonsets", "deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: opsx-loki
namespace: log
subjects:
- kind: ServiceAccount
name: opsx-loki
namespace: log
roleRef:
kind: Role
name: opsx-loki
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Secret
metadata:
name: opsx-loki-token
namespace: log
annotations:
kubernetes.io/service-account.name: opsx-loki
type: kubernetes.io/service-account-token
EOF
kubectl -n log get secret opsx-loki-token -o jsonpath='{.data.token}' | base64 -d
```
- 这个 Token **不会自动过期**,但只绑定 `log` 命名空间的 Role:能操作该命名空间的 Pod、日志、工作负载、Service、ConfigMap、Secret、PVC,**无权访问其他命名空间和集群级资源**。
- 平台展示节点数需要 `nodes` 只读权限;命名空间级 Token 没有该权限时,平台会跳过节点信息,不影响集群接入与部署。
- 平台收到 Token 后会转成 kubeconfig,并用 `ENCRYPTION_KEY` 加密存 DB(`ENC:` 前缀),接口不返回明文。
- 轮换:删除 Secret 后重建,旧 Token 立即失效。
- 如果集群策略要求短期凭证,可改用 `kubectl -n log create token opsx-loki --duration=24h` 定期轮换。
- 上面的 Namespace 由管理员创建,平台 Token 本身没有集群级权限;只有确实需要平台自建命名空间时,才额外授予 `namespaces: create/get` 的 ClusterRole(create 无法按名称限制,预建 `log` 更安全)。