技术排错文档:Dify on Kubernetes (Fedora) 部署失败
技术排错文档:Dify on Kubernetes (Fedora) 部署失败
1. 概述 (Executive Summary)
本文档记录了一次在 Kubernetes (K8s) 集群上部署 Dify 应用(使用 Helm)的完整排错过程。最初的症状是 Dify 的核心 Pod(api, worker)无法启动,状态为 Pending。
排错过程揭示了三个层层递进的根本问题:
-
存储模式冲突:Dify Helm Chart 默认请求
ReadWriteMany(RWX) 存储,而集群的默认StorageClass(local-path) 只支持ReadWriteOnce(RWO)。 -
系统资源耗尽:在部署 NFS 解决方案时,由于 Kubernetes
kubelet消耗了宿主机(Fedora 42)上所有的inotify资源,导致systemd无法管理nfs-server服务。 -
Helm 配置覆盖失效:在修复了 NFS 问题后,错误的
values.yaml文件导致 Helm 覆盖配置失败,安装时仍在使用错误的默认StorageClass。
本文档详细记录了每个问题的症状、诊断和最终解决方案。
2. 问题一:Dify Pod Pending (RWX vs. RWO 冲突)
2.1 症状
-
kubectl get pods -n dify显示dify-api、dify-worker、dify-plugin-daemon等 Pod 状态为Pending。 -
kubectl get pvc -n dify显示对应的dify和dify-plugin-daemonPVC 状态为Pending。 -
kubectl describe pvc dify -n dify的Events中显示:Warning ProvisioningFailed ... failed to provision volume with StorageClass "local-path": NodePath only supports ReadWriteOnce and ReadWriteOncePod ... access modes
2.2 根本原因
Dify 的多个 Pod(如 api 和 worker)需要共享同一个存储卷来读写数据。因此,Helm Chart 为这个卷请求了 ReadWriteMany (RWX) 访问模式。
然而,集群的默认 StorageClass (local-path) 是一种本地路径存储,其数据存储在单一节点的磁盘上,天然无法被多个节点共享,因此它只支持 ReadWriteOnce (RWO)。
冲突点: 应用请求了 RWX,而存储只能提供 RWO。
2.3 解决方案
必须提供一个支持 ReadWriteMany (RWX) 的 StorageClass。我们选择在集群宿主机(r430 / Fedora 42)上搭建一个 NFS 服务器。
-
在 Fedora 主机上安装 NFS 服务
sudo dnf install -y nfs-utils -
创建并配置共享目录(我们使用了
/opt/nfs/kubedata)sudo mkdir -p /opt/nfs/kubedata sudo chown -R nobody:nobody /opt/nfs/kubedata sudo chmod -R 777 /opt/nfs/kubedata -
编辑
/etc/exports,添加配置(注意no_root_squash至关重要):/opt/nfs/kubedata 192.168.6.0/24(rw,sync,no_subtree_check,no_root_squash) -
配置防火墙
sudo firewall-cmd --permanent --add-service=nfs sudo firewall-cmd --permanent --add-service=rpc-bind sudo firewall-cmd --permanent --add-service=mountd sudo firewall-cmd --reload -
启动 NFS 服务
sudo systemctl enable --now nfs-server sudo systemctl enable --now rpcbind -
在 Kubernetes 中部署 NFS Provisioner
helm repo add nfs-subdir-external-provisioner [https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/](https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/) kubectl create namespace nfs-provisioner helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ --namespace nfs-provisioner \ --set nfs.server=192.168.6.166 \ --set nfs.path=/opt/nfs/kubedata \ --set storageClass.name=nfs-client
3. 问题二:NFS Provisioner Pending (Inotify 资源耗尽)
3.1 症状
-
kubectl get pods -n nfs-provisioner显示nfs-subdir-external-provisionerPod 状态卡在Pending或ContainerCreating。 -
在 Fedora 主机上尝试重启 NFS 服务时,
systemd报错:Failed to allocate directory watch: Too many open files
3.2 根本原因
这是一个**"宿主机"与"K8s节点"角色冲突**的经典问题。
-
Kubernetes 的核心组件
kubelet会创建海量的inotify监视器来跟踪 Cgroups、配置、卷挂载等。 -
inotify是 Linux 内核提供的功能,但它有资源上限(max_user_watches和max_user_instances)。 -
kubelet及其管理的容器耗尽了主机上所有的inotify资源配额。 -
systemd(系统服务管理器)也需要inotify来监视和管理服务(如nfs-server)。 -
当
systemd尝试重启nfs-server时,它无法分配新的inotify监视器,因此操作失败。 -
nfs-server服务无法正常运行,导致kubelet无法为nfs-provisionerPod 挂载 NFS 卷,Pod 因此卡住。
3.3 解决方案
必须提高 inotify 的内核限制,并重启主机以确保 systemd (PID 1) 能在 kubelet 之前获取这些新限制。
-
创建配置文件
/etc/sysctl.d/99-k8s-inotify.conffs.inotify.max_user_watches=524288 fs.inotify.max_user_instances=8192 -
加载配置(在重启前临时生效)
sudo sysctl -p -
(关键) 重启主机
systemctl daemon-reexec在此场景下已失效,因为systemd资源已耗尽。必须重启。sudo reboot -
验证 重启后,
nfs-provisionerPod 自动变为Running状态。nfs-clientStorageClass 准备就绪。
4. 问题三:Dify Install 仍使用 local-path (Helm values 覆盖失效)
4.1 症状
-
在 NFS 正常运行后,使用
helm install dify dify/dify -f values.yaml ...部署 Dify。 -
kubectl get pvc -n dify显示新创建的difyPVC (AGE < 1m) 的STORAGECLASS仍然是local-path,并且状态为Pending。 -
自定义的
values.yaml文件(如下)被完全无视了:# 这是一个错误的、被忽略的 values.yaml api: persistence: storageClass: nfs-client ...
4.2 根本原因
Helm 只会覆盖与其官方 Chart 结构完全匹配的配置键。 我们自定义的 values.yaml 文件中的键(如 api:)与 Dify Helm Chart 的真正结构不符。Helm 在合并配置时,静默地忽略了这些不认识的键,并回退(Fallback)到使用 Chart 内部的默认值,即 storageClass: local-path。
4.3 解决方案
必须获取并使用官方的 values.yaml 文件结构。
-
清理失败的部署
helm uninstall dify -n dify kubectl delete pvc dify -n dify kubectl delete pvc dify-plugin-daemon -n dify -
(关键) 导出官方
values.yamlhelm show values dify/dify > real-values.yaml -
编辑
real-values.yaml打开这个新文件,搜索storageClass,找到所有需要 RWX 的地方(如api,worker,pluginDaemon的持久化配置),将其storageClass修改为nfs-client。 -
使用正确的文件安装
helm install dify dify/dify \ -f real-values.yaml \ --namespace dify -
验证
kubectl get pvc -n dify显示新 PVC 的STORAGECLASS正确显示为nfs-client,状态很快变为Bound。所有 Pod 启动成功。