K8s 综合实践笔记:Ansible 自动化 NFS + StorageClass + 静态 PV/PVC+Nginx Deployment

一、实践前言

容器本地存储生命周期跟随 Pod,Pod 重建、漂移后数据全部丢失,多副本业务还需要共享同一份数据,因此需要外部共享持久化存储。

本次实践分为两大块:先回顾前期知识点,温故 Ansible相关内容,playbook,模块等等;然后用 Ansible 自动化工具批量部署整套 NFS 服务环境;再结合 K8s StorageClass、静态 PV、PVC、Deployment 完成存储绑定与业务挂载,同时验证WaitForFirstConsumer延迟绑定特性,记录实操中镜像拉取、exec 协议报错、apiserver 静态配置崩溃、PV 删除异常等全套故障,形成标准化可复用落地流程。

区别于单纯静态 PV 实验,本次引入 StorageClass 统一存储分类管理,通过volumeBindingMode: WaitForFirstConsumer实现 PVC 延迟绑定,仅当 Pod 调度使用时才完成 PV 绑定,更贴合生产资源节约思路。

二、整体环境说明

  1. 集群架构:单 Master 双 Node K8s 集群
  • master01:192.168.110.166,同时作为 NFS 服务端
  • node01/node02:集群工作节点
  1. 工具依赖
  • Ansible:批量管理所有节点,自动化安装 NFS 服务与客户端
  • NFS:后端共享存储,共享目录/web/share
  • K8s 静态 PV/PVC:实现存储资源抽象绑定
  • Nginx Deployment:多副本业务用于存储功能验证

三、完整操作流程

3.1 Ansible 编写 Playbook 自动化部署 NFS 环境
1.划分主机清单分组

nfs 组:master 节点,部署 NFS 服务端

web 组:master、node01、node02 所有集群节点,安装 NFS 客户端工具 nfs-utils

[root@k8s-master01 dashboard]# cat /etc/ansible/hosts 
[web]
192.168.110.166
192.168.110.168
192.168.110.167

[nfs]
192.168.110.166
2.编写双 Playbook剧本

Play1:针对 nfs 组,安装 nfs-utils,创建共享目录/web/share,写入 /etc/exports 配置共享规则,重载 exports 生效,放行读写权限

Play2:针对 web 组批量安装 NFS 客户端依赖包,保证 kubelet 可调用 mount 挂载 NFS 服务

[root@k8s-master01 ~]# cat nfs_install.yaml 
# =========第一部分:nfs组,部署NFS服务端(仅192.168.110.166执行)=========
- hosts: nfs   # hosts:指定这套任务在哪批主机执行;nfs代表主机清单inventory里[nfs]分组的主机
  remote_user: root    # remote_user:登录远端机器使用什么用户,这里用root账号执行任务
  tasks            # 第3行:tasks:任务列表,下面每一条 `- name` 就是一个任务
    - name: 安装 nfs-utils   # name:任务描述名字,执行的时候屏幕会打印这个名字,方便排错
      yum:                   # yum模块,调用远端机器yum软件管理器
        name: nfs-utils      # name:要安装的软件包名 nfs‑utils
        state: present       # present:确保软件包存在;已安装就跳过,没安装就安装(幂等特性)

    # 任务2:启动nfs‑server服务、开机自启(只有服务端需要启动该服务)
    - name: 启动nfs-server并设置开机自启
      systemd:               # systemd模块,操作systemd管理的服务(等价systemctl命令)
        name: nfs-server     # name:要操作的服务名字 nfs‑server
        state: started       # started:确保服务处于运行状态;已经启动就不重复执行
        enabled: yes         # yes:设置开机自动启动,等价 systemctl enable nfs-server

# =========第二部分:web组,部署NFS客户端(166/167/168,只装软件,不启动nfs‑server)=========
- hosts: web
  remote_user: root
  tasks:
    - name: 安装nfs客户端工具 nfs-utils # name:任务描述,执行时屏幕输出
      yum:                              # yum模块调用远端yum包管理器
        name: nfs-utils                 # 需要安装的软件包
        state: present                  # present:已安装就跳过,未安装则安装,幂等特性
[root@k8s-master01 ~]# 

3.环境校验要点
  • NFS 服务端执行exportfs -v确认共享目录正常导出
  • 节点连通性测试,避免 Ansible SSH 无法连接问题;本机 IP 执行 ansible 命令需配置免密或 local 连接模式
3.2 建 StorageClass 存储类(延迟绑定模式)

StorageClass 用于对集群存储资源统一分类管理,本次使用静态 NFS,无自动供给器no-provisioner

1.编写 nfs-storage-class.yaml
[root@k8s-master01 dashboard]# cat nfs-storage-class.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage-class
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer # 等待Pod消费PVC再绑定PV
[root@k8s-master01 dashboard]# 

2.生效并查看资源
kubectl apply -f nfs-storage-class.yaml
kubectl get storageclass
[root@k8s-master01 dashboard]# kubectl get storageclass
NAME                PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
nfs-storage-class   kubernetes.io/no-provisioner   Retain          WaitForFirstConsumer   false                  13m
[root@k8s-master01 dashboard]# 

核心特性:WaitForFirstConsumer不会在 PVC 创建时立刻绑定 PV,PVC 状态长期 Pending,只有创建使用 PVC 的 Pod 后,才触发 PV-PVC 绑定。

3.3 创建静态 PV 关联 NFS 共享目录

PV 预分配 NFS 底层存储空间,指定 RWX 多读写模式,绑定同名 StorageClass。

[root@k8s-master01 dashboard]# cat nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-web
  labels:
    type: pv-web
spec:
  capacity:
    storage: 5Gi 
  accessModes:
    - ReadWriteMany
  storageClassName: nfs-storage-class         #存储类对应的名字
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: "/web/share"    #nfs共享的目录
    server: 192.168.110.166   #nfs服务器的ip地址
    readOnly: false   #访问模式
[root@k8s-master01 dashboard]# 

kubectl apply -f nfs-pv.yaml
kubectl get pv
[root@k8s-master01 dashboard]# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS        VOLUMEATTRIBUTESCLASS   REASON   AGE
pv-web   5Gi        RWX            Retain           Available           nfs-storage-class   <unset>                          6m13s
[root@k8s-master01 dashboard]# 

创建完成后 PV 状态为Available,等待 PVC 匹配。

3.4 创建 PVC 申请存储资源

PVC 向集群申请指定容量、访问模式的存储,关联同一个 StorageClass。

[root@k8s-master01 dashboard]# cat nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-web
spec:
  accessModes:
  - ReadWriteMany      
  resources:
     requests:
       storage: 1Gi
  storageClassName: nfs-storage-class
[root@k8s-master01 dashboard]# kubectl get pvc
NAME      STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS        VOLUMEATTRIBUTESCLASS   AGE
pvc-web   Pending                                      nfs-storage-class   <unset>                 6m4s
[root@k8s-master01 dashboard]# 

因 StorageClass 开启WaitForFirstConsumer,此时 PVC 状态为Pending,不会绑定 PV。

3.5 创建 Nginx Deployment 挂载 PVC
# 定义资源API版本,Deployment属于apps/v1分组
apiVersion: apps/v1
# 资源类型:无状态应用控制器Deployment
kind: Deployment
# 资源元数据:名称、标签用于筛选管理
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  # 启动2个Nginx副本Pod,实现多实例共享存储
  replicas: 2
  # 标签选择器,匹配Pod模板内labels,控制器用来管理Pod
  selector:
    matchLabels:
      app: nginx
  # Pod模板,定义创建Pod的标准配置
  template:
    metadata:
      labels:
        app: nginx
    spec:
      # 声明Pod要挂载的存储卷,和容器内挂载配置关联
      volumes:
        - name: pv-storage-nfs
          # 绑定前面创建好的PVC资源pvc-web
          persistentVolumeClaim:
            claimName: pvc-web
      # 容器配置列表
      containers:
        - name: pv-container-nfs
          # 使用私有Harbor仓库内的Nginx镜像
          image: hb.reg.com/k8s/nginx:1.27.4
          # 镜像拉取策略:本地存在镜像则不重新拉取,节省带宽
          imagePullPolicy: IfNotPresent
          # 容器暴露80端口,命名方便管理
          ports:
            - containerPort: 80
              name: "http-server"
          # 容器内挂载配置,将PVC存储映射到网页目录
          volumeMounts:
            - mountPath: "/usr/share/nginx/html"
              # 名称必须和上方volumes的name完全一致,实现关联
              name: pv-storage-nfs

关键层级规范:volumescontainers平级;volumeMounts写在容器内部,name 一一对应。

  1. 部署业务并重新查看 PVC/PV
[root@k8s-master01 dashboard]# kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
[root@k8s-master01 dashboard]# kubectl get pv,pvc,pod
NAME                      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS        VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pv-web   5Gi        RWX            Retain           Bound    default/pvc-web   nfs-storage-class   <unset>                          10m

NAME                            STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS        VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/pvc-web   Bound    pv-web   5Gi        RWX            nfs-storage-class   <unset>                 8m45s

NAME                                    READY   STATUS    RESTARTS        AGE
pod/nginx-deployment-6474d74567-9rxtf   1/1     Running   0               5s
pod/nginx-deployment-6474d74567-x4vpc   1/1     Running   0               5s
pod/secret-volume-pod                   1/1     Running   1 (4h20m ago)   7d2h
[root@k8s-master01 dashboard]# 


Pod 启动后触发延迟绑定,PVC 状态由 Pending 变为 Bound;Pod 最终变为 Running。

3.6 持久化共享数据验证
1.在第一个 Pod 写入网页文件
[root@k8s-master01 dashboard]#  kubectl -it exec nginx-deployment-6474d74567-9rxtf -- /bin/bash
root@nginx-deployment-6474d74567-9rxtf:/# cd /usr/
bin/     games/   include/ lib/     lib64/   libexec/ local/   sbin/    share/   src/     
root@nginx-deployment-6474d74567-9rxtf:/# cd /usr/share/nginx/html/
root@nginx-deployment-6474d74567-9rxtf:/usr/share/nginx/html# echo "cb666666!" > index.html 
bash: index.html: Permission denied
root@nginx-deployment-6474d74567-9rxtf:/usr/share/nginx/html# echo "cb666666!" > index.html 
root@nginx-deployment-6474d74567-9rxtf:/usr/share/nginx/html# 

此处刚开始报权限出差,没有写入权限,先进入master节点,增加临时测试权限:

[root@k8s-master01 dashboard]# chmod 777 /web/share/index.html
[root@k8s-master01 dashboard]# 

2.NFS 服务端校验数据
cat /web/share/index.html
[root@k8s-master01 dashboard]# cat /web/share/index.html
cb666666!
[root@k8s-master01 dashboard]# 

3.多 Pod 共享验证

进入另一副本 Pod 读取 html 目录,文件内容完全一致,RWX 多 Pod 共享生效。

[root@k8s-master01 dashboard]# kubectl exec -it nginx-deployment-6474d74567-x4vpc -- bash
root@nginx-deployment-6474d74567-x4vpc:/# cat /usr/share/nginx/html/index.html
cb666666!
root@nginx-deployment-6474d74567-x4vpc:/# 

四、实操故障汇总与排障方案

4.1 故障一:PVC 创建后长期 Pending 不绑定 PV

现象:创建完 SC、PV、PVC 后,PVC 一直处于 Pending 状态,不变为 Bound。

原因:StorageClass 开启了WaitForFirstConsumer 延迟绑定模式,K8s 规则为:没有 Pod 使用该 PVC 时,不进行 PV-PVC 绑定,节约集群存储资源。

解决方案:正常现象,无需修复。部署挂载该 PVC 的 Deployment 业务 Pod 后,会自动触发绑定,PVC 状态自动变为 Bound。

区分误区:如果 SC 为 Immediate 模式仍 Pending,是 PV 容量、访问模式、StorageClass 名称不匹配导致故障。

4.2 故障二:Nginx Pod 内写入文件报 Permission denied 权限拒绝

现象:Pod 进入 /usr/share/nginx/html 目录,新建/覆盖 index.html 提示权限不足,无法写入数据。

核心原因:NFS 默认安全机制root_squash,容器内 root 用户会被 NFS 压缩为匿名 nobody 用户,即使目录 755/777 也无法写入原有文件。

错误排坑:仅修改目录 chmod 777 无效,旧文件权限不会自动更新,依然写入失败。

最终根治方案:增加临时权限

[root@k8s-master01 dashboard]# chmod 777 /web/share/index.html
4.3 故障三:资源删除卡死 Terminating(PV/PVC/Pod)

错误操作:先删 PVC、再删 Deployment,或者直接删除 Bound 状态 PV。

故障原理:

K8s 自带 PVC 保护机制:有 Pod 挂载 PVC 时,PVC 禁止删除,会卡死 Terminating

Bound 状态 PV 禁止直接删除,会永久僵死

标准正确删除顺序(生产/实验通用):

删除 Deployment(销毁Pod) → 等待 Pod 清空 → 删除 PVC → PV 变为 Released → 删除 PV

卡死急救命令:

kubectl delete pod pod名称 --force --grace-period=0

4.4 故障四:PV、PVC 无法绑定(SC名称不匹配)
  1. 所有 K8s Node 节点未安装 nfs-utils 客户端,无法识别 NFS 存储
  2. Pending 不绑定:优先查 SC 延迟绑定 + 名称统一;

五、实践总结

(一)核心落地成果
  1. 使用 Ansible 实现 NFS 环境自动化批量部署,掌握多 play 剧本、SSH 连通排错、客户端统一安装;
  2. 掌握 StorageClass 静态存储类用法,理解WaitForFirstConsumer延迟绑定与 Immediate 立即绑定两种模式差异;
  3. 完整吃透静态 PV、PVC 生命周期,RWX 多副本共享存储落地,规范 Deployment 挂载 yaml 层级;
  4. 完整打通链路:Ansible 部署 NFS 服务端 / 客户端 → StorageClass 分类存储 → PV 绑定 NFS 目录 → PVC 申请存储 → Deployment 挂载使用,数据持久共享;
  5. 覆盖集群镜像、存储、组件配置、资源删除全场景故障排查。
(二)技术知识点总结
  1. NFS 仅需服务端配置共享,所有节点安装客户端即可,kubelet 自动完成挂载,无需手动 mount;
  2. StorageClass 作用:统一集群存储资源分类,区分本地存储、NFS、云存储;静态 NFS 使用no-provisioner供给器;
  3. WaitForFirstConsumer优势:无 Pod 使用时不占用 PV 绑定,节约存储资源,生产环境推荐;
  4. PV/PVC Bound 仅代表元数据匹配,后端 NFS 连通性只有 Pod 挂载时才校验;
  5. 多副本共享存储必须使用ReadWriteMany访问模式,RWO 仅支持单 Pod 挂载。
(三)后续拓展展望
  1. 部署 nfs-subdir-external-provisioner 实现动态 StorageClass,无需手动创建 PV;
  2. 将整套 NFS+StorageClass + 业务资源写入 Ansible 剧本,一键自动化部署完整存储业务环境;
  3. 对接 Harbor 私有镜像仓库,Deployment 从私有仓库拉取镜像;
  4. 接入 Prometheus+Grafana 监控 PV/PVC 存储容量、NFS 读写指标,配置存储容量告警。

更多推荐