Kubernetes 存储方案与 NFS 配置

Kubernetes 存储方案与 NFS 配置 本文介绍 Kubernetes 集群中的持久化存储方案,重点讲解 NFS StorageClass 的部署配置以及动态卷供应的实现。 概述 在 Kubernetes 中,Pod 的生命周期是短暂的,当 Pod 被删除或重启时,容器内的数据会丢失。为了解

Kubernetes 存储方案与 NFS 配置

本文介绍 Kubernetes 集群中的持久化存储方案,重点讲解 NFS StorageClass 的部署配置以及动态卷供应的实现。

概述

在 Kubernetes 中,Pod 的生命周期是短暂的,当 Pod 被删除或重启时,容器内的数据会丢失。为了解决这个问题,Kubernetes 提供了持久化存储机制。本文将介绍如何通过 NFS 实现动态存储供应,为应用提供可靠的数据持久化能力。

存储基础概念

PV 与 PVC

  • PV(PersistentVolume):集群管理员提供的存储资源,独立于 Pod 生命周期
  • PVC(PersistentVolumeClaim):用户对存储的请求,类似于 Pod 消费 Node 资源
  • StorageClass:定义存储的"类型",实现动态供应

访问模式

模式 说明
ReadWriteOnce(RWO) 单节点读写
ReadOnlyMany(ROX) 多节点只读
ReadWriteMany(RWX) 多节点读写
ReadWriteOncePod(RWOP) 单 Pod 读写(K8s 1.22+)

回收策略

  • Retain:保留数据,PV 不被删除
  • Delete:删除 PV 和数据
  • Recycle:清理数据后重新可用(已弃用)

NFS 服务端配置

安装 NFS 服务

# Ubuntu/Debian
apt-get install -y nfs-kernel-server nfs-common

# CentOS/RHEL
yum install -y nfs-utils rpcbind

配置共享目录

# 创建共享目录
mkdir -p /data/kubernetes/volumes
chmod 777 /data/kubernetes/volumes

# 配置 exports
cat >> /etc/exports <<EOF
/data/kubernetes/volumes *(rw,sync,no_root_squash,no_subtree_check)
EOF

# 启动服务
exportfs -ra
systemctl enable --now nfs-server
systemctl enable --now rpcbind

验证 NFS 服务

# 本地测试
showmount -e localhost

# 从客户端测试
showmount -e <nfs-server-ip>

部署 NFS Subdir External Provisioner

方式一:Helm 安装(推荐)

添加仓库

helm repo add nfs-subdir-external-provisioner \
  https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update

创建生产环境配置

# prod-values.yaml
replicaCount: 2
strategyType: Recreate

image:
  repository: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner
  tag: v4.0.2
  pullPolicy: IfNotPresent

storageClass:
  create: true
  defaultClass: true  # 设置为默认 StorageClass
  accessModes: ReadWriteMany
  reclaimPolicy: Retain  # 保留数据
  name: nfs-ssd-01
  pathPattern: "${.PVC.namespace}/${.PVC.name}"

nfs:
  server: 10.10.10.30  # NFS 服务器地址
  path: /data/kubernetes/volumes
  mountOptions:
    - nfsvers=4.1
    - rsize=1048576
    - wsize=1048576
    - hard
    - timeo=600
    - retrans=2
    - noresvport

nodeSelector:
  kubernetes.io/os: linux

tolerations: []

安装 Chart

# 创建命名空间
kubectl create namespace nfs

# 安装
helm upgrade --install nfs-subdir-external-provisioner \
  nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  -f prod-values.yaml \
  --namespace nfs \
  --create-namespace

方式二:手动部署

创建 RBAC 权限

# nfs-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: nfs
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: nfs
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

创建 Deployment

# nfs-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: nfs
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: nfs-ssd-01
            - name: NFS_SERVER
              value: 10.10.10.30
            - name: NFS_PATH
              value: /data/kubernetes/volumes
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.10.10.30
            path: /data/kubernetes/volumes

创建 StorageClass

# nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-ssd-01
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: nfs-ssd-01
parameters:
  archiveOnDelete: "true"  # 删除 PVC 时归档数据
mountOptions:
  - hard
  - nfsvers=4.1
  - nolock
  - rsize=1048576
  - wsize=1048576

应用配置

kubectl apply -f nfs-rbac.yaml
kubectl apply -f nfs-provisioner.yaml
kubectl apply -f nfs-storageclass.yaml

验证存储配置

查看 StorageClass

kubectl get storageclass

# 输出示例
NAME         PROVISIONER    RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-ssd-01   nfs-ssd-01     Retain          Immediate           false                  5m

创建测试 PVC

# test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  storageClassName: nfs-ssd-01
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
kubectl apply -f test-pvc.yaml
kubectl get pvc test-pvc

创建测试 Pod

# test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
    - name: test-container
      image: busybox:latest
      command: ["/bin/sh", "-c", "touch /mnt/SUCCESS && sleep 3600"]
      volumeMounts:
        - name: test-volume
          mountPath: /mnt
  volumes:
    - name: test-volume
      persistentVolumeClaim:
        claimName: test-pvc
kubectl apply -f test-pod.yaml
kubectl get pods test-pod

# 验证文件是否创建
kubectl exec test-pod -- ls /mnt

在应用中使用持久化存储

MySQL 有状态应用示例

# mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: password
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-data
          persistentVolumeClaim:
            claimName: mysql-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  storageClassName: nfs-ssd-01
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

多副本共享存储示例

# nginx-shared.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-shared
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-shared
  template:
    metadata:
      labels:
        app: nginx-shared
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          volumeMounts:
            - name: shared-data
              mountPath: /usr/share/nginx/html
      volumes:
        - name: shared-data
          persistentVolumeClaim:
            claimName: shared-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-pvc
spec:
  storageClassName: nfs-ssd-01
  accessModes:
    - ReadWriteMany  # 多节点读写
  resources:
    requests:
      storage: 5Gi

存储管理最佳实践

命名空间隔离

使用 pathPattern 参数按命名空间隔离存储:

storageClass:
  pathPattern: "${.PVC.namespace}/${.PVC.name}"

这将在 NFS 服务器上创建如下目录结构:

/data/kubernetes/volumes/
├── default/
│   └── mysql-pvc/
├── nginx/
│   └── nginx-pvc/
└── databases/
    └── postgres-pvc/

数据备份策略

# 备份 NFS 数据
rsync -avz /data/kubernetes/volumes/ backup-server:/backup/k8s-volumes/

# 使用快照(如底层存储支持)
# ZFS/Btrfs 快照
zfs snapshot tank/k8s-volumes@$(date +%Y%m%d)

监控存储使用

# 查看 PV 使用情况
kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,STATUS:.status.phase,CLAIM:.spec.claimRef.name

# 查看 PVC 使用情况
kubectl get pvc --all-namespaces

故障排查

常见问题

  1. PVC 处于 Pending 状态
# 检查 StorageClass 是否存在
kubectl get storageclass

# 查看 Provisioner 日志
kubectl logs -n nfs -l app=nfs-subdir-external-provisioner
  1. Pod 无法挂载卷
# 检查 NFS 服务
showmount -e <nfs-server-ip>

# 检查节点上的 NFS 客户端
apt-get install -y nfs-common
  1. 权限问题
# 检查 NFS 导出选项
# 确保使用 no_root_squash 或正确的用户映射

其他存储方案对比

方案 优点 缺点 适用场景
NFS 简单易用,RWX 支持 性能一般,单点故障 开发测试,小型集群
Ceph RBD 高性能,数据冗余 部署复杂 生产环境数据库
CephFS RWX 支持,高可用 资源占用高 共享存储场景
Longhorn 易用,快照备份 性能开销 中小型集群
Local PV 高性能 无高可用 有状态应用缓存

总结

本文介绍了 Kubernetes 中 NFS 存储的配置和使用:

  1. NFS 服务部署:配置 NFS 服务器和共享目录
  2. 动态供应:使用 nfs-subdir-external-provisioner 实现动态卷创建
  3. 应用集成:在 MySQL、Nginx 等应用中使用 PVC
  4. 最佳实践:命名空间隔离、数据备份、监控告警

NFS 是一种简单实用的存储方案,适合中小型集群和开发测试环境。对于生产环境,建议评估 Ceph、Longhorn 等更可靠的存储解决方案。

LICENSED UNDER CC BY-NC-SA 4.0
Comment