K8s 综合实践笔记:Ansible 自动化 NFS + StorageClass + 静态 PV/PVC+Nginx Deployment
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 绑定,更贴合生产资源节约思路。
二、整体环境说明
- 集群架构:单 Master 双 Node K8s 集群
- master01:192.168.110.166,同时作为 NFS 服务端
- node01/node02:集群工作节点
- 工具依赖
- 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
关键层级规范:volumes与containers平级;volumeMounts写在容器内部,name 一一对应。
- 部署业务并重新查看 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名称不匹配)
- 所有 K8s Node 节点未安装 nfs-utils 客户端,无法识别 NFS 存储
- Pending 不绑定:优先查 SC 延迟绑定 + 名称统一;
五、实践总结
(一)核心落地成果
- 使用 Ansible 实现 NFS 环境自动化批量部署,掌握多 play 剧本、SSH 连通排错、客户端统一安装;
- 掌握 StorageClass 静态存储类用法,理解
WaitForFirstConsumer延迟绑定与 Immediate 立即绑定两种模式差异; - 完整吃透静态 PV、PVC 生命周期,RWX 多副本共享存储落地,规范 Deployment 挂载 yaml 层级;
- 完整打通链路:Ansible 部署 NFS 服务端 / 客户端 → StorageClass 分类存储 → PV 绑定 NFS 目录 → PVC 申请存储 → Deployment 挂载使用,数据持久共享;
- 覆盖集群镜像、存储、组件配置、资源删除全场景故障排查。
(二)技术知识点总结
- NFS 仅需服务端配置共享,所有节点安装客户端即可,kubelet 自动完成挂载,无需手动 mount;
- StorageClass 作用:统一集群存储资源分类,区分本地存储、NFS、云存储;静态 NFS 使用
no-provisioner供给器; WaitForFirstConsumer优势:无 Pod 使用时不占用 PV 绑定,节约存储资源,生产环境推荐;- PV/PVC Bound 仅代表元数据匹配,后端 NFS 连通性只有 Pod 挂载时才校验;
- 多副本共享存储必须使用
ReadWriteMany访问模式,RWO 仅支持单 Pod 挂载。
(三)后续拓展展望
- 部署 nfs-subdir-external-provisioner 实现动态 StorageClass,无需手动创建 PV;
- 将整套 NFS+StorageClass + 业务资源写入 Ansible 剧本,一键自动化部署完整存储业务环境;
- 对接 Harbor 私有镜像仓库,Deployment 从私有仓库拉取镜像;
- 接入 Prometheus+Grafana 监控 PV/PVC 存储容量、NFS 读写指标,配置存储容量告警。
更多推荐
所有评论(0)