From 3c9388658bbff07750472f4a461735af71b43c2c Mon Sep 17 00:00:00 2001 From: opsx Date: Mon, 26 Oct 2026 17:16:15 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E8=B5=84=E6=BA=90=E5=90=8D=20opsx-?= =?UTF-8?q?loki/opsxloki=20=E2=86=92=20lokistack=EF=BC=88=E7=BA=BF?= =?UTF-8?q?=E4=B8=8A=E5=B7=B2=E8=BF=81=E7=A7=BB=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【已在线完成,零数据丢失】 K8s ns=log 资源全部改名: Deployment opsx-loki → lokistack Service opsx-loki → lokistack Service opsx-loki-nodeport → lokistack-nodeport PVC opsxloki-data → lokistack-data Secret opsxloki-secrets → lokistack-secrets Label app: opsx-loki → app: lokistack Ingress log.astx.club 后端 → lokistack-nodeport:8080 镜像 lghost/opsxloki → lghost/lokistack docker-compose 卷/容器名同步更名 【保留不变(改了会出事)】 namespace: log 数据文件名 opsxloki.json(代码硬编码) 容器内路径 /app/data 【迁移过程】 1. 备份 PVC 数据(32KB) + Secret 原值 + 旧 yaml → /root/lokistack-migration-backup-20261026-171141 2. scale 旧 Deployment → 0,等 Pod 消失 3. 复制 Secret(JWT sha256 校验一致) 4. 新建 PVC lokistack-data(Bound 30Gi cbs) 5. 临时 Pod 恢复数据(chown 1000:1000,opsxloki.json 保留 0600) 6. apply 新 Deployment → 1/1 Running 7. 修复 Ingress 后端(TKE qcloud 要求 NodePort)→ CLB 恢复 200 8. 删除旧 Deployment / 失效 Service 【验证】 lokistack 1/1,0 重启,镜像 lghost/lokistack:latest(statically linked) /api/v1/health → {"status":"ok","version":"2.0.0"} /app/data/opsxloki.json 610B 完整(admin 用户 + 配置) http://log.astx.club/ → HTTP 200 镜像加固:admin123=0、/app/backend/=0、initial-admin-password.txt 存在 【回滚兜底(建议 7 天后删)】 pvc/opsxloki-data、secret/opsxloki-secrets 【文档】 README 新增「从 opsx-loki 升级」章节: K8s 七步迁移脚本 + docker-compose 卷迁移命令 --- README.md | 78 +++++++++++++++++++++++++ docker-compose.prod.yml | 48 +++++++-------- docker-compose.yml | 28 ++++----- docs/deployment/kubernetes.md | 44 +++++++------- kubernetes/README.md | 76 ++++++++++++------------ kubernetes/deployment.yaml | 28 ++++----- kubernetes/examples/ingress-tke.yaml | 4 +- kubernetes/examples/ingress.yaml | 6 +- kubernetes/examples/secret.example.yaml | 4 +- kubernetes/install.sh | 52 ++++++++--------- kubernetes/pvc.yaml | 2 +- kubernetes/service-nodeport.yaml | 6 +- kubernetes/service.yaml | 6 +- 13 files changed, 230 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 423969c..ee2017d 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,84 @@ --- +--- + +## 从 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`(代码里硬编码,不改),只是目录/卷名变了。 + +--- + ## 快速开始 ### 前置条件 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e3e5849..fe0dd80 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,5 +1,5 @@ # ============================================================ -# opsx-loki - Production Docker Compose Configuration +# lokistack - Production Docker Compose Configuration # ============================================================ # Features: # - Multi-instance deployment with load balancing @@ -15,14 +15,14 @@ services: # ---- Nginx Load Balancer ---- nginx: image: nginx:1.27-alpine - container_name: opsxloki-lb + container_name: lokistack-lb restart: unless-stopped ports: - "8080:8080" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro networks: - - opsxloki-net + - lokistack-net healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 30s @@ -52,21 +52,21 @@ services: - /var/cache/nginx:noexec,nosuid,size=10m - /var/run:noexec,nosuid,size=5m depends_on: - opsxloki-1: + lokistack-1: condition: service_healthy - opsxloki-2: + lokistack-2: condition: service_healthy # ---- Application Instance 1 ---- - opsxloki-1: + lokistack-1: build: context: . dockerfile: Dockerfile args: TARGETARCH: ${TARGETARCH:-amd64} image: lghost/lokistack:latest - container_name: opsxloki-1 - hostname: opsxloki-1 + container_name: lokistack-1 + hostname: lokistack-1 restart: unless-stopped security_opt: @@ -85,16 +85,16 @@ services: volumes: # Named volume for data persistence - - opsxloki-data-1:/app/data + - lokistack-data-1:/app/data # kubeconfig read-only (P0: non-root path) - - ${KUBECONFIG_PATH:-~/.kube}:/home/opsxloki/.kube:ro + - ${KUBECONFIG_PATH:-~/.kube}:/home/lokistack/.kube:ro # SSH keys read-only - - ${SSH_KEY_PATH:-~/.ssh}:/home/opsxloki/.ssh:ro + - ${SSH_KEY_PATH:-~/.ssh}:/home/lokistack/.ssh:ro environment: - SERVER_PORT=8080 - DATA_DIR=/app/data - - KUBECONFIG=/home/opsxloki/.kube/config + - KUBECONFIG=/home/lokistack/.kube/config # Secrets via Docker Secrets file injection - JWT_SECRET_FILE=${JWT_SECRET_FILE:-/run/secrets/jwt_secret} - ENCRYPTION_KEY_FILE=${ENCRYPTION_KEY_FILE:-/run/secrets/encryption_key} @@ -136,18 +136,18 @@ services: stop_grace_period: 30s networks: - - opsxloki-net + - lokistack-net # ---- Application Instance 2 ---- - opsxloki-2: + lokistack-2: build: context: . dockerfile: Dockerfile args: TARGETARCH: ${TARGETARCH:-amd64} image: lghost/lokistack:latest - container_name: opsxloki-2 - hostname: opsxloki-2 + container_name: lokistack-2 + hostname: lokistack-2 restart: unless-stopped security_opt: @@ -163,14 +163,14 @@ services: - /tmp:noexec,nosuid,size=50m,uid=1000,gid=1000 volumes: - - opsxloki-data-2:/app/data - - ${KUBECONFIG_PATH:-~/.kube}:/home/opsxloki/.kube:ro - - ${SSH_KEY_PATH:-~/.ssh}:/home/opsxloki/.ssh:ro + - lokistack-data-2:/app/data + - ${KUBECONFIG_PATH:-~/.kube}:/home/lokistack/.kube:ro + - ${SSH_KEY_PATH:-~/.ssh}:/home/lokistack/.ssh:ro environment: - SERVER_PORT=8080 - DATA_DIR=/app/data - - KUBECONFIG=/home/opsxloki/.kube/config + - KUBECONFIG=/home/lokistack/.kube/config - JWT_SECRET_FILE=${JWT_SECRET_FILE:-/run/secrets/jwt_secret} - ENCRYPTION_KEY_FILE=${ENCRYPTION_KEY_FILE:-/run/secrets/encryption_key} @@ -209,13 +209,13 @@ services: stop_grace_period: 30s networks: - - opsxloki-net + - lokistack-net # ---- Named Volumes ---- volumes: - opsxloki-data-1: + lokistack-data-1: driver: local - opsxloki-data-2: + lokistack-data-2: driver: local # ---- Docker Secrets ---- @@ -227,7 +227,7 @@ secrets: # ---- Network ---- networks: - opsxloki-net: + lokistack-net: driver: bridge ipam: config: diff --git a/docker-compose.yml b/docker-compose.yml index 8cd8093..c485acd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,8 @@ # ============================================================ -# opsx-loki - Development Docker Compose Configuration +# lokistack - Development Docker Compose Configuration # ============================================================ # P0 Fixes: -# - Non-root kubeconfig mount (/home/opsxloki/.kube) +# - Non-root kubeconfig mount (/home/lokistack/.kube) # - Healthcheck for orchestration visibility # - Resource limits to prevent noisy-neighbor # - security_opt: no-new-privileges @@ -14,15 +14,15 @@ # ============================================================ services: - opsxloki: + lokistack: build: context: . dockerfile: Dockerfile args: TARGETARCH: ${TARGETARCH:-amd64} image: lghost/lokistack:latest - container_name: opsxloki - hostname: opsxloki + container_name: lokistack + hostname: lokistack # ---- P0: Security options ---- security_opt: @@ -43,11 +43,11 @@ services: # ---- P0: Named volumes for data persistence ---- volumes: - - opsxloki-data:/app/data + - lokistack-data:/app/data # P0: Mount kubeconfig to non-root user directory - - ${KUBECONFIG_PATH:-${HOME}/.kube}:/home/opsxloki/.kube:ro + - ${KUBECONFIG_PATH:-${HOME}/.kube}:/home/lokistack/.kube:ro # SSH keys for host-based deployments - - ${SSH_KEY_PATH:-${HOME}/.ssh}:/home/opsxloki/.ssh:ro + - ${SSH_KEY_PATH:-${HOME}/.ssh}:/home/lokistack/.ssh:ro # ---- P0: Environment variables (no default secrets) ---- environment: @@ -60,7 +60,7 @@ services: # P0: ENCRYPTION_KEY must be provided externally (AES-256, >=32 字符) - "ENCRYPTION_KEY=${ENCRYPTION_KEY:?ENCRYPTION_KEY is required. Generate: openssl rand -hex 32, then: export ENCRYPTION_KEY=}" # KUBECONFIG points to non-root user's .kube directory - - KUBECONFIG=/home/opsxloki/.kube/config + - KUBECONFIG=/home/lokistack/.kube/config # ---- P0: Healthcheck ---- healthcheck: @@ -95,21 +95,21 @@ services: stop_grace_period: 15s networks: - - opsxloki-net + - lokistack-net # ---- Named volumes ---- volumes: - opsxloki-data: + lokistack-data: driver: local labels: - - "com.opsxloki.description=opsx-loki data directory" + - "com.lokistack.description=lokistack data directory" # ---- Network isolation ---- networks: - opsxloki-net: + lokistack-net: driver: bridge ipam: config: - subnet: 172.28.0.0/16 labels: - - "com.opsxloki.description=opsx-loki internal network" + - "com.lokistack.description=lokistack internal network" diff --git a/docs/deployment/kubernetes.md b/docs/deployment/kubernetes.md index 4a03cbe..7639ee8 100644 --- a/docs/deployment/kubernetes.md +++ b/docs/deployment/kubernetes.md @@ -7,7 +7,7 @@ ```bash kubectl create namespace log # 已存在可跳过 -kubectl -n log create secret generic opsxloki-secrets \ +kubectl -n log create secret generic lokistack-secrets \ --from-literal=JWT_SECRET="$(openssl rand -hex 32)" \ --from-literal=ENCRYPTION_KEY="$(openssl rand -hex 32)" ``` @@ -23,7 +23,7 @@ kubectl -n log create secret generic opsxloki-secrets \ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: opsxloki-data + name: lokistack-data namespace: log spec: accessModes: [ReadWriteOnce] @@ -64,12 +64,12 @@ kubectl apply -f kubernetes/namespace.yaml \ apiVersion: v1 kind: Service metadata: - name: opsx-loki + name: lokistack namespace: log spec: type: ClusterIP selector: - app: opsx-loki + app: lokistack ports: - name: http port: 8080 @@ -89,7 +89,7 @@ metadata: name: loki.xxx.io namespace: log labels: - app: opsx-loki + app: lokistack spec: ingressClassName: alb rules: @@ -100,7 +100,7 @@ spec: pathType: Prefix backend: service: - name: opsx-loki + name: lokistack port: number: 8080 ``` @@ -120,7 +120,7 @@ JSON DB 是单文件,迁移时先停旧实例,再写入 PVC: ```bash # 1) 缩容到 0,避免应用关机时覆盖 -kubectl -n log scale deploy/opsx-loki --replicas=0 +kubectl -n log scale deploy/lokistack --replicas=0 # 2) 临时 Pod 挂 PVC 拷数据(与业务 Pod 同节点,RWO 盘才能挂上) kubectl apply -f - <<'EOF' @@ -137,14 +137,14 @@ spec: volumeMounts: [{name: data, mountPath: /data}] volumes: - name: data - persistentVolumeClaim: {claimName: opsxloki-data} + persistentVolumeClaim: {claimName: lokistack-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 +kubectl -n log scale deploy/lokistack --replicas=1 ``` > 不要在业务 Pod 运行时 `kubectl cp` 覆盖 DB:应用退出时会用内存数据回写,覆盖你拷进去的文件。 @@ -153,15 +153,15 @@ kubectl -n log scale deploy/opsx-loki --replicas=1 ```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 +kubectl -n log logs deploy/lokistack --tail=20 +kubectl -n log exec deploy/lokistack -- wget -qO- http://127.0.0.1:8080/api/v1/health ``` ## 8. 升级 ```bash -kubectl -n log set image deploy/opsx-loki opsx-loki=lghost/lokistack:v0.2 -kubectl -n log rollout status deploy/opsx-loki +kubectl -n log set image deploy/lokistack lokistack=lghost/lokistack:v0.2 +kubectl -n log rollout status deploy/lokistack ``` `Recreate` 会先停旧 Pod 再起新 Pod,期间平台短暂不可用(单写者约束的代价)。 @@ -183,13 +183,13 @@ metadata: apiVersion: v1 kind: ServiceAccount metadata: - name: opsx-loki + name: lokistack namespace: log --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: opsx-loki + name: lokistack namespace: log rules: - apiGroups: [""] @@ -205,34 +205,34 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: opsx-loki + name: lokistack namespace: log subjects: - kind: ServiceAccount - name: opsx-loki + name: lokistack namespace: log roleRef: kind: Role - name: opsx-loki + name: lokistack apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: Secret metadata: - name: opsx-loki-token + name: lokistack-token namespace: log annotations: - kubernetes.io/service-account.name: opsx-loki + kubernetes.io/service-account.name: lokistack type: kubernetes.io/service-account-token EOF -kubectl -n log get secret opsx-loki-token -o jsonpath='{.data.token}' | base64 -d +kubectl -n log get secret lokistack-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` 定期轮换。 +- 如果集群策略要求短期凭证,可改用 `kubectl -n log create token lokistack --duration=24h` 定期轮换。 - 上面的 Namespace 由管理员创建,平台 Token 本身没有集群级权限;只有确实需要平台自建命名空间时,才额外授予 `namespaces: create/get` 的 ClusterRole(create 无法按名称限制,预建 `log` 更安全)。 diff --git a/kubernetes/README.md b/kubernetes/README.md index 3b80e59..24e2dd4 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -1,4 +1,4 @@ -# opsx-loki 部署到 Kubernetes +# lokistack 部署到 Kubernetes 命名空间固定:**`log`**(日志相关资源统一放这里) @@ -13,11 +13,11 @@ bash kubernetes/install.sh 脚本会依次完成: 1. 创建 `log` 命名空间 -2. **生成随机密钥** `JWT_SECRET` / `ENCRYPTION_KEY` 写入 Secret `opsxloki-secrets` +2. **生成随机密钥** `JWT_SECRET` / `ENCRYPTION_KEY` 写入 Secret `lokistack-secrets` (已存在则跳过,**不会覆盖**) -3. 创建 PVC `opsxloki-data`(30 GiB,使用集群默认 StorageClass) -4. 部署 `opsx-loki`(单副本 + Recreate) -5. 创建 Service `opsx-loki:8080`(ClusterIP) +3. 创建 PVC `lokistack-data`(30 GiB,使用集群默认 StorageClass) +4. 部署 `lokistack`(单副本 + Recreate) +5. 创建 Service `lokistack:8080`(ClusterIP) 6. 等待 Pod 就绪并输出访问方式 **需要 NodePort(配 ingress 时)**: @@ -40,7 +40,7 @@ WITH_NODEPORT=1 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/opsxloki PV_SIZE=50Gi 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` | **环境变量一览**: @@ -51,7 +51,7 @@ WITH_NODEPORT=1 bash kubernetes/install.sh | `STORAGE_CLASS` | 空(自动探测)| 显式指定 StorageClass 名称 | | `LOCAL_STORAGE` | `auto` | `auto` 无 SC 时自动建本地 PV / `off` 只诊断 / `force` 强制本地盘 | | `NODE_NAME` | 空(第一个 Ready 节点)| hostPath PV 绑定的节点 | -| `PV_PATH` | `/var/lib/opsxloki-data` | hostPath 在宿主机上的路径 | +| `PV_PATH` | `/var/lib/lokistack-data` | hostPath 在宿主机上的路径 | | `PV_SIZE` | `30Gi` | PV 容量声明 | --- @@ -95,13 +95,13 @@ WITH_NODEPORT=1 bash kubernetes/install.sh 检测到集群完全没有 SC 时,脚本自动创建一个**静态 local PV**: ```yaml -StorageClass: opsxloki-local +StorageClass: lokistack-local provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer # 先定 Pod 再绑 PV reclaimPolicy: Retain # 误删 PVC 不丢数据 -PersistentVolume: opsxloki-data-pv - hostPath: { path: /var/lib/opsxloki-data, type: DirectoryOrCreate } +PersistentVolume: lokistack-data-pv + hostPath: { path: /var/lib/lokistack-data, type: DirectoryOrCreate } nodeAffinity: 锁定一台节点 ``` @@ -142,7 +142,7 @@ LOCAL_STORAGE=off bash kubernetes/install.sh | **Longhorn** | Helm,每节点装 | 需要副本/高可用 | 每节点约 1C2G 开销 | | **NFS + nfs-subdir-provisioner** | 需现成 NFS 服务器 | 多节点共享 | 依赖外部 NFS | -> 对 opsx-loki 这类**单副本 + JSON DB 单写者**的应用,**local PV 完全够用**, +> 对 lokistack 这类**单副本 + JSON DB 单写者**的应用,**local PV 完全够用**, > 不需要为了"高可用"上 Longhorn —— 副本存储反而会和"单写者"约束冲突。 --- @@ -155,12 +155,12 @@ LOCAL_STORAGE=off bash kubernetes/install.sh ```bash # 1) 找到 PV 绑定的节点和路径 -kubectl get pv opsxloki-data-pv -o custom-columns=\ +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/opsxloki-data/' +ssh root@${NODE} 'ls -la /var/lib/lokistack-data/' # 应看到:opsxloki.json kubeconfigs/ known_hosts ``` @@ -169,8 +169,8 @@ ssh root@${NODE} 'ls -la /var/lib/opsxloki-data/' ```bash NODE=<节点> TS=$(date +%Y%m%d-%H%M%S) -ssh root@${NODE} "tar czf /tmp/opsxloki-backup-${TS}.tar.gz -C /var/lib opsxloki-data" -scp root@${NODE}:/tmp/opsxloki-backup-${TS}.tar.gz ./ +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 ./ ``` > `opsxloki.json` 写入是原子的(临时文件 + fsync + rename),**热备份安全**,不用停 Pod。 @@ -181,41 +181,41 @@ scp root@${NODE}:/tmp/opsxloki-backup-${TS}.tar.gz ./ OLD=<旧节点> NEW=<新节点> # 1) 停止写入 -kubectl -n log scale deploy opsx-loki --replicas=0 +kubectl -n log scale deploy lokistack --replicas=0 # 2) 从旧节点拉取数据 -ssh root@${OLD} 'tar czf - -C /var/lib opsxloki-data' > /tmp/opsxloki-data.tar.gz +ssh root@${OLD} 'tar czf - -C /var/lib lokistack-data' > /tmp/lokistack-data.tar.gz # 3) 推到新节点并解包 -scp /tmp/opsxloki-data.tar.gz root@${NEW}:/tmp/ -ssh root@${NEW} 'mkdir -p /var/lib && tar xzf /tmp/opsxloki-data.tar.gz -C /var/lib && ls -la /var/lib/opsxloki-data' +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 opsxloki-data-pv --type=json -p \ +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 opsx-loki --replicas=1 -kubectl -n log rollout status deploy/opsx-loki +kubectl -n log scale deploy lokistack --replicas=1 +kubectl -n log rollout status deploy/lokistack ``` ### 卸载但保留数据 ```bash # 删工作负载和服务(数据卷保留) -kubectl -n log delete deploy opsx-loki svc opsx-loki +kubectl -n log delete deploy lokistack svc lokistack # PVC 保留(Retain 策略,删了也不会丢 PV 里的数据) # 想彻底清掉的话: -# kubectl delete pvc opsxloki-data -# kubectl delete pv opsxloki-data-pv -# 然后去节点上 rm -rf /var/lib/opsxloki-data +# kubectl delete pvc lokistack-data +# kubectl delete pv lokistack-data-pv +# 然后去节点上 rm -rf /var/lib/lokistack-data # 如果要连脏数据一起重置重装: -kubectl -n log delete pvc opsxloki-data -kubectl delete pv opsxloki-data-pv -kubectl delete sc opsxloki-local -ssh root@${NODE} 'rm -rf /var/lib/opsxloki-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 ``` @@ -238,7 +238,7 @@ kubernetes/ ├── namespace.yaml # log 命名空间 ├── pvc.yaml # 数据卷 30Gi(storageClassName 留空 → 用默认 SC) ├── deployment.yaml # 平台本体(单副本 + Recreate,必须) -├── service.yaml # ClusterIP opsx-loki:8080 +├── service.yaml # ClusterIP lokistack:8080 ├── service-nodeport.yaml # NodePort 30080(配 ingress 时用) └── examples/ # 云相关配置,不参与默认部署 ├── ingress.yaml # 阿里云 ACK(alb class) @@ -260,7 +260,7 @@ kubectl apply -f kubernetes/namespace.yaml # 2) 密钥(⚠️ 必须先做,否则 Pod 起不来) # 密钥只生成一次,丢失会导致已保存的凭证无法解密 -kubectl -n log create secret generic opsxloki-secrets \ +kubectl -n log create secret generic lokistack-secrets \ --from-literal=JWT_SECRET="$(openssl rand -hex 32)" \ --from-literal=ENCRYPTION_KEY="$(openssl rand -hex 32)" @@ -270,7 +270,7 @@ kubectl apply -f kubernetes/deployment.yaml kubectl apply -f kubernetes/service.yaml # 4) 等待就绪 -kubectl -n log rollout status deployment/opsx-loki --timeout=180s +kubectl -n log rollout status deployment/lokistack --timeout=180s ``` > ❗**不要直接 `kubectl apply -f kubernetes/`**:Secret 不在目录里(密钥不能进 Git), @@ -283,7 +283,7 @@ kubectl -n log rollout status deployment/opsx-loki --timeout=180s **临时(本地端口转发)**: ```bash -kubectl -n log port-forward svc/opsx-loki 8080:8080 +kubectl -n log port-forward svc/lokistack 8080:8080 # 浏览器打开 http://localhost:8080 ``` @@ -304,7 +304,7 @@ vi kubernetes/examples/ingress-tke.yaml # 改 host kubectl apply -f kubernetes/examples/ingress-tke.yaml # 3) 查 CLB 分配的公网 IP,把域名解析过去 -kubectl -n log get ingress opsx-loki +kubectl -n log get ingress lokistack ``` | 云 | ingressClassName | 备注 | @@ -332,12 +332,12 @@ kubectl -n log get ingress opsx-loki apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: - name: opsxloki-data-20261025 + name: lokistack-data-20261025 namespace: log spec: volumeSnapshotClassName: <集群的 snapshotClass> source: - persistentVolumeClaimName: opsxloki-data + persistentVolumeClaimName: lokistack-data ``` --- @@ -383,7 +383,7 @@ spec: | 现象 | 原因 | 解决 | |------|------|------| -| Pod `CreateContainerConfigError` | 缺 Secret `opsxloki-secrets` | 跑 `install.sh`,或手动 `kubectl create secret` | +| 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 单独处理 | diff --git a/kubernetes/deployment.yaml b/kubernetes/deployment.yaml index dcd87e4..7556432 100644 --- a/kubernetes/deployment.yaml +++ b/kubernetes/deployment.yaml @@ -1,4 +1,4 @@ -# opsx-loki 平台本体 +# lokistack 平台本体 # # 关键约束: # 1) replicas 必须为 1:JSON DB 为单写者,多副本会互相覆盖。 @@ -8,28 +8,28 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: opsx-loki + name: lokistack namespace: log labels: - app: opsx-loki + app: lokistack spec: replicas: 1 strategy: type: Recreate selector: matchLabels: - app: opsx-loki + app: lokistack template: metadata: labels: - app: opsx-loki + app: lokistack spec: securityContext: runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 containers: - - name: opsx-loki + - name: lokistack image: lghost/lokistack:latest imagePullPolicy: Always ports: @@ -38,12 +38,12 @@ spec: env: # 可选:指定 admin 初始密码。不设置则自动生成 16 位随机密码, # 写入 PVC 的 initial-admin-password.txt 并打印到首次启动日志。 - # 获取:kubectl -n log exec deploy/opsx-loki -- cat /app/data/initial-admin-password.txt + # 获取:kubectl -n log exec deploy/lokistack -- cat /app/data/initial-admin-password.txt # 自定义: # - name: ADMIN_INIT_PASSWORD # valueFrom: # secretKeyRef: - # name: opsxloki-secrets + # name: lokistack-secrets # key: ADMIN_INIT_PASSWORD # optional: true - name: DATA_DIR @@ -51,22 +51,22 @@ spec: - name: JWT_SECRET valueFrom: secretKeyRef: - name: opsxloki-secrets + name: lokistack-secrets key: JWT_SECRET - name: ENCRYPTION_KEY valueFrom: secretKeyRef: - name: opsxloki-secrets + name: lokistack-secrets key: ENCRYPTION_KEY # 可选:挂载本地集群 kubeconfig 时启用 # - name: KUBECONFIG - # value: /home/opsxloki/.kube/config + # value: /home/lokistack/.kube/config volumeMounts: - name: data mountPath: /app/data # 可选:本地集群 kubeconfig(in-cluster 管理本集群时用) # - name: kubeconfig - # mountPath: /home/opsxloki/.kube + # mountPath: /home/lokistack/.kube # readOnly: true readinessProbe: httpGet: @@ -91,7 +91,7 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: opsxloki-data + claimName: lokistack-data # - name: kubeconfig # secret: - # secretName: opsxloki-kubeconfig + # secretName: lokistack-kubeconfig diff --git a/kubernetes/examples/ingress-tke.yaml b/kubernetes/examples/ingress-tke.yaml index 6920e29..3178633 100644 --- a/kubernetes/examples/ingress-tke.yaml +++ b/kubernetes/examples/ingress-tke.yaml @@ -12,7 +12,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: opsx-loki + name: lokistack namespace: log annotations: # CLB 健康检查(可选,TKE 默认会用 TCP 检查) @@ -28,6 +28,6 @@ spec: pathType: Prefix backend: service: - name: opsx-loki-nodeport # ← 必须是 NodePort 类型 Service + name: lokistack-nodeport # ← 必须是 NodePort 类型 Service port: number: 8080 diff --git a/kubernetes/examples/ingress.yaml b/kubernetes/examples/ingress.yaml index 6fbc971..d487fc2 100644 --- a/kubernetes/examples/ingress.yaml +++ b/kubernetes/examples/ingress.yaml @@ -4,7 +4,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: opsx-loki + name: lokistack namespace: log annotations: # 阿里云 ALB 示例;其他云改成对应 ingressClass @@ -12,13 +12,13 @@ metadata: spec: ingressClassName: alb rules: - - host: opsx-loki.91ai.io + - host: lokistack.91ai.io http: paths: - path: / pathType: Prefix backend: service: - name: opsx-loki + name: lokistack port: number: 8080 diff --git a/kubernetes/examples/secret.example.yaml b/kubernetes/examples/secret.example.yaml index 298140c..19c8a10 100644 --- a/kubernetes/examples/secret.example.yaml +++ b/kubernetes/examples/secret.example.yaml @@ -1,12 +1,12 @@ # 只做模板:不要提交真实密钥。 # 推荐直接用命令创建(避免密钥落盘到 Git): -# kubectl -n log create secret generic opsxloki-secrets \ +# kubectl -n log create secret generic lokistack-secrets \ # --from-literal=JWT_SECRET="$(openssl rand -hex 32)" \ # --from-literal=ENCRYPTION_KEY="$(openssl rand -hex 32)" apiVersion: v1 kind: Secret metadata: - name: opsxloki-secrets + name: lokistack-secrets namespace: log type: Opaque stringData: diff --git a/kubernetes/install.sh b/kubernetes/install.sh index c1b9b28..a3fc7fe 100755 --- a/kubernetes/install.sh +++ b/kubernetes/install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ============================================================================ -# opsx-loki 一键部署 +# lokistack 一键部署 # # 自动适配:阿里云 ACK / 腾讯云 TKE / AWS EKS / 华为云 CCE / GCP GKE / 自建 K8s # 命名空间固定:log @@ -94,10 +94,10 @@ resolve_storage_class() { # # 代价:数据固定在一台节点,节点损毁需从备份恢复(README 有 CSI 备份说明) # ============================================================================ -LOCAL_SC_NAME="opsxloki-local" -LOCAL_PV_NAME="opsxloki-data-pv" +LOCAL_SC_NAME="lokistack-local" +LOCAL_PV_NAME="lokistack-data-pv" LOCAL_PV_SIZE="${PV_SIZE:-30Gi}" -LOCAL_PV_PATH="${PV_PATH:-/var/lib/opsxloki-data}" +LOCAL_PV_PATH="${PV_PATH:-/var/lib/lokistack-data}" pick_local_node() { if [ -n "${NODE_NAME:-}" ]; then echo "${NODE_NAME}"; return 0; fi @@ -129,7 +129,7 @@ kind: PersistentVolume metadata: name: ${LOCAL_PV_NAME} labels: - app: opsx-loki + app: lokistack spec: capacity: storage: ${LOCAL_PV_SIZE} @@ -157,7 +157,7 @@ EOF } CLOUD="$(detect_cloud)" -info "opsx-loki 部署开始(namespace: ${NS})" +info "lokistack 部署开始(namespace: ${NS})" info "集群: $(kubectl config current-context 2>/dev/null || echo unknown)" info "平台: ${CLOUD}" @@ -219,11 +219,11 @@ rand_hex() { fi } -if kubectl -n "${NS}" get secret opsxloki-secrets >/dev/null 2>&1; then - info "Secret opsxloki-secrets 已存在,跳过(不覆盖现有密钥)" +if kubectl -n "${NS}" get secret lokistack-secrets >/dev/null 2>&1; then + info "Secret lokistack-secrets 已存在,跳过(不覆盖现有密钥)" else info "生成随机密钥 JWT_SECRET / ENCRYPTION_KEY" - kubectl -n "${NS}" create secret generic opsxloki-secrets \ + kubectl -n "${NS}" create secret generic lokistack-secrets \ --from-literal=JWT_SECRET="$(rand_hex)" \ --from-literal=ENCRYPTION_KEY="$(rand_hex)" >/dev/null ok "Secret 已创建" @@ -256,33 +256,33 @@ fi # 5) 等待就绪(失败时给出按云定制的诊断) # ============================================================================ info "等待 Deployment 就绪(最多 180 秒)..." -if ! kubectl -n "${NS}" rollout status deployment/opsx-loki --timeout=180s; then +if ! kubectl -n "${NS}" rollout status deployment/lokistack --timeout=180s; then echo "" warn "部署未在 180 秒内就绪,开始诊断:" echo "" - kubectl -n "${NS}" get po -l app=opsx-loki -o wide 2>/dev/null | sed 's/^/ /' + kubectl -n "${NS}" get po -l app=lokistack -o wide 2>/dev/null | sed 's/^/ /' - REASON=$(kubectl -n "${NS}" get po -l app=opsx-loki -o jsonpath='{.items[0].status.containerStatuses[0].state.waiting.reason}' 2>/dev/null || true) + REASON=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].status.containerStatuses[0].state.waiting.reason}' 2>/dev/null || true) echo "" case "${REASON}" in ImagePullBackOff|ErrImagePull) warn "镜像拉取失败(${REASON})" echo " → 当前镜像: $(grep -E '^\s+image:' "${DIR}/deployment.yaml" | head -1 | awk '{print $2}')" echo " → 该节点拉不到 Docker Hub。请改为目标云可拉的仓库后重跑:" - echo " 腾讯云 TCR : ccr.ccs.tencentyun.com//opsxloki:latest" - echo " 阿里云 ACR : registry.cn-hangzhou.aliyuncs.com//opsxloki:latest" - echo " 华为云 SWR : swr.cn-north-4.myhuaweicloud.com//opsxloki:latest" - echo " 自建 Harbor: harbor.your.domain/library/opsxloki:latest" + echo " 腾讯云 TCR : ccr.ccs.tencentyun.com//lokistack:latest" + echo " 阿里云 ACR : registry.cn-hangzhou.aliyuncs.com//lokistack:latest" + echo " 华为云 SWR : swr.cn-north-4.myhuaweicloud.com//lokistack:latest" + echo " 自建 Harbor: harbor.your.domain/library/lokistack:latest" ;; CreateContainerConfigError) warn "容器配置缺失(通常是 Secret 不存在)" - echo " → 检查: kubectl -n ${NS} get secret opsxloki-secrets" + echo " → 检查: kubectl -n ${NS} get secret lokistack-secrets" ;; "") - PVC_PHASE=$(kubectl -n "${NS}" get pvc opsxloki-data -o jsonpath='{.status.phase}' 2>/dev/null || echo unknown) + PVC_PHASE=$(kubectl -n "${NS}" get pvc lokistack-data -o jsonpath='{.status.phase}' 2>/dev/null || echo unknown) if [ "${PVC_PHASE}" != "Bound" ]; then warn "PVC 未绑定(phase=${PVC_PHASE})" - echo " → 检查 StorageClass: kubectl -n ${NS} describe pvc opsxloki-data" + echo " → 检查 StorageClass: kubectl -n ${NS} describe pvc lokistack-data" echo " → ${CLOUD} 建议的 SC:" case "${CLOUD}" in 腾讯云*) echo " cbs" ;; @@ -296,7 +296,7 @@ if ! kubectl -n "${NS}" rollout status deployment/opsx-loki --timeout=180s; then esac echo "" - kubectl -n "${NS}" describe po -l app=opsx-loki 2>/dev/null | tail -15 | sed 's/^/ /' + kubectl -n "${NS}" describe po -l app=lokistack 2>/dev/null | tail -15 | sed 's/^/ /' die "部署未完成,请按上面提示处理" fi ok "Deployment 已就绪" @@ -304,24 +304,24 @@ ok "Deployment 已就绪" # ============================================================================ # 6) 输出访问信息 # ============================================================================ -POD=$(kubectl -n "${NS}" get po -l app=opsx-loki -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "-") -POD_IP=$(kubectl -n "${NS}" get po -l app=opsx-loki -o jsonpath='{.items[0].status.podIP}' 2>/dev/null || echo "-") -NODE=$(kubectl -n "${NS}" get po -l app=opsx-loki -o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || echo "-") +POD=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "-") +POD_IP=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].status.podIP}' 2>/dev/null || echo "-") +NODE=$(kubectl -n "${NS}" get po -l app=lokistack -o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || echo "-") echo "" echo "════════════════════════════════════════════════════════" -ok "opsx-loki 部署完成" +ok "lokistack 部署完成" echo "════════════════════════════════════════════════════════" echo " 平台 : ${CLOUD}" echo " Namespace : ${NS}" echo " 存储类 : ${RESOLVED_SC}" echo " Pod : ${POD} (node=${NODE} ip=${POD_IP})" -echo " Service : opsx-loki.${NS}.svc.cluster.local:8080" +echo " Service : lokistack.${NS}.svc.cluster.local:8080" [ "${WITH_NODEPORT}" = "1" ] && echo " NodePort : <任意节点IP>:30080" echo "" echo " 初始账号 : admin / admin123 ← 首次登录请立即改密" echo "" -echo " 本地访问 : kubectl -n ${NS} port-forward svc/opsx-loki 8080:8080" +echo " 本地访问 : kubectl -n ${NS} port-forward svc/lokistack 8080:8080" echo " 浏览器打开 http://localhost:8080" echo "" echo " 配 Ingress(可选,先跑 WITH_NODEPORT=1 bash install.sh):" diff --git a/kubernetes/pvc.yaml b/kubernetes/pvc.yaml index 86ab978..d0055ca 100644 --- a/kubernetes/pvc.yaml +++ b/kubernetes/pvc.yaml @@ -5,7 +5,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: opsxloki-data + name: lokistack-data namespace: log spec: accessModes: diff --git a/kubernetes/service-nodeport.yaml b/kubernetes/service-nodeport.yaml index 5c0e707..31f4fa5 100644 --- a/kubernetes/service-nodeport.yaml +++ b/kubernetes/service-nodeport.yaml @@ -1,14 +1,14 @@ apiVersion: v1 kind: Service metadata: - name: opsx-loki-nodeport + name: lokistack-nodeport namespace: log labels: - app: opsx-loki + app: lokistack spec: type: NodePort selector: - app: opsx-loki + app: lokistack ports: - name: http port: 8080 diff --git a/kubernetes/service.yaml b/kubernetes/service.yaml index 7e8c8c7..2f4d207 100644 --- a/kubernetes/service.yaml +++ b/kubernetes/service.yaml @@ -1,14 +1,14 @@ apiVersion: v1 kind: Service metadata: - name: opsx-loki + name: lokistack namespace: log labels: - app: opsx-loki + app: lokistack spec: type: ClusterIP selector: - app: opsx-loki + app: lokistack ports: - name: http port: 8080