K8s NFS+StorageClass+PV/PVC+Deployment 实战笔记

一、前置准备(所有K8s节点执行)

以下sudo是用的别的用户,root用户不需要

所有节点必须安装nfs-utils,否则Pod挂载NFS会直接失败

复制代码
sudo dnf install -y nfs-utils
sudo systemctl enable --now rpcbind

二、NFS服务端配置(192.168.24.11执行)

1. 创建共享目录

复制代码
sudo mkdir -p /data/nfs/{1..3}
属主为nobody,权限666,避免挂载后无写入权限
sudo chown -R nobody:nobody /data/nfs
sudo chmod -R 666 /data/nfs
# 写入测试文件,方便后续验证
echo "NFS Test Data - PV1" > /data/nfs/1/index.html
echo "NFS Test Data - PV2" > /data/nfs/2/index.html
echo "NFS Test Data - PV3" > /data/nfs/3/index.html

2. 配置NFS导出规则

复制代码
sudo vim /etc/exports
# 添加以下内容,允许K8s网段所有节点读写访问
/data/nfs/1 192.168.24.0/24(rw,no_root_squash,sync)
/data/nfs/2 192.168.24.0/24(rw,no_root_squash,sync)
/data/nfs/3 192.168.24.0/24(rw,no_root_squash,sync)

# 重载配置
sudo exportfs -ra
# 验证导出是否生效
sudo exportfs -v

3. 防火墙放行

复制代码
sudo firewall-cmd --permanent --add-service=nfs
sudo firewall-cmd --permanent --add-service=mountd
sudo firewall-cmd --permanent --add-service=rpc-bind
sudo firewall-cmd --reload

4. SELinux配置

复制代码
# 开启NFS导出权限,否则SELinux会拦截挂载请求
sudo setsebool -P nfs_export_all_rw 1
# 验证目录安全上下文是否为nfs_t
ls -Zd /data/nfs
# 正确输出:unconfined_u:object_r:nfs_t:s0 /data/nfs


-P(Persistent):写入磁盘策略库

三、StorageClass配置(NFS动态供给,动态PV分配方式)

PV可通过StorageClass动态分配,无需手动创建PV,适合生产环境,解决静态PV需要提前规划容量的痛点。

1. 部署NFS动态供给器(nfs-subdir-external-provisioner)

复制代码
# nfs-provisioner.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-provisioner-runner
rules:
- apiGroups: [""]
  resources: ["nodes", "persistentvolumes", "persistentvolumeclaims", "endpoints"]
  verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["storage.k8s.io"]
  resources: ["storageclasses"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
  name: nfs-provisioner
  namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-provisioner
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-provisioner
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccountName: nfs-provisioner
      containers:
      - name: nfs-provisioner
        # 使用你的Harbor仓库镜像,避免拉取失败
        image: hb.reg.com/k8s/nfs:4.0.2
        volumeMounts:
        - name: nfs-root
          mountPath: /persistentvolumes
        env:
        - name: PROVISIONER_NAME
          value: k8s-sigs.io/nfs-subdir-external-provisioner
        - name: NFS_SERVER
          value: 192.168.24.11  # NFS服务端IP
        - name: NFS_PATH
          value: /data/nfs      # NFS根共享目录
      volumes:
      - name: nfs-root
        nfs:
          server: 192.168.24.11
          path: /data/nfs
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-sc  # PVC中引用的StorageClass名称
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
  archiveOnDelete: "false"  # 删除PVC时不归档数据,直接删除
资源类型 资源名称 核心作用 关键配置说明 关联依赖/注意事项
ServiceAccount nfs-provisioner 给NFS供给器的Pod提供集群内的合法身份,用于和kube-apiserver通信鉴权 namespace: default:和后续Deployment的命名空间保持一致 Pod必须通过serviceAccountName字段引用该SA,否则没有权限操作集群资源
ClusterRole nfs-provisioner-runner 定义NFS供给器需要的集群级权限(PV、StorageClass是跨命名空间的集群资源,普通Role权限不足) rules字段声明了get/list/watch/create/delete PV、PVC、StorageClass等资源的权限,是动态供给的核心权限集合 必须用ClusterRole而非Role,否则无法操作集群级资源
ClusterRoleBinding run-nfs-provisioner 把ServiceAccount和ClusterRole绑定,让nfs-provisioner这个SA拥有nfs-provisioner-runner的全部权限 subjects指定SA的名称和命名空间;roleRef指定绑定的ClusterRole subjectsnamespace必须和SA的namespace一致,否则权限绑定无效
Deployment nfs-provisioner 运行NFS动态供给器的业务Pod,负责监听PVC请求,自动在NFS服务端创建目录、生成PV并绑定PVC 1. image用你的Harbor私有仓库镜像,避免公网拉取失败 2. PROVISIONER_NAME是供给器唯一标识,必须和StorageClass的provisioner字段完全一致 3. NFS_SERVER: 192.168.24.11NFS_PATH: /data/nfs对应你之前配置的NFS共享目录 4. 通过volumesvolumeMounts把NFS根目录挂载到容器的/persistentvolumes,供给器才能操作NFS存储 所有Node节点必须安装nfs-utils;NFS服务端/data/nfs目录权限需为nobody属主、666权限,SELinux需开启nfs_export_all_rw
StorageClass nfs-sc 定义NFS动态存储的规则,是PVC申请存储的入口,告诉K8s用哪个供给器、按什么规则创建存储 1. provisioner字段必须和Deployment的PROVISIONER_NAME完全一致,否则找不到供给器 2. archiveOnDelete: "false":删除PVC时直接删除NFS上的对应目录,无需归档;设为"true"会生成archived-前缀的备份目录 PVC中指定storageClassName: nfs-sc即可自动申请动态PV,无需手动创建PV
复制代码
kubectl apply -f nfs-provisioner.yaml
# 验证provisioner是否运行正常
kubectl get pods -l app=nfs-provisioner

这里遇到了镜像拉取失败的问题,仓库地址写错了

复制代码
[root@k8s-master01 work]# kubectl get pods
NAME                               READY   STATUS             RESTARTS   AGE
nfs-provisioner-6f8b95db9d-dlnfh   0/1     ImagePullBackOff   0          8s
[root@k8s-master01 work]# kubectl get pods
NAME                               READY   STATUS             RESTARTS   AGE
nfs-provisioner-6f8b95db9d-dlnfh   0/1     ImagePullBackOff   0          10s
[root@k8s-master01 work]# kubectl get pods -l app=nfs-provisioner.yml 
No resources found in default namespace.
[root@k8s-master01 work]# kubectl get pods -l app=nfs-provisioner
NAME                               READY   STATUS         RESTARTS   AGE
nfs-provisioner-6f8b95db9d-dlnfh   0/1     ErrImagePull   0          37s
[root@k8s-master01 work]# 

四、PV/PVC配置(文档静态供给案例)

PV是集群存储资源的抽象,PVC是用户对存储的请求,二者通过容量、访问模式、StorageClass三者完全匹配才能绑定。

1. 静态创建PV(对应3个NFS共享目录)

复制代码
# pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-1
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce  # 单节点读写,适合Deployment单副本场景
  persistentVolumeReclaimPolicy: Retain  # PVC删除后PV保留数据,避免误删
  storageClassName: nfs-sc  # 关联上面创建的StorageClass
  nfs:
    server: 192.168.24.11
    path: /data/nfs/1
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-2
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs-sc
  nfs:
    server: 192.168.24.11
    path: /data/nfs/2
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-3
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs-sc
  nfs:
    server: 192.168.24.11
    path: /data/nfs/3

kubectl apply -f pv-nfs.yaml
# 验证PV状态,应为Available
kubectl get pv


[root@k8s-master01 work]# kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nfs-pv     1Gi        RWO            Delete           Failed      default/www-web-0                  <unset>                          35h
nfs-pv-1   1Gi        RWO            Retain           Available                       nfs-sc         <unset>                          8s
nfs-pv-2   1Gi        RWO            Retain           Available                       nfs-sc         <unset>                          8s
nfs-pv-3   1Gi        RWO            Retain           Available                       nfs-sc         <unset>  

2. 创建PVC绑定PV

复制代码
# pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs-sc  # 与PV的StorageClass一致,否则无法绑定

kubectl apply -f pvc-nfs.yaml
# 验证PVC状态,应为Bound,且绑定到nfs-pv-1
kubectl get pvc
kubectl describe pvc nfs-pvc



[root@k8s-master01 work]# kubectl get pvc
NAME      STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
nfs-pvc   Bound    nfs-pv-1   1Gi        RWO            nfs-sc         <unset>                 8s


[root@k8s-master01 work]# kubectl describe pvc nfs-pvc
Name:          nfs-pvc
Namespace:     default
StorageClass:  nfs-sc
Status:        Bound
Volume:        nfs-pv-1
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      1Gi
Access Modes:  RWO
VolumeMode:    Filesystem
Used By:       <none>
Events:        <none>

五、Deployment部署(结合ConfigMap/Secret)

Deployment适合无状态应用,结合ConfigMap可实现配置热更新,结合Secret可安全存储敏感信息。

1. 创建ConfigMap(nginx配置,文档热更新案例)

复制代码
# nginx-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  default.conf: |
    server {
      listen 80;
      server_name localhost;
      location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
      }
    }

kubectl apply -f nginx-configmap.yaml


[root@k8s-master01 work]# kubectl get configmaps 
NAME               DATA   AGE
default-nginx      1      4d9h
kube-root-ca.crt   1      13d
nginx-conf         1      13s

2. 创建Secret(敏感信息)

复制代码
# 文档要求:Secret的value必须是base64编码,否则创建失败
echo -n "admin" | base64    # 输出:YWRtaW4=
echo -n "123456" | base64  # 输出:MTIzNDU2

# nginx-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: nginx-secret
type: Opaque
data:
  username: YWRtaW4=    # base64编码后的admin
  password: MTIzNDU2  # base64编码后的123456

kubectl apply -f nginx-secret.yaml

[root@k8s-master01 work]# kubectl get secrets -o wide
NAME           TYPE     DATA   AGE
nginx-secret   Opaque   2      26s

3. 创建Deployment(挂载PVC/ConfigMap/Secret)

复制代码
# nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
      # 用于触发热更新后的滚动更新(文档重点方案)
      annotations:
        version/config: "v1"
    spec:
      containers:
      - name: nginx
        # 使用你的Harbor仓库镜像
        image: hb.reg.com/library/nginx:1.27.4
        ports:
        - containerPort: 80
        volumeMounts:
        # 挂载PVC作为网站根目录,数据持久化到NFS
        - name: nfs-storage
          mountPath: /usr/share/nginx/html
        # 挂载ConfigMap作为nginx配置,支持热更新
        - name: nginx-conf
          mountPath: /etc/nginx/conf.d/
          readOnly: true
        # 挂载Secret作为环境变量,自动解码为明文
        env:
        - name: NGINX_USER
          valueFrom:
            secretKeyRef:
              name: nginx-secret
              key: username
        - name: NGINX_PASS
          valueFrom:
            secretKeyRef:
              name: nginx-secret
              key: password
      volumes:
      - name: nfs-storage
        persistentVolumeClaim:
          claimName: nfs-pvc  # 关联之前创建的PVC
      - name: nginx-conf
        configMap:
          name: nginx-conf

kubectl apply -f nginx-deploy.yaml
# 验证Pod运行状态
kubectl get pods -l app=nginx
# 验证环境变量是否注入成功
kubectl exec -it $(kubectl get pods -l app=nginx -o jsonpath='{.items[0].metadata.name}') -- env | grep NGINX
# 验证NFS挂载是否成功
curl $(kubectl get pods -l app=nginx -o jsonpath='{.items[0].status.podIP}')
# 输出应为:NFS Test Data - PV1


[root@k8s-master01 work]# kubectl get pods -l app=nginx
NAME                           READY   STATUS    RESTARTS   AGE
nginx-deploy-6b4756c98-6zf8h   1/1     Running   0          8s
nginx-deploy-6b4756c98-bs9mp   1/1     Running   0          8s
nginx-deploy-6b4756c98-nz869   1/1     Running   0          8s


[root@k8s-master01 work]# kubectl exec -it $(kubectl get pods -l app=nginx -o jsonpath='{.items[0].metadata.name}') -- env | grep NGINX
NGINX_USER=admin
NGINX_PASS=123456
NGINX_VERSION=1.27.4

这里我遇到了访问的403问题

复制代码
[root@k8s-master01 work]# curl 10.244.85.248
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.27.4</center>
</body>
</html>

解决:

复制代码
# 先拿到第一个Pod的名字(这里也可以查看,我查看了自动获取pod名字的办法而已)
POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath='{.items[0].metadata.name}')
# 看Nginx错误日志,90%的情况会直接告诉你原因
kubectl logs $POD_NAME     (我这里遇到了Permission denied),判断是nfs权限的问题

操作:
# 1. 目录必须有x权限,才能进入;统一给755权限
sudo chmod -R 755 /data/nfs

# 2. 文件统一给644权限(所有用户可读)
sudo chmod -R 644 /data/nfs/*/index.html

# 3. 统一属主为nobody:nobody,匹配NFS的匿名映射规则
sudo chown -R nobody:nobody /data/nfs

重新写exports文件(这个不是关键,查询后属于规范,不要让客户端root直接拥有服务端root权限)
/data/nfs/1 192.168.24.0/24(rw,sync,root_squash,anonuid=65534,anongid=65534)
/data/nfs/2 192.168.24.0/24(rw,sync,root_squash,anonuid=65534,anongid=65534)
/data/nfs/3 192.168.24.0/24(rw,sync,root_squash,anonuid=65534,anongid=65534)

sudo exportfs -ra
sudo exportfs -v  # 验证规则是否生效


验证结果:
解决403问题,可以访问
[root@k8s-master01 work]# curl 10.244.85.218
NFS Test Data - PV1

六、ConfigMap热更新演示

更新ConfigMap不会自动触发Pod滚动更新,需要手动patch Deployment的annotations触发,否则应用不会加载新配置。

1. 修改ConfigMap(比如修改端口为8080)

复制代码
kubectl edit cm nginx-conf
# 将listen 80改为listen 8080,保存退出

2. 验证ConfigMap已更新

复制代码
kubectl get cm nginx-conf -o yaml

[root@k8s-master01 work]# kubectl get cm nginx-conf -o yaml
apiVersion: v1
data:
  default.conf: |
    server {
      listen 8080;
      server_name localhost;
      location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
      }
    }
。。。。。

3. 触发Deployment滚动更新

复制代码
# 修改annotations的version/config值,触发滚动更新
kubectl patch deployment nginx-deploy --patch '{"spec":{"template":{"metadata":{"annotations":{"version/config":"v2"}}}}}'
# 验证Pod是否滚动更新完成
kubectl get pods -l app=nginx
# 验证配置是否生效(访问PodIP:8080)
kubectl get pods -l app=nginx -o wide


[root@k8s-master01 work]# kubectl patch deployment nginx-deploy --patch '{"spec":{"template":{"metadata":{"annotations":{"version/config":"v2"}}}}}'
deployment.apps/nginx-deploy patched

[root@k8s-master01 work]# curl 10.244.85.219:8080    (这里ip改变了,说明在改变)
NFS Test Data - PV1

[root@k8s-master01 work]# kubectl get deployments.apps nginx-deploy -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "2"
.....

版本也成功换成2

七、难点解释

1. PV/PVC绑定失败

  • 原因:容量、访问模式、StorageClass三者必须完全匹配,否则PVC会一直处于Pending状态。

    • 例:PV是ReadWriteOnce,PVC请求ReadWriteMany,无法绑定;PV是1Gi,PVC请求2Gi,无法绑定;PV的StorageClass是nfs-sc,PVC的StorageClass是"",无法绑定。
  • 排查kubectl describe pvc <pvc-name> 查看Events字段。

2. NFS挂载失败

  • 现象 :Pod处于ContainerCreating,事件提示mount failed: exit status 32

  • 排查步骤

    1. 所有节点是否安装nfs-utils

    2. NFS服务端exportfs -v是否有对应目录导出;

    3. 防火墙是否放行nfs/mountd/rpc-bind服务;

    4. SELinux的nfs_export_all_rw是否为on

    5. 节点是否能ping通NFS服务端,telnet 192.168.24.11 2049是否通。

3. ConfigMap热更新不生效

  • 原因:ConfigMap更新后,Pod内的文件会同步更新,但应用不会自动重载配置,需要手动触发Deployment滚动更新。

  • 解决方案 :用kubectl patch修改Deployment的template.annotations,触发滚动更新。

4. Secret使用注意事项

  • 编码要求 :Secret的data字段的value必须是base64编码,否则创建失败;

  • 自动解码:Secret挂载到Pod后,K8s会自动base64解码,应用可直接使用明文值;

  • 权限控制:Secret默认只有被挂载的Pod可见,比ConfigMap更安全,适合存储敏感信息。

5. StorageClass动态供给优势

  • 无需手动创建PV,PVC创建后自动创建对应PV并绑定;

  • 适合大规模集群,减少运维工作量;

  • 可通过参数控制回收策略、归档规则等。


八、资源清理

复制代码
kubectl delete -f nginx-deploy.yaml
kubectl delete -f nginx-secret.yaml
kubectl delete -f nginx-configmap.yaml
kubectl delete -f pvc-nfs.yaml
kubectl delete -f pv-nfs.yaml
kubectl delete -f nfs-provisioner.yaml
相关推荐
影寂ldy4 小时前
SQL 索引(Index)完整笔记
数据库·笔记·sql
GlueNa2SiO34 小时前
06-Docker存储与数据持久化
笔记·学习·docker
一位正在转型AI全栈的前端工程师5 小时前
前端转全栈:从 502 到 200,我的第一个 Docker+Nginx+Node.js 项目
docker·容器
上下求索,莫负韶华5 小时前
切面学习笔记
笔记·python·学习
测试运维日常笔记6 小时前
Unix/Linux 系统软件包管理笔记
linux·笔记·unix
维核科技6 小时前
大模型私有化部署:Docker与Kubernetes实战避坑
docker·容器·kubernetes
上下求索,莫负韶华7 小时前
笔记-数据库事务和java事务和切面
java·数据库·笔记
sakiko_7 小时前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift
神明不懂浪漫7 小时前
【第五章】队列
开发语言·数据结构·经验分享·笔记·算法