哪吒监控容器化部署:Docker Compose与Kubernetes实战 0. 开头哪吒监控Nezha Monitoring这套东西我在三四个不同规模的机房里都落地过小到一台 VPS 加两个探针大到二十多个节点的集群。它本质上是「一个中心面板 若干被监控端探针」的架构面板负责收集数据、渲染图表、触发告警探针负责把 CPU、内存、磁盘、网络、负载这些指标按固定周期回传。容器化部署这件事说难不难说简单也真不简单——真正让你熬夜的从来不是docker run那行命令而是 gRPC 端口被前置网关吞掉、SQLite 文件没挂出来、探针在容器里读到的是容器自己的指标而不是宿主机的。这篇内容我要把两件事讲透一是用 Docker Compose 在单机或小规模环境下把哪吒监控跑起来二是用 Kubernetes 把它做成一套能覆盖整个集群节点的监控体系。适合谁看手上有几台服务器想做统一监控的运维、刚接触 Kubernetes 想找个真实小项目练手的开发、以及已经在用哪吒但被容器化后的各种怪问题折磨过的朋友。文中的参数、配置、目录结构都是我实际跑过的形态涉及具体数值的地方会给你算清楚为什么这么定。先说结论性的判断单机 1 到 10 台机器的规模Docker Compose 是性价比最高的方案节点数超过 15 台或者你本身就在 Kubernetes 上跑业务那就直接上 Kubernetes用 DaemonSet 把探针铺到每个节点省掉一台台手工安装的重复劳动。下面按照这个思路展开。1. 整体架构拆解与容器化思路1.1 哪吒监控到底由哪几块组成很多人第一次接触哪吒以为它就是一个网页装完就完事。实际上它至少由三块东西构成而这三块在容器化时的处理方式完全不同。第一块是面板服务Dashboard。它是一个 Go 编译的单体二进制内置了 Web 服务、API、数据写入逻辑和告警引擎。它监听两个端口一个是给浏览器访问的 HTTP 端口默认 8008另一个是给探针上报数据用的 gRPC 端口默认 8009。这两个端口的性质不一样HTTP 端口可以走七层网关做域名转发gRPC 端口是长连接 HTTP/2很多网关默认不支持这是后面最容易踩坑的地方。第二块是探针端Agent。同样是一个 Go 二进制跑在被监控的机器上采集指标后通过 gRPC 长连接回传给面板。它的关键特点是必须能读到宿主机的真实信息——CPU 核数、内存总量、磁盘挂载点、网卡流量、系统发行版。这一点直接决定了容器化探针时必须开hostNetwork、hostPID并挂载/proc、/sys这些宿主机路径否则你看到的永远是一个假的容器指标。第三块是数据存储。默认用 SQLite文件落在面板的数据目录里。数据量小时完全够用如果你的探针数量在 100 以内、指标保留周期是默认的SQLite 撑得住。规模再大就要换 MySQL 或 PostgreSQL这就涉及到连接串的配置和网络互通问题。三块的划分决定了容器化的核心矛盾面板是有状态的探针是无状态但需要特权视角的。理解这一点后面所有配置选择都能推导出来。1.2 为什么要容器化以及容器化会带来什么代价容器化的收益很明确环境一致、升级简单、回滚快、配置即代码。哪吒的版本迭代不算慢数据库结构偶尔会有迁移用容器的话升级就是改个镜像标签再重启出问题把标签改回去就行比手动替换二进制文件再加 systemd 重启要干净得多。但代价也必须摆到台面上讲。第一数据持久化变成了你的责任。裸机安装时数据就在本地目录里容器化之后如果你忘了挂卷容器一删数据全没而且很多人是在跑了一周、积累了一堆历史图表之后才发现的。第二网络模型变复杂。面板和探针之间要通 gRPC容器网络的 NAT、端口映射、Service 的转发规则都会影响这条链路出问题时你会发现排查路径比裸机长得多。第三探针的宿主机视角需要显式授权。hostNetwork和hostPID这些配置不是可选项是必选项安全边界需要你自己评估。所以我的做法是单机场景用 Compose 把面板和数据卷管好探针尽量用宿主机二进制或者带完整特权配置的容器Kubernetes 场景用 StatefulSet 管面板和数据用 DaemonSet 铺探针让集群自己帮你做调度和重启。1.3 部署形态选型对照选型不看情怀看规模下面这张表是我自己做决策时用的判断依据你可以直接对照自己的场景。场景特征推荐形态核心理由主要代价1 至 5 台机器个人或小团队Docker Compose 单机一条命令起停备份就是拷目录面板单点宿主机挂了监控就没了5 至 15 台有独立监控机Docker Compose 独立数据盘成本低升级路径短需要自己写定时备份15 台以上或已在 K8s 上Kubernetes StatefulSet DaemonSet探针自动覆盖新节点无需手工装gRPC 暴露方式需要额外设计对告警时效要求高任意形态 双面板冗余避免面板挂了没人知道数据一致性需要额外处理通常不建议表格里最后一行我要多解释一句很多人想给面板本身做高可用但哪吒的 SQLite 是单文件多副本共享同一份数据会直接损坏。真要做冗余正确思路是两台独立面板各自跑探针分别上报而不是让两个面板实例挂同一个卷。这个坑我见过有人踩数据文件锁冲突之后面板直接起不来。2. 核心参数与配置细节拆解2.1 面板端的启动参数与数据目录规划面板容器的配置项其实不多但每一个都影响后续运维。核心是三组监听端口、数据目录、通信密钥。监听端口方面HTTP 端口默认 8008gRPC 端口默认 8009。我的习惯是把这两个端口都显式写进编排文件不依赖默认值因为默认值在你将来跑多个实例或者换端口时会让你抓瞎。端口映射时要注意gRPC 端口必须是 TCP 直通不能经过任何做七层解析的环节否则探针连接会握手失败。数据目录是重中之重。哪吒的数据文件集中在容器内的/opt/nezha下面包括 SQLite 数据库、上传的图标、配置缓存。容器化时必须把这个目录映射到一个宿主机路径或者命名卷。我一般用宿主机绑定挂载而不是命名卷原因是备份和迁移更直观——tar一下目录就能搬走用命名卷的话还得先起个临时容器去导。密钥部分要单独强调。面板和探针之间靠一个共享密钥做身份校验这个密钥在面板初始化时生成或由你指定。容器化环境下这个值必须通过环境变量或配置文件注入绝对不能写死在镜像里也不要提交到代码仓库。Kubernetes 场景下用 Secret 存Compose 场景下用.env文件存并加进.gitignore。下面是我常用的一份 Compose 编排骨架参数都做了显式声明services: dashboard: image: 面板镜像:版本标签 container_name: nezha-dashboard restart: unless-stopped ports: - 8008:8008 - 8009:8009 volumes: - ./data:/opt/nezha - /etc/localtime:/etc/localtime:ro environment: - TZAsia/Shanghai - NZ_SERVER面板对外地址:8009 - NZ_CLIENT_SECRET你的密钥 logging: driver: json-file options: max-size: 10m max-file: 3最后那段日志配置看着不起眼但很实用。哪吒面板的访问日志量不算小默认的 Docker 日志驱动不限制大小跑上几个月能把磁盘写满。限制单文件 10MB、最多 3 个一共 30MB 封顶足够排查问题了。2.2 探针的接入逻辑与容器化差异探针的启动逻辑比面板简单但容器化之后的差异比面板大得多。裸机跑探针时它直接读取/proc/stat、/proc/meminfo、/proc/net/dev这些文件拿到的就是真实数据。容器里跑默认只能看到容器自己的命名空间CPU 是容器的配额、内存是容器的限制、网卡是虚拟网卡上报上去的数据全错。解决办法是三条一起上hostNetwork: true让容器共享宿主机网络命名空间网卡流量才对hostPID: true让容器能看到宿主机的进程列表进程数指标才对挂载/proc、/sys/class、/etc/os-release这些路径让探针读到真实的硬件信息和系统标识。还有一个容易被忽略的点磁盘使用率。探针统计磁盘时依赖挂载点列表容器里如果没有把宿主机的真实挂载点暴露出来它会认为只有容器根文件系统一个盘。所以要么挂载/到容器内的某个路径要么用--volumes-from之类的机制把宿主机的挂载信息带进去。这块在 Compose 里就是多写几行volumes在 Kubernetes 里就是 DaemonSet 里多几条hostPath。2.3 数据库与端口规划的实际计算先算端口。假设你的面板要监控 N 台机器每台机器每秒上报一次指标默认间隔通常比这个大这里按最坏情况估算每次上报的数据包在 2KB 到 5KB 之间。100 台机器的话gRPC 端口每秒要处理大约 500KB 的入站流量折合 4Mbps。这个量对任何一台像样的机器都不算什么所以瓶颈不在带宽在连接数管理。每个探针维持一条长连接100 台就是 100 条Go 的服务端处理起来毫无压力。再算存储。SQLite 单条记录的写入成本很低但索引和 WAL 日志会占空间。按经验值单台探针一天的原始指标大概在 2MB 到 8MB 之间取中间值 5MB。50 台机器跑 30 天就是 7.5GB。如果你设置了数据保留策略这个数会小很多。我的建议是磁盘预留至少是估算值的 3 倍给迁移和临时膨胀留余量。50 台机器就准备 25GB 以上的数据目录空间。数据库选型上探针数在 100 以内、保留周期 30 天以内SQLite 完全够用且免运维。超过这个规模换成外置数据库更合适但要注意容器网络里访问外置数据库的连通性问题连接串里的地址不能写localhost得写宿主机的可达地址或者 Kubernetes 里的 Service 名。2.4 入口转发与 gRPC 的特殊处理这是整个部署里最容易翻车的地方我要单独拎出来说。HTTP 端口 8008 用 Nginx 做七层转发没有任何问题配个proxy_pass就行。但 8009 的 gRPC 端口不行原因在于 gRPC 跑在 HTTP/2 之上而且是长连接普通 Nginx 转发会把它当成 HTTP/1.1 处理握手阶段就断开了。三种正确处理方式按推荐度排序第一种gRPC 端口直接暴露不经过任何网关。用防火墙限制来源 IP只允许已知的探针机器访问。这是最简单也最不容易出问题的方案代价是端口直接对外需要靠防火墙兜底。第二种用支持 gRPC 的网关做四层转发。Nginx 从某个版本之后支持grpc_pass指令但要求上游是 HTTP/2配置里需要显式声明grpc_pass grpc://面板地址:8009;并且监听端口要开http2。这种方案能复用 443 端口但配置写错一个字符就完全不生效排查起来很痛苦。第三种探针侧使用 TLS 直连。面板配置证书后探针通过加密通道连 8009网关做纯 TCP 转发即可不解析内容。这种方式兼顾了安全和简单我在生产环境用得最多。提醒一句不管用哪种方式改完之后一定要用探针实际测一次连通性别只看端口通不通。端口通但 gRPC 握手失败的情况太常见了表现就是探针进程活着但面板上显示离线。3. Docker 单机部署完整实操3.1 目录规划与初始化我习惯给每个自建服务一个独立目录结构固定这样备份脚本可以统一处理。哪吒的目录我一般这么规划mkdir -p /opt/stacks/nezha/{data,backup,logs} cd /opt/stacks/nezha touch .env docker-compose.yml chmod 600 .env.env文件权限设成 600 是基本操作里面存的是密钥。内容大致是密钥、面板地址、时区这几项NZ_CLIENT_SECRET随机生成的长字符串 NZ_SERVERmonitor.example.internal:8009 TZAsia/Shanghai密钥怎么生成用openssl rand -hex 32出一条 64 位的十六进制串就够了别用生日、别用项目名。这个值一旦定了就别随便改改了之后所有探针都得重新配置等于重装一遍。data目录是面板的持久化落点权限要提前处理好。容器里的进程用户 UID 和宿主机可能不一致最省事的做法是先chown成容器内运行用户的 UID或者干脆给 755 让容器自己初始化。我第一次部署时就是因为宿主机目录是 root 所有、容器内是非 root 用户面板启动后写数据库直接报权限错误日志里只写了个unable to open database file排查了半小时才发现是权限问题。3.2 面板启动与初始化验证编排文件写好后docker compose up -d起服务然后用三步验证。第一步看容器状态docker compose ps应该是running且healthy如果你配了健康检查。第二步看日志docker compose logs -f --tail100 dashboard正常启动的日志里会有监听端口的输出和数据库初始化信息如果出现permission denied或者address already in use那就是权限或端口冲突。第三步用curl打一下 HTTP 端口返回 200 或者重定向到登录页就说明 Web 服务正常。curl -I http://127.0.0.1:8008/ # 期望看到 200 或 302gRPC 端口没法用 curl 直接测但可以用nc确认端口是监听状态ss -lntp | grep 8009第一次访问面板会要求创建管理员账号。这里有个细节账号密码创建后是存在数据库里的如果你后面把data目录删了重新起账号会重新要求创建。所以创建完账号之后第一件事就是做一次备份别等到配置了一堆告警规则才想起来。3.3 探针部署本机与跨机的差异处理本机部署探针最省事的方式其实是不用容器直接把二进制扔在宿主机上跑用 systemd 管起来。理由很简单本机探针需要读宿主机的全部信息用容器反而要多写一堆特权配置收益不大。但如果你的原则是所有服务都容器化那就按下面这套配置来services: agent: image: 探针镜像:版本标签 container_name: nezha-agent restart: unless-stopped network_mode: host pid: host volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /etc/os-release:/host/etc/os-release:ro - /:/host/root:ro environment: - NZ_SERVERmonitor.example.internal:8009 - NZ_CLIENT_SECRET同一个密钥 - TZAsia/Shanghainetwork_mode: host和pid: host是两个必须项。少任何一个你看到的指标就是错的而且错得很有迷惑性——数据能上报图表能画出来但数值和实际对不上你可能要过很久才发现。跨机部署就简单多了网络是通的探针只要知道面板的 gRPC 地址和密钥就行。这时候我会用面板自带的一键安装脚本生成命令把生成的命令在目标机器上执行即可。脚本里带的是面板地址和密钥注意别把这个命令贴到公开的地方密钥泄露等于任何人都能往你的面板里塞假数据。3.4 连通性排查的固定动作探针装完后别急着看图表先做三件事确认链路。第一件从探针机器上测 gRPC 端口的 TCP 连通性nc -zv 面板地址 8009。通了再往下走不通就是防火墙或者端口映射的问题跟哪吒本身没关系。第二件看探针容器日志里有没有连接成功的记录。成功的话日志里会有注册或心跳相关的输出失败的话通常是context deadline exceeded或者connection refused前者多半是网络不通后者是端口没监听。第三件回到面板看节点列表。如果探针日志显示连上了但面板不显示节点那就是密钥不匹配或者探针注册被拒绝检查两边的密钥是不是完全一致注意行尾空格这个坑我踩过。4. Kubernetes 部署实操4.1 命名空间与配置资源准备Kubernetes 上的部署我建议从干净的命名空间开始避免和业务资源混在一起。kubectl create namespace monitoring配置资源分三类ConfigMap 存非敏感配置时区、端口、对外地址Secret 存密钥通信密钥、数据库密码PVC 存数据。Secret 的创建方式我一般用命令行从文件创建而不是写在 YAML 里提交到仓库kubectl -n monitoring create secret generic nezha-secret \ --from-literalclient-secret$(openssl rand -hex 32)这样密钥只在集群里存在不会落到代码库里。如果用 GitOps 流程那就得配套的外部密钥管理方案这里不展开。4.2 面板用 StatefulSet 而不是 Deployment为什么不用 Deployment因为面板是有状态服务数据落在 PVC 上PVC 默认是ReadWriteOnce同一时刻只能被一个节点挂载。Deployment 滚动更新时会先起新 Pod 再停旧 Pod两个 Pod 同时想挂同一个 PVC第二个就会卡在ContainerCreating状态滚动更新永远完成不了。StatefulSet 配合volumeClaimTemplates就没这个问题它的更新策略是逆序逐个重建天然适配独占卷。再加上strategy配置成RollingUpdate升级时是有序的。核心片段apiVersion: apps/v1 kind: StatefulSet metadata: name: nezha-dashboard namespace: monitoring spec: serviceName: nezha-dashboard replicas: 1 selector: matchLabels: app: nezha-dashboard template: metadata: labels: app: nezha-dashboard spec: containers: - name: dashboard image: 面板镜像:版本标签 ports: - name: http containerPort: 8008 - name: grpc containerPort: 8009 envFrom: - configMapRef: name: nezha-config - secretRef: name: nezha-secret volumeMounts: - name: data mountPath: /opt/nezha readinessProbe: httpGet: path: / port: http initialDelaySeconds: 10 periodSeconds: 15 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 1000m memory: 512Mi volumeClaimTemplates: - metadata: name: data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 20Gi资源请求那部分别照抄按你的探针数量和指标保留周期算。50 台探针、保留 30 天的话内存 512Mi 上限是够的CPU 在指标聚合时会有短时冲高1 核上限能扛住。存储 20Gi 对应前面算的 25GB 再打点折扣如果存储类支持扩容建议先给小一点再按需扩。4.3 Service 与 gRPC 入口的三种设计Service 要建两个一个给 HTTP一个给 gRPC或者建一个带双端口的 Service 也行但分开更清晰。apiVersion: v1 kind: Service metadata: name: nezha-http namespace: monitoring spec: type: ClusterIP selector: app: nezha-dashboard ports: - name: http port: 8008 targetPort: http --- apiVersion: v1 kind: Service metadata: name: nezha-grpc namespace: monitoring spec: type: NodePort selector: app: nezha-dashboard ports: - name: grpc port: 8009 targetPort: grpc nodePort: 30809HTTP 走 ClusterIP前面用 Ingress 接域名这是标准做法。gRPC 我选 NodePort 而不是 Ingress理由是前面说的那个道理Ingress 控制器对 gRPC 的支持取决于具体实现和版本配错了排查成本高。NodePort 虽然暴露范围大一点但用网络策略或者主机防火墙限制来源后完全可控而且稳定可预测。如果你的集群一定要走 Ingress那 Ingress 资源里必须显式声明后端协议否则会被当成普通 HTTP 处理metadata: annotations: nginx.ingress.kubernetes.io/backend-protocol: GRPC这个注解少写或者写错表现就是探针一直连不上而 Ingress 控制器日志里可能什么都不报非常难查。4.4 探针用 DaemonSet 覆盖全集群这是 Kubernetes 方案最大的价值点新节点加入集群后DaemonSet 会自动在上面起一个探针 Pod不需要你登录任何一台机器。apiVersion: apps/v1 kind: DaemonSet metadata: name: nezha-agent namespace: monitoring spec: selector: matchLabels: app: nezha-agent template: metadata: labels: app: nezha-agent spec: hostNetwork: true hostPID: true dnsPolicy: ClusterFirstWithHostNet tolerations: - operator: Exists containers: - name: agent image: 探针镜像:版本标签 env: - name: NZ_SERVER value: nezha-grpc.monitoring.svc.cluster.local:8009 - name: NZ_CLIENT_SECRET valueFrom: secretKeyRef: name: nezha-secret key: client-secret volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: sys mountPath: /host/sys readOnly: true - name: os-release mountPath: /host/etc/os-release readOnly: true volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys - name: os-release hostPath: path: /etc/os-release几个关键配置的解释。hostNetwork: true之后Pod 的 DNS 解析会退化成宿主机的解析器这时候必须配dnsPolicy: ClusterFirstWithHostNet否则 Pod 解析不了nezha-grpc.monitoring.svc.cluster.local这个集群内域名探针会一直连不上——这个坑非常隐蔽因为 Pod 是 Running 状态日志里只有超时。tolerations里那个operator: Exists允许探针被调度到有污点的节点上通常控制面节点带污点但你也想监控它们所以需要这个容忍。如果你不希望监控控制面把这行去掉。还有一点DaemonSet 的探针上报的节点名默认是主机名在 Kubernetes 里就是节点名这样面板上的节点列表和kubectl get nodes能对上很直观。如果对不上检查一下容器内的 hostname 是不是被覆盖了。4.5 滚动更新与健康检查的设计面板的滚动更新在 StatefulSet 下是有序的但因为replicas: 1更新期间会短暂不可用。探针这边有重连机制短暂断开后会自动重连所以能接受。如果你完全不能容忍中断那就得在前面加一层把请求导到另一个独立的面板实例这属于架构层面的冗余不是单靠 K8s 能解决的。健康检查我用了 HTTP 的 readinessProbe只检查根路径能不能返回。没有配 livenessProbe 是有意为之面板进程偶尔会因为在做数据整理而短暂响应慢liveness 探针配得激进会导致容器被反复重启反而更糟。如果一定要配initialDelaySeconds给足 30 秒以上failureThreshold设到 5 次以上。5. 常见问题与排查实录5.1 面板正常但探针显示离线的排查路径这是最高频的问题我整理了一套从外到内的排查顺序按这个走基本能定位。第一步确认探针进程是否活着。docker ps或者kubectl get pod看状态如果是Restarting先看日志里的退出原因通常是配置项缺失导致启动即退出。第二步确认网络连通性。从探针所在机器测面板的 gRPC 端口nc -zv或者telnet。不通就是这个层面的问题跟哪吒无关。第三步确认密钥一致。把两边配置里的密钥打印出来对比长度和内容注意有没有多余的空格或者换行。密钥不一致时探针日志里通常会有鉴权失败的提示。第四步确认时间同步。这条很多人想不到探针和面板的时间偏差超过一定范围某些校验会失败。容器里的时区配置不对、宿主机 NTP 没同步都会导致这个问题。检查两边的date输出。第五步看面板侧的日志。如果探针侧一切正常但面板侧有拒绝连接的记录那可能是面板的监听地址绑错了比如只绑了127.0.0.1而不是0.0.0.0容器外就连不上。5.2 gRPC 经过网关后的典型故障现象这个问题的症状很有辨识度探针日志反复出现连接超时或者握手失败但端口用nc测是通的。这就说明 TCP 层没问题问题出在应用层协议上。原因通常是网关把 gRPC 的 HTTP/2 流量降级处理了。判断方法是在网关的访问日志里看请求的协议版本如果显示的是 HTTP/1.1 而不是 HTTP/2那就实锤了。处理方式我在第 2.4 节讲过三种。这里补一个实操细节如果你用的是 Nginx 且必须走它转发 gRPC配置里除了grpc_pass还要确保listen指令带http2参数并且上游地址的协议前缀写对http 还是 https。这三处任一处错了都不生效而且 Nginx 不会报配置错误只会默默转发失败。5.3 数据丢失与备份的正确姿势SQLite 在 WAL 模式下的备份有个陷阱直接cp数据库文件可能拿到一个不一致的快照因为部分数据还在 WAL 日志里没合并。正确的三种做法按可靠性排序第一种停容器再拷。docker compose stop dashboard拷完data目录再start。缺点是有一小段监控空窗但对大多数场景可以接受。第二种用 SQLite 的在线备份命令。容器里如果带了sqlite3命令行工具执行.backup /path/to/backup.db能拿到一致性快照不需要停服务。这是我首选的方式写成定时任务每天跑一次。第三种用流式复制工具做持续备份。适合对数据可靠性要求很高的场景但配置成本高小规模没必要。备份策略上我的习惯是每天一次全量、保留 7 天、每周再做一次归档保留 4 周。备份文件放在另一块盘或者对象存储上别和原数据放同一个目录——这个道理都懂但我见过把备份放在同目录然后磁盘满了把备份一起撑爆的案例。5.4 告警风暴与资源占用的实际调优面板刚上线时很容易出现告警风暴某个指标阈值设得太紧几十台机器同时触发告警通道被打爆。处理思路分两层。第一层是阈值调优把静态阈值换成基于历史数据的相对阈值或者给告警加个持续时长条件比如连续 5 分钟超过阈值才告警能过滤掉大量瞬时抖动。第二层是告警去重与聚合同一类告警在时间窗口内合并成一条避免刷屏。资源占用方面面板的 CPU 消耗主要在指标聚合和图表查询时。如果发现面板 CPU 长期高位先看是不是有查询频率很高的看板被反复刷新其次是检查数据保留周期是不是设得太长。把明细数据保留 7 天、聚合数据保留 90 天是个比较平衡的配置。下面是常见问题速查表出问题时可以直接对照现象最可能原因快速验证处理方式探针 Running 但面板离线密钥不一致或域名解析失败对比密钥长度容器内nslookup面板域名统一密钥补dnsPolicy探针上报的 CPU 核数不对未开hostPID或未挂载/proc容器内nproc与宿主机对比补特权配置和挂载gRPC 端口通但连不上网关降级了 HTTP/2网关日志看协议版本改四层转发或直连面板启动报数据库打不开数据目录权限不对ls -ln data看属主chown成容器用户 UID磁盘使用率显示 100%未暴露宿主机挂载点容器内df -h对比挂载宿主机根路径StatefulSet 更新卡住旧 Pod 未释放 PVCkubectl describe pod看事件确认ReadWriteOnce与副本数历史图表突然空白数据文件损坏或备份覆盖查面板日志有无 SQL 错误从备份恢复检查写入权限6. 一些踩过坑之后的经验最后分享几个不在文档里、但实际部署时很容易撞上的细节。关于镜像标签别用latest。哪吒的版本迭代里偶尔会有数据库结构变更从旧版直接跳到最新版可能触发迁移失败。固定一个明确版本号升级前先在测试环境跑一遍确认数据迁移没问题再动生产。我现在的做法是镜像标签精确到小版本比如v1.x.y这种粒度升级一次改一次。关于时区容器默认是 UTC面板上的图表时间轴会和你看到的时间差 8 小时。这个不是 bug但会让排查问题时产生误判。挂载/etc/localtime或者设TZ环境变量二选一我一般两个都做双保险。关于端口冲突宿主机上如果已经有服务占了 8008 或 8009容器会起不来。起来之前先ss -lntp扫一眼能省掉一次无效部署。关于 Kubernetes 里的探针更新DaemonSet 更新时是逐个节点替换的maxUnavailable默认是 1节点多的话更新会慢。可以调到 10%代价是更新期间有一小部分节点短暂失联但这期间监控数据的缺口通常可以接受。这套方案我在不同规模下跑了挺久面板侧几乎不需要日常干预真正需要盯的是磁盘空间和备份是否正常执行。把这两件事做成定时任务加告警剩下的基本可以交给它自己跑。