Files
LokiStack/kubernetes/README.md
T
opsx f3733f0bfc 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 未翻译、词典双逗号等
2026-10-27 17:11:38 +08:00

392 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# lokistack 部署到 Kubernetes
命名空间固定:**`log`**(日志相关资源统一放这里)
---
## 一条命令部署(推荐)
```bash
bash kubernetes/install.sh
```
脚本会依次完成:
1. 创建 `log` 命名空间
2. **生成随机密钥** `JWT_SECRET` / `ENCRYPTION_KEY` 写入 Secret `lokistack-secrets`
(已存在则跳过,**不会覆盖**)
3. 创建 PVC `lokistack-data`(30 GiB,使用集群默认 StorageClass)
4. 部署 `lokistack`(单副本 + Recreate)
5. 创建 Service `lokistack:8080`(ClusterIP)
6. 等待 Pod 就绪并输出访问方式
**需要 NodePort(配 ingress 时)**:
```bash
WITH_NODEPORT=1 bash kubernetes/install.sh
```
**幂等性**:重复执行只做增量更新,不会覆盖密钥、不会重建数据卷。
---
## 全部用法速查
| 场景 | 命令 |
|------|------|
| **标准部署**(自动探测云平台 + StorageClass)| `bash kubernetes/install.sh` |
| 同时开 NodePort(配 ingress 用)| `WITH_NODEPORT=1 bash kubernetes/install.sh` |
| 指定 StorageClass | `STORAGE_CLASS=cbs bash kubernetes/install.sh` |
| 指定 SC(自建用 local-path)| `STORAGE_CLASS=local-path bash kubernetes/install.sh` |
| **自建集群强制用节点本地盘**(hostPath)| `LOCAL_STORAGE=force bash kubernetes/install.sh` |
| 只诊断不动手(无变更)| `LOCAL_STORAGE=off bash kubernetes/install.sh` |
| **指定本地存储的节点/路径/容量** | `NODE_NAME=node1 PV_PATH=/data/lokistack PV_SIZE=50Gi bash kubernetes/install.sh` |
| 组合:自建 + 指定节点 + NodePort | `LOCAL_STORAGE=force NODE_NAME=node1 WITH_NODEPORT=1 bash kubernetes/install.sh` |
**环境变量一览**:
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `WITH_NODEPORT` | `0` | `1` = 额外创建 NodePort Service(30080)|
| `STORAGE_CLASS` | 空(自动探测)| 显式指定 StorageClass 名称 |
| `LOCAL_STORAGE` | `auto` | `auto` 无 SC 时自动建本地 PV / `off` 只诊断 / `force` 强制本地盘 |
| `NODE_NAME` | 空(第一个 Ready 节点)| hostPath PV 绑定的节点 |
| `PV_PATH` | `/var/lib/lokistack-data` | hostPath 在宿主机上的路径 |
| `PV_SIZE` | `30Gi` | PV 容量声明 |
---
## 环境要求
| 项 | 要求 |
|----|------|
| Kubernetes | 1.20+ |
| StorageClass | **脚本自动探测**(default 注解 → 唯一 SC → 手动 `STORAGE_CLASS=`);自建集群无 SC 时会提前报错并给出解决方案,不会白等 |
| 镜像 | 节点可拉 `lghost/lokistack:latest`(或改成自己的 Harbor/ACR/TCR/SWR) |
| 权限 | 有 `log` 命名空间的 create/apply 权限 |
## 脚本自动处理什么
`install.sh` 不是无脑 apply,它会先探测环境再动手:
| 场景 | 脚本行为 |
|------|---------|
| **云平台识别** | 读节点 `providerID`,识别出 腾讯云 TKE / AWS EKS / 阿里云 ACK / 华为云 CCE / GCP GKE / Azure AKS / K3s / 自建 |
| **StorageClass** | ① 显式 `STORAGE_CLASS=xxx` → ② `is-default-class` 注解 → ③ 集群唯一 SC → ④ **都没有则自动创建节点本地存储**(静态 local PV,零外部依赖) |
| **Secret** | 不存在则用 `openssl rand -hex 32` 生成;**已存在则跳过,绝不覆盖** |
| **PVC** | 把探测到的 SC 显式写进渲染后的 pvc.yaml(避免依赖隐式默认行为) |
| **就绪等待** | `rollout status` 最多 180s,超时后按**失败原因分类诊断** |
| **镜像拉不到** | 诊断为 `ImagePullBackOff` 时,按云给出对应仓库格式(TCR / ACR / SWR / Harbor) |
| **PVC 未绑定** | 诊断为 StorageClass 问题时,按云给出推荐 SC 名(cbs / gp3 / alicloud-disk-essd / csi-disk) |
**脚本不做什么**(需要人工决策的部分):
- ❌ 不自动改镜像仓库(不知道你的仓库地址,只给格式提示)
- ❌ 不覆盖已存在的 Secret / 已有数据卷
---
## 自建 K8s(kubeadm 裸装)的存储怎么办
自建集群最常见的坑就是**没有 StorageClass** → PVC 永久 Pending。脚本内置三种处理方式:
### 默认行为(`LOCAL_STORAGE=auto`):自动创建节点本地存储
检测到集群完全没有 SC 时,脚本自动创建一个**静态 local PV**:
```yaml
StorageClass: lokistack-local
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer # 先定 Pod 再绑 PV
reclaimPolicy: Retain # 误删 PVC 不丢数据
PersistentVolume: lokistack-data-pv
hostPath: { path: /var/lib/lokistack-data, type: DirectoryOrCreate }
nodeAffinity: 锁定一台节点
```
**特点**:
| 优点 | 代价 |
|------|------|
| **零外部依赖**(不拉任何镜像,国内网络友好)| 数据固定在单节点,Pod 不会漂移到别处 |
| kubelet 自动建目录(`DirectoryOrCreate`)| 只能供给一个 PVC(单实例场景够用)|
| Pod 一定调度到数据所在节点(`nodeAffinity`)| 节点损毁需从备份恢复(见「持久化」的 CSI 快照)|
**实测数据**:PVC 从 Pending 到 Bound **5 秒**,Pod 正常读写宿主机目录。
### 想要动态供给(支持多 PVC / 多实例)
装 Rancher **local-path-provisioner**(自建集群事实标准):
```bash
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
STORAGE_CLASS=local-path bash kubernetes/install.sh
```
### 想先看着不动手(`LOCAL_STORAGE=off`)
```bash
LOCAL_STORAGE=off bash kubernetes/install.sh
```
只打印诊断和 4 种解法,不做任何变更。
### 自建 K8s 存储方案选型
| 方案 | 外部依赖 | 适用 | 备注 |
|------|---------|------|------|
| **静态 local PV**(本脚本默认)| **零** | 单副本、数据固定 | 最省事,国内网络友好 |
| **local-path-provisioner** | 2 个镜像 | 需要动态供给/多 PVC | 自建集群首选,社区标准 |
| **OpenEBS LocalPV** | Helm + 较多镜像 | 要 LVM/ZFS 精细控制 | 较重 |
| **Longhorn** | Helm,每节点装 | 需要副本/高可用 | 每节点约 1C2G 开销 |
| **NFS + nfs-subdir-provisioner** | 需现成 NFS 服务器 | 多节点共享 | 依赖外部 NFS |
> 对 lokistack 这类**单副本 + JSON DB 单写者**的应用,**local PV 完全够用**,
> 不需要为了"高可用"上 Longhorn —— 副本存储反而会和"单写者"约束冲突。
---
## hostPath 数据的运维(自建集群必读)
自建集群用 hostPath 时,**数据在宿主机目录里**,不在云盘上。下面这些操作要会。
### 数据在哪
```bash
# 1) 找到 PV 绑定的节点和路径
kubectl get pv lokistack-data-pv -o custom-columns=\
'NAME:.metadata.name,PATH:.spec.hostPath.path,NODE:.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]'
# 2) 去那台节点上看
NODE=<上面的 NODE 值>
ssh root@${NODE} 'ls -la /var/lib/lokistack-data/'
# 应看到:lokistack.json kubeconfigs/ known_hosts
```
### 备份(不用停机)
```bash
NODE=<节点>
TS=$(date +%Y%m%d-%H%M%S)
ssh root@${NODE} "tar czf /tmp/lokistack-backup-${TS}.tar.gz -C /var/lib lokistack-data"
scp root@${NODE}:/tmp/lokistack-backup-${TS}.tar.gz ./
```
> `lokistack.json` 写入是原子的(临时文件 + fsync + rename),**热备份安全**,不用停 Pod。
### 迁移到另一台节点
```bash
OLD=<旧节点> NEW=<新节点>
# 1) 停止写入
kubectl -n log scale deploy lokistack --replicas=0
# 2) 从旧节点拉取数据
ssh root@${OLD} 'tar czf - -C /var/lib lokistack-data' > /tmp/lokistack-data.tar.gz
# 3) 推到新节点并解包
scp /tmp/lokistack-data.tar.gz root@${NEW}:/tmp/
ssh root@${NEW} 'mkdir -p /var/lib && tar xzf /tmp/lokistack-data.tar.gz -C /var/lib && ls -la /var/lib/lokistack-data'
# 4) 改 PV 的 nodeAffinity 指向新节点
kubectl patch pv lokistack-data-pv --type=json -p \
"[{\"op\":\"replace\",\"path\":\"/spec/nodeAffinity/required/nodeSelectorTerms/0/matchExpressions/0/values\",\"value\":[\"${NEW}\"]}]"
# 5) 恢复
kubectl -n log scale deploy lokistack --replicas=1
kubectl -n log rollout status deploy/lokistack
```
### 卸载但保留数据
```bash
# 删工作负载和服务(数据卷保留)
kubectl -n log delete deploy lokistack svc lokistack
# PVC 保留(Retain 策略,删了也不会丢 PV 里的数据)
# 想彻底清掉的话:
# kubectl delete pvc lokistack-data
# kubectl delete pv lokistack-data-pv
# 然后去节点上 rm -rf /var/lib/lokistack-data
# 如果要连脏数据一起重置重装:
kubectl -n log delete pvc lokistack-data
kubectl delete pv lokistack-data-pv
kubectl delete sc lokistack-local
ssh root@${NODE} 'rm -rf /var/lib/lokistack-data'
# 然后重新 bash install.sh
```
### 常见坑
| 现象 | 原因 | 处理 |
|------|------|------|
| Pod 一直 Pending | PV 的 nodeAffinity 指向的节点不可用 | `kubectl get nodes` 确认该节点 Ready,或按上面「迁移」流程改节点 |
| Pod Pending 且报 `node(s) didn't match node affinity` | Pod 被调度到其他节点 | 正常,scheduler 会重试;若持续,检查 PV 的 nodeAffinity 是否写错节点名 |
| 换节点后数据"消失" | 数据在新节点上不存在 | 走「迁移到另一台节点」流程,别只改 nodeAffinity |
| 磁盘写满 | hostPath 用节点本地盘 | `df -h /var/lib`,迁移到大盘或改 `PV_PATH` |
---
## 目录结构
```
kubernetes/
├── install.sh # 一键部署脚本(推荐入口)
├── namespace.yaml # log 命名空间
├── pvc.yaml # 数据卷 30Gi(storageClassName 留空 → 用默认 SC)
├── deployment.yaml # 平台本体(单副本 + Recreate,必须)
├── service.yaml # ClusterIP lokistack:8080
├── service-nodeport.yaml # NodePort 30080(配 ingress 时用)
└── examples/ # 云相关配置,不参与默认部署
├── ingress.yaml # 阿里云 ACK(alb class)
├── ingress-tke.yaml # 腾讯云 TKE(qcloud class)
└── secret.example.yaml # Secret 模板(仅供手工创建参考)
```
> ⚠️ `examples/` 里的文件**故意不放在 `kubernetes/` 根目录**:
> 它们是云厂商相关的(ingress class 各云不同),混在默认目录里会让
> `kubectl apply -f kubernetes/` 在不同云上互相覆盖或报错。
---
## 手动部署(等价于 install.sh,供需要精细控制时用)
```bash
# 1) 命名空间
kubectl apply -f kubernetes/namespace.yaml
# 2) 密钥(⚠️ 必须先做,否则 Pod 起不来)
# 密钥只生成一次,丢失会导致已保存的凭证无法解密
kubectl -n log create secret generic lokistack-secrets \
--from-literal=JWT_SECRET="$(openssl rand -hex 32)" \
--from-literal=ENCRYPTION_KEY="$(openssl rand -hex 32)"
# 3) 数据卷 + 平台 + 服务
kubectl apply -f kubernetes/pvc.yaml
kubectl apply -f kubernetes/deployment.yaml
kubectl apply -f kubernetes/service.yaml
# 4) 等待就绪
kubectl -n log rollout status deployment/lokistack --timeout=180s
```
> ❗**不要直接 `kubectl apply -f kubernetes/`**:Secret 不在目录里(密钥不能进 Git),
> 缺 Secret 时 Pod 会一直 `CreateContainerConfigError`。
---
## 访问
**临时(本地端口转发)**:
```bash
kubectl -n log port-forward svc/lokistack 8080:8080
# 浏览器打开 http://localhost:8080
```
**初始账号**:`admin` / `admin123` ← **首次登录请立即改密**
---
## 配 Ingress(可选)
以腾讯云 TKE 为例(其他云见 `examples/`):
```bash
# 1) 先开 NodePort(TKE 的 qcloud ingress 后端要求 NodePort 类型 Service)
WITH_NODEPORT=1 bash kubernetes/install.sh
# 2) 改域名后应用
vi kubernetes/examples/ingress-tke.yaml # 改 host
kubectl apply -f kubernetes/examples/ingress-tke.yaml
# 3) 查 CLB 分配的公网 IP,把域名解析过去
kubectl -n log get ingress lokistack
```
| 云 | ingressClassName | 备注 |
|----|------------------|------|
| 腾讯云 TKE | `qcloud` | 后端 Service 必须 NodePort |
| 阿里云 ACK | `alb` | 用 `alb.ingress.kubernetes.io/*` 注解 |
| AWS EKS | `alb` 或 `nginx` | 需装 AWS Load Balancer Controller |
| 华为云 CCE | `nginx` 或 `cce` | — |
| 自建 | `nginx` | 需装 ingress-nginx |
---
## 持久化(重要)
- `/app/data` 挂 PVC,保存 `lokistack.json`、`kubeconfigs/`、`known_hosts`。
- **必须 `replicas: 1` + `strategy: Recreate`**:JSON DB 是单写者;滚动更新时两个 Pod
同时挂同一个 RWO PVC 会互相覆盖。
- DB 写入本身是原子的(临时文件 + fsync + rename),容器被杀不会写坏文件。
### 备份:CSI VolumeSnapshot
各云都支持同一套 `snapshot.storage.k8s.io` API(AWS EBS / 腾讯 CBS / 华为 EVS / 阿里云 ESSD):
```yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: lokistack-data-20261025
namespace: log
spec:
volumeSnapshotClassName: <集群的 snapshotClass>
source:
persistentVolumeClaimName: lokistack-data
```
---
## 多云兼容
| 项 | 做法 |
|---|---|
| 存储类 | `pvc.yaml` **不写** `storageClassName`,用集群默认 SC;没有默认 SC 时填对应云的名字(AWS `gp3` / 腾讯 `cbs` / 华为 `csi-disk` / 阿里云 `alicloud-disk-essd`)|
| 镜像仓库 | `deployment.yaml` 的 `image` 填目标云可拉的仓库(Harbor / ACR / TCR / SWR)|
| Loki 存储 | 平台已支持 S3 / OSS / COS / OBS,均 S3 兼容,无需云厂商 SDK |
| 查询链路 | 平台与 Loki 同集群时,数据源选「集群服务」(namespace `log`、service `loki`、port `3100`),无需 Ingress/WAF |
| Namespace | 固定 `log`,跨云一致 |
---
## 与外部实例的关系
平台的 JSON DB 单写者,**不要两个实例共用同一份数据卷**。要么:
- 集群内新实例 + 迁移旧数据文件(停旧实例后拷贝);或
- 保留外部实例做带外管理,集群内实例使用独立数据卷(两套数据)。
---
## 日志
- 平台**只写 stdout**(Go/GIN 默认),不写日志文件;`kubectl logs` 可直接看。
- Kubernetes 由 kubelet 统一轮转,节点级配置建议:
```yaml
# /var/lib/kubelet/config.yaml
containerLogMaxSize: 50Mi
containerLogMaxFiles: 5
```
- Loki / Promtail 同样输出到 stdout,由容器运行时轮转;不要在容器内写业务日志文件。
- 应用自身的部署日志写在 DB(`deployment_logs`),可在部署详情里查看,不占容器日志空间。
---
## 常见问题
| 现象 | 原因 | 解决 |
|------|------|------|
| Pod `CreateContainerConfigError` | 缺 Secret `lokistack-secrets` | 跑 `install.sh`,或手动 `kubectl create secret` |
| Pod `Pending`,PVC `Pending` | 集群没有默认 StorageClass | `pvc.yaml` 里填 `storageClassName` |
| Pod `ImagePullBackOff` | 节点拉不到 `lghost/lokistack:latest` | 换镜像地址(ACR/TCR/SWR/Harbor) |
| `kubectl apply -f kubernetes/` 报 ingress 错误 | 误把 examples 里的 ingress 一起 apply 了 | 只 apply 根目录的 yaml,ingress 单独处理 |
| 重启后数据丢失 | `/app/data` 没挂 PVC | 确认 `deployment.yaml` 的 volumeMounts |
| 两个 Pod 同时写 DB | 用了 RollingUpdate 或多副本 | 必须 `replicas: 1` + `strategy: Recreate` |