# 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` 会自动检查,不通过直接给出具体修法) 脚本启动时做**三级预检 + 一级软检查**,任何一级不通过都会立即退出并打印「是哪一类问题、 下一步做什么」,不会跑到一半才报错: | 预检项 | 不通过时的提示内容 | |--------|-------------------| | **kubectl 是否安装** | 按 CentOS/TencentOS、Ubuntu/Debian、macOS 分别给安装命令;并给出「机器没有外网」时的替代方案(控制台下载 / 内网跳板机执行 / 只 scp 二进制) | | **kubeconfig 是否存在** | 打印实际查找路径(`$KUBECONFIG` 或 `~/.kube/config`),给出腾讯云 TKE / 阿里云 ACK / AWS EKS 各自下载 kubeconfig 的入口,以及 `chmod 600` 建议 | | **能否连上 API Server** | **按错误类型分类**(见下表),不是笼统一句「请检查 kubeconfig」 | | **部署权限(软检查)** | `kubectl auth can-i create deployments.apps -n ` 非 `yes` 时告警,提示绑定 Role 或改用管理员 kubeconfig;**不阻断**,避免首次部署时命名空间尚未创建导致误拦 | ### 连不上集群时的分类提示 | 实际错误 | 脚本的判断 | 给出的处理方向 | |---------|-----------|---------------| | `current-context is not set` / `context does not exist` | kubeconfig 没有可用 context | `kubectl config get-contexts` → `use-context` → 重新下载 kubeconfig | | `Unauthorized` / `Authentication required` | 认证失败(云厂商 token 几小时就过期) | 回控制台重新下载 kubeconfig;自建集群查客户端证书 | | `Forbidden` / `403` | 已连上集群但 RBAC 拒绝 | `kubectl auth whoami`、`auth can-i --list -n `,列出部署所需的具体资源权限 | | `x509` / `certificate` / `tls:` | 证书过期或域名不匹配 | 确认 API Server 证书;可信内网可临时 `--insecure-skip-tls-verify` 排障 | | `connection refused` / `no such host` / `context deadline exceeded` / `i/o timeout` | 网络不通 | ①`curl -k /version` ②**内网地址需 VPN/专线** ③安全组放行 6443 ④查集群状态 ⑤换成跳板机试 | 失败时的完整输出形如: ```text ❌ 连不上集群 API Server(网络不通) kubeconfig : /root/.kube/config 当前 context : tke-pre API Server : https://172.19.16.13:443 当前用户 : admin 实际报错: Unable to connect to the server: context deadline exceeded (...) ── 按顺序排查 ─────────────────────────────────────────────────────── 1) 地址对不对?用上面打印的 API Server 测一下: curl -k --max-time 5 /version 2) 要不要 VPN / 专线?云上 API Server 常是内网地址(10.x / 172.16~31.x / 192.168.x)... 3) 安全组 / 防火墙:云厂商默认只放行运维网段访问 6443 端口 4) 集群本身是否正常:控制台看集群状态是否「运行中」 5) 换台机器试:在能访问集群的跳板机上执行本脚本,可排除本机网络问题 ``` > 预检只读不写、耗时 < 1 秒。API Server 连接超时可用 `KUBECTL_TIMEOUT=15s` 放宽(默认 8s)。 ## 脚本自动处理什么 `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` | --- ## 集群凭证的最小权限(「资源」面板相关) 部署卡片上的「资源」按钮读的是**部署目标集群**的用量。平台里登记集群的 kubeconfig/token 可以是**最小权限**,只需下面几条: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: lokistack-viewer namespace: log # 只授 Loki 所在的命名空间即可 rules: # 读 StatefulSet 的 requests/limits - apiGroups: ["apps"] resources: ["statefulsets"] verbs: ["get"] # 读 Pod 就绪状态、重启次数 - apiGroups: [""] resources: ["pods"] verbs: ["get", "list"] # 读实时 CPU/内存用量(metrics-server 的 API 组,常被漏授) - apiGroups: ["metrics.k8s.io"] resources: ["pods"] verbs: ["get", "list"] ``` > ⚠️ `metrics.k8s.io` 是**独立 API 组**,很多「只读」ClusterRole(如内置 `view`)在国内云 > 的 TKE/ACK 上并不包含它。缺这一条时: - 页面**不会报错**,仍显示 limits、就绪状态、重启次数; - 实时用量区域会提示「当前集群凭证缺少 metrics.k8s.io 权限」并给出需要补的规则; - 补授后无需重启平台,刷新弹框即可看到用量。 集群若**没装 metrics-server**(`kubectl top` 报 `Metrics API not available`), 提示会变成「集群未安装 metrics-server」。 ### 非 Kubernetes 部署 | 模式 | 采集方式 | 能力 | |------|---------|------| | `host-docker`(docker / docker-compose) | SSH 到主机执行 `docker stats --no-stream` + `docker inspect` | 实时 CPU/内存、限额(若 `docker run --memory/--cpus` 有设)、重启次数 | | `host-binary`(裸进程) | SSH 执行 `ps` 采集进程 CPU%/RSS | 仅实时占用(无 cgroup 限额,无峰值) | Docker 模式下若容器**没有**设置 `--memory` / `--cpus`,页面会显示「未配置资源限额」, 容量参考值是宿主机内存(`docker stats` 的上限列),此时没有百分比可用于判断扩容。