一、前置准备(所有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 |
subjects的namespace必须和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.11、NFS_PATH: /data/nfs对应你之前配置的NFS共享目录 4. 通过volumes和volumeMounts把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是"",无法绑定。
- 例:PV是
-
排查 :
kubectl describe pvc <pvc-name>查看Events字段。
2. NFS挂载失败
-
现象 :Pod处于
ContainerCreating,事件提示mount failed: exit status 32。 -
排查步骤:
-
所有节点是否安装
nfs-utils; -
NFS服务端
exportfs -v是否有对应目录导出; -
防火墙是否放行
nfs/mountd/rpc-bind服务; -
SELinux的
nfs_export_all_rw是否为on; -
节点是否能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