Files
opsx bad2a1e624 fix(hosts,collectors,usage): 主机验证结果失真 + 主机模式系统日志解析 + metrics 单位
=== 一、主机「测试连接」永远显示旧错误(真实 bug)===
HostService.Validate 的失败分支也写 status=active,且从不写 statusMessage:
    if info, err := client.GetHostInfo(ctx); err == nil { 更新 os/arch }
    else { s.Update(id, {status:"active"}) }   // ← 失败也标记成功
结果:连不上也显示正常,页面永远停在几分钟前的旧错误文案,
用户改了 IP 再点测试,还以为"改了还是不行"。

改为区分成功/失败并写清原因:
    成功 → status=active + os/arch + statusMessage="连接成功"
    失败 → status=error + statusMessage=FormatSSHError(err)

=== 二、回环地址在平台里必然连不上 ===
平台跑在容器/Pod 内,127.0.0.1 指向平台自身。
- 新建主机时拦截:hostIP 为 127.0.0.1/localhost/::1 直接报错并解释原因
- 前端表单同步校验 + 文案
- SSH 错误映射补充:连接被拒绝时提示可能是地址填了回环

=== 三、主机模式采集系统日志(两处会导致采集失败)===
1. pipeline 一律套 cri 阶段:cri 只认容器运行时格式,
   套在 /var/log/messages 这类 syslog 上会取不到时间戳、整行被丢。
   → appPipelineStages() 按路径判断:容器日志用 cri,普通文本日志只做限流。
2. promtailExternalLabels 写死 8 空格缩进:主机模板 external_labels 只有 4 空格,
   一旦给采集器配置了标签就生成非法 YAML,Promtail 装不上。
   → 增加 indent 参数,主机 6 空格 / DaemonSet 8 空格。
3. 新增 system 采集任务:
   /var/log/{messages,secure,cron,maillog,syslog,dmesg}
   正则解析 syslog 时间戳(Jan _2 15:04:05,Asia/Shanghai)+ 限流

测试:TestHostPromtailConfigValid / CRIPathKeepsCRIStage / PlainFileHasNoCRIStage
      TestPromtailManifestParses 全部通过

=== 四、资源用量 CPU 解析(nanocores)===
metrics-server 返回的 CPU 常是纳核(如 "194533645n"=194.5m)。
原解析只处理 "m",导致 194 亿核、页面出现"CPU 已达 limit 的 4863341125%"。
→ parseQuantityMilli 支持 n/u/m/整核
实测:CPU 当前=178m,峰值=3300m,建议"峰值占 limit 82%,可考虑提前扩容"

=== 五、其他 ===
- 添加集群默认改用 Token 认证(最小权限、可随时吊销)
- RBAC 片段补 metrics.k8s.io(资源面板需要,独立 API 组常被漏授)
- 文案去掉 Grafana 需求描述(平台自身不部署 Grafana)

镜像:lghost/lokistack:latest + v0.1 = sha256:7bddd0f4d1f1
2026-10-28 17:58:30 +00:00

20 KiB
Raw Permalink Blame History

lokistack 部署到 Kubernetes

命名空间固定:log(日志相关资源统一放这里)


一条命令部署(推荐)

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 时):

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 <ns> 非 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 <ns>,列出部署所需的具体资源权限
x509 / certificate / tls: 证书过期或域名不匹配 确认 API Server 证书;可信内网可临时 --insecure-skip-tls-verify 排障
connection refused / no such host / context deadline exceeded / i/o timeout 网络不通 ①curl -k <API>/version ②内网地址需 VPN/专线 ③安全组放行 6443 ④查集群状态 ⑤换成跳板机试

失败时的完整输出形如:

❌ 连不上集群 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 <API Server>/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:

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(自建集群事实标准):

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)

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 时,数据在宿主机目录里,不在云盘上。下面这些操作要会。

数据在哪

# 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

备份(不用停机)

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。

迁移到另一台节点

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

卸载但保留数据

# 删工作负载和服务(数据卷保留)
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,供需要精细控制时用)

# 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。


访问

临时(本地端口转发):

kubectl -n log port-forward svc/lokistack 8080:8080
# 浏览器打开 http://localhost:8080

初始账号:admin / admin123 ← 首次登录请立即改密


配 Ingress(可选)

以腾讯云 TKE 为例(其他云见 examples/):

# 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):

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 统一轮转,节点级配置建议:

    # /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 可以是最小权限,只需下面几条:

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 的上限列),此时没有百分比可用于判断扩容。