Kubernetes集群NFS持久化存储实战:从搭建到动态供给
1. 项目概述:从单机到集群的存储进化
最近在折腾一个内部开发测试环境,核心需求很明确:需要一个能快速部署应用、方便管理且数据能持久保存的容器化平台。Kubernetes(k8s)自然是首选,但一涉及到有状态应用,比如数据库、文件服务,存储就成了必须跨过去的坎。用本地磁盘?节点挂了数据就丢了,不符合高可用预期。用云厂商的块存储?对于这个内部环境来说,成本和管理复杂度又有点高。于是,一个经典且朴素的方案浮出水面:搭建一个NFS服务器,为整个k8s集群提供共享的网络文件存储。
这个方案听起来有点“复古”,但在很多中小规模、对性能要求不是极端苛刻、且追求架构简单可控的场景下,它非常有效。NFS(Network File System)协议成熟稳定,几乎所有的Linux发行版都原生支持,配置起来也相对直观。把它作为k8s的持久化存储后端,意味着我们可以在集群的任何节点上运行的Pod,都能读写同一份数据,实现了数据的“持久化”和“共享”。无论是部署WordPress需要保存上传的插件主题,还是运行一个MySQL实例需要保证数据安全,都可以通过这个方案来解决。
整个流程可以拆解为三个核心阶段:首先是搭建一个稳定可靠的NFS服务器,这是整个存储体系的基石;然后是在若干台机器上部署一个最小化的k8s集群,作为我们的容器编排平台;最后,也是最关键的一步,是让k8s集群认识并使用我们搭建的NFS服务器,通过创建对应的存储类(StorageClass)、持久卷(PersistentVolume)和持久卷声明(PersistentVolumeClaim),将网络存储空间安全、动态地供给给Pod使用。接下来,我就把这次从零开始搭建的详细过程、踩过的坑以及一些优化思路分享出来,如果你也在规划类似的内部开发或测试环境,或许能直接抄作业。
2. 核心组件选型与环境规划
在动手之前,合理的规划能避免后期很多麻烦。这个方案涉及两个核心组件:NFS Server和Kubernetes Cluster。我们需要为它们选择合适的系统版本、规划网络并分配好角色。
2.1 操作系统与内核版本考量
我选择了CentOS 7.9作为基础操作系统。原因有几个:首先,CentOS 7的生命周期支持到2024年6月,在当下仍有广泛的社区支持和资料;其次,它的内核版本(3.10.x)对NFSv4和Kubernetes所需的基础功能(如OverlayFS、iptables)支持完善,且非常稳定;最后,其软件包管理工具yum用起来很顺手,离线安装依赖也相对方便。当然,你也可以选择Rocky Linux 8/9或Ubuntu 20.04/22.04 LTS等,原理相通,只是包管理命令(apt/dnf)和部分服务管理命令(systemctl)略有差异。
注意:如果你计划使用CentOS 8 Stream或其他更新版本,需要特别注意kubeadm等工具对系统组件的兼容性,例如默认的iptables版本与kube-proxy的配合。CentOS 7在这方面经过大量实践验证,更为稳妥。
对于内核,我坚持使用发行版自带的内核,不轻易升级。稳定压倒一切,自定义内核可能引入未知的与容器运行时或网络插件的兼容性问题。
2.2 服务器角色与网络规划
我用了四台虚拟机来构建这个环境,配置均为4核CPU、8GB内存、50GB系统盘。具体角色分配如下:
- nfs-server (192.168.1.100): 专职NFS服务器。我将额外挂载一块200GB的云盘或独立物理盘(
/dev/sdb)用于NFS共享,避免占用系统盘空间,也便于后期扩容和管理。 - k8s-master (192.168.1.101): Kubernetes控制平面节点。运行kube-apiserver、kube-scheduler、kube-controller-manager以及etcd。
- k8s-node1 (192.168.1.102): Kubernetes工作节点1。
- k8s-node2 (192.168.1.103): Kubernetes工作节点2。
所有机器处于同一局域网段(192.168.1.0/24),确保网络延迟低且稳定。防火墙策略需要预先规划:NFS服务需要开放相关端口(如2049, 20048等),k8s集群节点之间需要开放一系列端口用于组件通信(如6443, 2379-2380, 10250, 10259, 10257等)。一个简单的做法是在内网环境中,可以先暂时禁用防火墙(systemctl stop firewalld+systemctl disable firewalld)并在所有节点上设置SELinux为permissive模式(setenforce 0并修改/etc/selinux/config),待全部调试通过后再根据安全需求细化规则。对于生产环境,务必配置精确的防火墙规则。
2.3 软件版本确定
- NFS Server: 使用CentOS 7自带的
nfs-utils即可,它将同时提供NFS服务器和客户端功能。 - Kubernetes: 我选择了当前较为稳定的1.28.x版本。k8s版本迭代快,建议选择比最新版低1-2个的次要版本,社区生态和解决方案更成熟。配套工具如下:
kubeadm: 1.28.x,用于快速引导集群。kubelet: 1.28.x,节点上的核心代理。kubectl: 1.28.x,命令行管理工具。
- 容器运行时: 使用
containerd。Docker作为运行时已被弃用,containerd是更轻量、更直接的选择。版本需与k8s兼容,我选用containerd.io 1.6.x。 - CNI网络插件: 选择
Calico。它功能全面,性能良好,支持网络策略,且安装配置相对简单。版本选用与k8s 1.28兼容的v3.26.x。
版本对齐非常重要,不兼容的版本组合是后续各种诡异错误的根源。你可以在Kubernetes官方发布日志或各组件GitHub仓库的Release页面找到推荐的搭配。
3. 第一阶段:搭建高可用NFS服务器
NFS服务器是整个存储架构的基石,它的稳定性和性能直接影响后续所有应用。我们的目标不仅是“能挂载”,更要“挂得稳、用得好”。
3.1 系统准备与存储配置
首先登录nfs-server节点进行操作。
1. 基础环境配置:关闭防火墙和SELinux(仅用于实验环境,生产环境需配置规则)。
systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config2. 配置独立的存储空间:假设我们为NFS服务单独准备了一块磁盘/dev/sdb。
# 查看磁盘 lsblk # 假设 /dev/sdb 是新磁盘,对其进行分区和格式化 echo -e "n\np\n1\n\n\nw\n" | fdisk /dev/sdb mkfs.xfs /dev/sdb1 # 使用XFS文件系统,对NFS性能更友好 # 创建挂载点并挂载 mkdir /data/nfs_share mount /dev/sdb1 /data/nfs_share # 配置开机自动挂载 echo "/dev/sdb1 /data/nfs_share xfs defaults 0 0" >> /etc/fstab这里选择XFS是因为它在处理大量小文件和大文件时都有不错的表现,并且与NFS协同工作良好。/data/nfs_share就是我们将要共享给k8s集群的根目录。
3.2 安装与配置NFS服务
1. 安装NFS服务器软件包:
yum install -y nfs-utils rpcbindnfs-utils包含了NFS服务器和客户端工具,rpcbind是NFSv3需要的RPC端口映射服务(NFSv4虽可不用,但安装上兼容性好)。
2. 配置NFS共享目录:编辑NFS的主配置文件/etc/exports。这个文件定义了哪些目录可以共享给哪些客户端,以及共享的权限。
vim /etc/exports添加如下内容:
/data/nfs_share 192.168.1.0/24(rw,sync,no_root_squash,no_subtree_check)/data/nfs_share: 要共享的本地目录。192.168.1.0/24: 允许访问的客户端网段。你可以指定单个IP(如192.168.1.101)或主机名,但网段更方便集群扩展。rw: 读写权限。sync: 同步写入,数据更安全,但性能略低于async。对于内部环境,sync是推荐选择。no_root_squash:重要且需谨慎的选项。它允许客户端的root用户在共享目录上保持root权限。在k8s场景下,有些Pod(特别是系统级或需要特殊权限的Pod)可能需要以root身份写文件。如果设置为默认的root_squash,客户端的root会被映射为服务端的nobody用户,导致权限错误。仅在可信的内网环境使用此选项。no_subtree_check: 禁用子树检查,可以提高性能,尤其是在目录频繁重命名时。
3. 启动NFS相关服务并设置开机自启:
systemctl enable --now rpcbind systemctl enable --now nfs-server # 对于NFSv4,还需要启动nfs-idmapd服务 systemctl enable --now nfs-idmapd4. 验证NFS共享发布:
exportfs -v输出应显示我们刚刚配置的共享目录和选项。 同时,可以使用showmount -e localhost查看本机发布的共享列表。
3.3 性能与安全调优(可选但建议)
默认配置可能不适合所有场景,做一些调整可以提升稳定性和性能。
1. 调整NFS线程数:NFS服务使用内核线程处理请求。默认线程数可能偏少。编辑/etc/sysconfig/nfs文件,调整RPCNFSDCOUNT参数。
vim /etc/sysconfig/nfs # 找到并修改(如果不存在则添加) RPCNFSDCOUNT=32这个值建议设置为CPU核心数的4到8倍。修改后需要重启nfs服务:systemctl restart nfs-server。
2. 配置NFS客户端挂载参数(为后续k8s挂载做准备):虽然这是在客户端(k8s节点)挂载时指定的,但作为服务端规划,我们需要知道推荐的参数。在k8s的PV定义中,常用的mountOptions包括:
nfsvers=4: 强制使用NFSv4协议,比v3更安全,身份管理更简洁。hard: 硬挂载。如果NFS服务器无响应,客户端会无限重试,保证数据一致性。这是必须项,软挂载(soft)在超时后可能丢数据。intr: 允许中断一个被挂起的NFS调用(配合hard使用)。timeo=600: 设置RPC超时时间为600个十分之一秒(即60秒)。对于网络稳定的内网可以适当减小,网络波动大可增大。retrans=2: 重试次数。rsize=1048576和wsize=1048576: 设置读写缓冲区大小为1MB。可以显著提升大文件传输性能。需要根据网络MTU和服务端支持情况调整。
3. 安全加固思路:
- 网络隔离:将NFS服务器和k8s节点放在独立的VLAN或安全组内,只开放必要的NFS端口(如TCP/UDP 2049, 20048, 111)。
- 使用更精确的
/etc/exports:尽量用IP地址而非网段,或者结合主机名。 - 考虑Kerberos认证:对于安全性要求极高的环境,可以配置NFSv4 with Kerberos,但这会极大增加复杂度。
至此,一个基础但可用的NFS服务器就搭建完成了。你可以在同一网络内的另一台测试机上执行mount -t nfs 192.168.1.100:/data/nfs_share /mnt来测试挂载和读写是否正常。
4. 第二阶段:部署Kubernetes集群
有了存储后端,接下来搭建消费这些存储的k8s集群。我们使用kubeadm这个官方工具来简化安装过程。
4.1 所有节点基础环境准备
以下操作在所有节点(master, node1, node2)上执行。
1. 关闭防火墙、SELinux并配置主机名:
# 关闭防火墙 systemctl stop firewalld && systemctl disable firewalld # 关闭swap,kubelet要求 swapoff -a sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab # 设置SELinux为permissive setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config # 设置主机名(分别在各自节点执行) hostnamectl set-hostname k8s-master # 在master节点 hostnamectl set-hostname k8s-node1 # 在node1节点 hostnamectl set-hostname k8s-node2 # 在node2节点 # 编辑/etc/hosts,添加所有节点的IP和主机名映射(所有节点内容一致) cat >> /etc/hosts << EOF 192.168.1.101 k8s-master 192.168.1.102 k8s-node1 192.168.1.103 k8s-node2 EOF2. 配置内核模块与系统参数:加载必要的内核模块并调整系统参数,这些是容器运行时的要求。
# 加载模块 cat > /etc/modules-load.d/containerd.conf << EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 配置系统参数 cat > /etc/sysctl.d/99-kubernetes-cri.conf << EOF net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF sysctl --system4.2 安装容器运行时Containerd
1. 配置yum源并安装:
# 安装依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加Docker仓库(Containerd包在其中) yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装containerd yum install -y containerd.io2. 配置containerd:生成默认配置,并修改其中的cgroup驱动为systemd,以与kubelet保持一致。
containerd config default > /etc/containerd/config.toml # 使用sed修改sandbox_image为国内可访问的镜像,并修改cgroup驱动 sed -i 's|registry.k8s.io/pause:3.8|registry.aliyuncs.com/google_containers/pause:3.9|g' /etc/containerd/config.toml sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml3. 启动并设置开机自启:
systemctl enable --now containerd4.3 安装kubeadm, kubelet和kubectl
1. 配置Kubernetes的yum源:
cat > /etc/yum.repos.d/kubernetes.repo << EOF [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF2. 安装指定版本的三件套:
# 查看可安装版本 yum list kubeadm --showduplicates | sort -r # 安装1.28.x版本 yum install -y kubelet-1.28.8-0 kubeadm-1.28.8-0 kubectl-1.28.8-0 # 设置kubelet开机自启(先不启动,等kubeadm init后再启动) systemctl enable kubelet4.4 使用Kubeadm初始化Master节点
以下操作仅在master节点执行。
1. 初始化集群:
kubeadm init \ --apiserver-advertise-address=192.168.1.101 \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.28.8 \ --service-cidr=10.96.0.0/12 \ --pod-network-cidr=192.168.0.0/16 \ --ignore-preflight-errors=Swap参数解释:
--apiserver-advertise-address: Master节点的API Server对外公告的IP。--image-repository: 使用阿里云镜像仓库,加速镜像拉取。--kubernetes-version: 指定版本,需与安装的一致。--service-cidr: 集群内部Service使用的虚拟IP网段。--pod-network-cidr: Pod的IP网段,需要与后续安装的CNI插件(Calico)的默认网段匹配。Calico默认使用192.168.0.0/16。--ignore-preflight-errors=Swap: 忽略swap警告(因为我们已禁用)。
初始化成功后会输出类似以下的提示,其中包含加入集群的命令,务必保存好:
Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Alternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.conf You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 192.168.1.101:6443 --token <token> \ --discovery-token-ca-cert-hash <hash>2. 配置kubectl:根据提示,以普通用户身份执行:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config3. 安装Pod网络插件(Calico):
# 下载Calico的清单文件,注意版本匹配 curl https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/calico.yaml -O # 如果pod-network-cidr不是192.168.0.0/16,需要修改calico.yaml中的CALICO_IPV4POOL_CIDR # 我们初始化的cidr是192.168.0.0/16,所以无需修改 kubectl apply -f calico.yaml等待几分钟,使用kubectl get pods -n kube-system查看,直到所有Calico相关的Pod状态都变为Running。
4.5 将Worker节点加入集群
在每个worker节点(node1, node2)上,执行之前kubeadm init输出中提供的kubeadm join命令。命令格式如下:
kubeadm join 192.168.1.101:6443 --token <your-token> --discovery-token-ca-cert-hash <your-hash>如果令牌过期,可以在master节点上使用kubeadm token create --print-join-command生成新的加入命令。
在master节点上执行kubectl get nodes,稍等片刻,应该能看到所有节点状态变为Ready。至此,一个三节点的k8s集群就部署完成了。
5. 第三阶段:在K8s中集成NFS持久化存储
集群跑起来了,现在要让Pod能用上NFS。在k8s中,这通常涉及三个层次的对象:PersistentVolume (PV)、PersistentVolumeClaim (PVC) 和 StorageClass (SC)。我们将创建两种使用模式:静态供给和动态供给。
5.1 静态供给:手动创建PV和PVC
静态供给适合存储空间固定、使用模式明确的场景。我们需要先手动定义一个PV,描述NFS服务器的详细信息。
1. 创建PersistentVolume (PV):创建一个YAML文件,例如nfs-static-pv.yaml。
apiVersion: v1 kind: PersistentVolume metadata: name: nfs-static-pv spec: capacity: storage: 10Gi # 定义这个PV的容量 volumeMode: Filesystem accessModes: - ReadWriteMany # NFS支持多节点读写 persistentVolumeReclaimPolicy: Retain # 回收策略,Retain表示删除PVC后保留PV和数据 storageClassName: nfs-static # 指定一个存储类名称,用于PVC绑定 mountOptions: - hard - nfsvers=4 - noresvport # 解决某些客户端重连问题 nfs: path: /data/nfs_share/static-app # NFS服务器上的子目录,建议按应用划分 server: 192.168.1.100 # NFS服务器IP执行kubectl apply -f nfs-static-pv.yaml创建PV。注意,这里path指定了NFS服务器上的一个具体子目录/data/nfs_share/static-app,你需要先在NFS服务器上创建它:mkdir -p /data/nfs_share/static-app。
2. 创建PersistentVolumeClaim (PVC):PVC是Pod对存储的“需求声明”。创建一个nfs-static-pvc.yaml。
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-static-pvc spec: storageClassName: nfs-static # 必须与PV中定义的匹配 accessModes: - ReadWriteMany resources: requests: storage: 10Gi # 请求的存储大小,不能超过PV的容量执行kubectl apply -f nfs-static-pvc.yaml。k8s会自动寻找一个满足PVC要求的、状态为Available的PV进行绑定。使用kubectl get pv,pvc查看绑定状态。
3. 在Pod中使用PVC:创建一个测试Pod,例如test-static-pod.yaml。
apiVersion: v1 kind: Pod metadata: name: test-static-pod spec: containers: - name: nginx image: nginx:alpine volumeMounts: - name: data mountPath: /usr/share/nginx/html volumes: - name: data persistentVolumeClaim: claimName: nfs-static-pvc # 引用上面创建的PVC应用后,Pod会将NFS服务器上的/data/nfs_share/static-app目录挂载到容器内的/usr/share/nginx/html。你可以在NFS服务器上该目录创建一个index.html文件,然后访问Pod的IP验证。
5.2 动态供给:使用StorageClass自动创建PV
静态供给需要管理员预先创建PV,当PVC很多时很繁琐。动态供给则通过StorageClass实现“按需分配”。当用户创建PVC时,如果指定了某个StorageClass,k8s会自动按需创建对应的PV。
1. 部署NFS Client Provisioner:k8s本身没有内置的NFS动态供给器,我们需要部署一个外部的provisioner。nfs-subdir-external-provisioner是一个常用且简单的选择。它会在你指定的NFS目录下,自动为每个PVC创建子目录。 首先,添加Helm仓库或直接下载部署文件。这里使用直接下载清单的方式。
# 下载部署文件 curl -LO https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/deployment.yaml curl -LO https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/class.yaml curl -LO https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/rbac.yaml我们需要修改deployment.yaml,配置provisioner的参数。
- 修改环境变量,指定NFS服务器和共享路径。
- 修改
args中的--provisioner名称,确保唯一性(例如example.com/nfs-dynamic)。
一个修改后的deployment.yaml关键部分示例如下:
... spec: template: spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 env: - name: NFS_SERVER value: 192.168.1.100 # NFS服务器IP - name: NFS_PATH value: /data/nfs_share/dynamic # NFS共享路径下的一个专门用于动态供给的子目录 args: - "-provisioner=example.com/nfs-dynamic" # provisioner标识,需与class.yaml匹配 ...同时,在NFS服务器上创建对应的目录:mkdir -p /data/nfs_share/dynamic。
2. 创建StorageClass和RBAC:class.yaml定义了StorageClass。确保其provisioner字段与上面deployment.yaml中的args一致。
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-dynamic provisioner: example.com/nfs-dynamic # 必须与provisioner参数一致 parameters: archiveOnDelete: "false" # 删除PVC时,是否归档数据(重命名目录而非删除) reclaimPolicy: Delete # 动态创建的PV,其回收策略由此处定义 volumeBindingMode: Immediaterbac.yaml为provisioner Pod提供了必要的Kubernetes API访问权限。
按顺序应用这些文件:
kubectl apply -f rbac.yaml kubectl apply -f deployment.yaml kubectl apply -f class.yaml3. 测试动态供给:创建一个PVC,指定storageClassName: nfs-dynamic。
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-dynamic-pvc spec: storageClassName: nfs-dynamic # 关键:指定动态StorageClass accessModes: - ReadWriteMany resources: requests: storage: 5Gi应用后,你会发现一个对应的PV被自动创建出来了,并且PV中的path会自动指向NFS服务器上的/data/nfs_share/dynamic/<namespace>-<pvc-name>-<random-id>目录。将这个PVC挂载到Pod中使用即可。
实操心得:动态供给极大地简化了存储管理。建议将
archiveOnDelete设置为"true"用于生产环境,这样删除PVC后数据不会立即被清除,而是被移动到归档目录,防止误操作。同时,注意监控NFS服务器上动态目录的磁盘使用情况。
6. 常见问题排查与性能优化指南
在实际操作中,你几乎一定会遇到一些问题。下面是我踩过的一些坑和对应的解决方案。
6.1 挂载失败与权限问题
问题现象:Pod状态为ContainerCreating,使用kubectl describe pod <pod-name>查看事件,显示MountVolume.SetUp failed或timeout waiting for mount。
排查思路:
- 检查网络连通性:在任意k8s节点上执行
ping 192.168.1.100和showmount -e 192.168.1.100,确保能发现NFS共享。 - 检查NFS服务端配置:确认
/etc/exports配置正确,且已用exportfs -ra重新加载。检查NFS服务端口(2049)是否在监听:netstat -tlnp | grep 2049。 - 检查客户端挂载参数:在k8s节点上手动尝试挂载,模拟k8s的行为:
mount -t nfs -o nfsvers=4,hard,intr,timeo=600 192.168.1.100:/data/nfs_share /mnt/test。手动挂载的成功与否能极大缩小问题范围。 - 检查SELinux和防火墙:这是最常见的拦路虎。确保所有节点(包括NFS服务器)的SELinux已设置为permissive或disabled。确保防火墙规则放行了NFS相关端口(
rpcbind(111),nfs(2049),mountd(20048)等),或者直接关闭防火墙进行测试。 - 检查目录权限:确保NFS服务器上共享的目录(如
/data/nfs_share及其子目录)对于NFS客户端映射的用户(由于设置了no_root_squash,客户端root即服务端root)有读写权限。检查目录的owner和group。
典型错误:access denied by server while mounting。这通常是/etc/exports配置的客户端IP或权限不对,或者防火墙阻止。
6.2 PV/PVC绑定失败
问题现象:PVC一直处于Pending状态。
排查思路:
- 查看PVC详情:
kubectl describe pvc <pvc-name>。查看Events部分,通常会有明确错误信息。 - 静态供给:检查PV的
storageClassName、accessModes、capacity是否与PVC要求匹配。检查PV状态是否为Available。 - 动态供给:
- 检查StorageClass是否存在且名称正确:
kubectl get storageclass。 - 检查Provisioner Pod是否运行正常:
kubectl get pods -n <provisioner-namespace>。 - 查看Provisioner Pod的日志:
kubectl logs -f <provisioner-pod-name> -n <namespace>,日志通常会打印创建PV时的详细错误,如连接NFS失败、目录创建权限不足等。
- 检查StorageClass是否存在且名称正确:
6.3 NFS性能调优建议
默认配置可能无法满足高IO需求,可以考虑以下优化:
- 客户端挂载参数:在PV定义的
mountOptions中调整。rsize和wsize:增加读写块大小,如rsize=1048576,wsize=1048576(1MB)。这对大文件连续读写提升明显。noatime或nodiratime:禁止记录文件访问时间,减少元数据操作。bg:如果首次挂载失败,在后台重试(对于非关键启动顺序的场景)。
- 服务端优化:
- 如前面所述,调整
/etc/sysconfig/nfs中的RPCNFSDCOUNT(NFS线程数)。 - 使用性能更好的磁盘(如SSD)和文件系统(如XFS)。
- 确保NFS服务器有足够的内存,因为NFS会进行缓存。
- 如前面所述,调整
- 应用层优化:
- 避免大量小文件的随机读写。如果应用场景如此,考虑使用本地SSD盘或专门的对象存储。
- 对于数据库等对延迟敏感的应用,NFS可能不是最佳选择,应考虑块存储方案(如Ceph RBD、云盘CSI驱动)。
6.4 数据安全与备份
NFS服务器是单点,其故障会导致所有依赖它的应用不可用。务必做好备份!
- 定期备份:使用
rsync或备份工具定期将/data/nfs_share目录同步到另一台备份服务器。 - 考虑高可用NFS:对于生产环境,可以搭建DRBD+Keepalived实现NFS服务器的主备高可用,或者使用GlusterFS、CephFS这类分布式文件系统替代单点NFS。
- 使用Velero进行k8s原生备份:Velero不仅可以备份集群资源(如PVC定义),配合适当的插件(如Restic)还可以备份PVC中的实际数据到对象存储。
这套由NFS提供底层存储、Kubernetes进行容器编排的方案,在开发测试、预发布甚至一些对性能要求不高的生产场景中,已经服务了相当长一段时间,运行稳定。它的优势在于架构清晰,排错直观,所有数据都集中在一个熟悉的文件系统目录下,管理起来心里有底。当然,随着应用规模的扩大,你会开始关注性能、高可用和更高级的存储特性,那时便是探索Ceph、Longhorn等更复杂分布式存储方案的时候了。但无论如何,从这套简单的方案入手,无疑是理解Kubernetes持久化存储概念的最佳实践起点。
