文章目录
- 一、前置内容错误检查与修正说明
- Kubernetes 存储实战:NFS PV/PVC、ConfigMap 与探针测试
- 1. 环境信息回顾
- 2. 核心概念梳理
- 2.1 PV 与 PVC
- 三种访问模式
- 2.2 NFS 存储
- 2.3 ConfigMap 与 Secret
- 2.4 三类探针
- 3. 前置准备工作
- 3.1 全节点 NFS 客户端安装与验证
- 3.2 离线镜像导入(containerd 运行时)
- 4. NFS 存储目录创建与权限配置
- 5. 创建 PV 与 PVC(静态供给)
- 5.1 Nginx 共享存储 PV/PVC
- nginx-pv.yaml
- nginx-pvc.yaml
- 5.2 MySQL 独占存储 PV/PVC
- mysql-pv.yaml
- mysql-pvc.yaml
- 5.3 执行创建与绑定验证
- 6. 部署 Nginx 无状态应用(Deployment + PVC + 探针)
- 6.1 创建 ConfigMap(Nginx 完整配置)
- nginx-configmap.yaml
- 6.2 Deployment + Service 完整定义
- nginx-deployment.yaml
- 6.3 部署与基础验证
- 7. 部署 MySQL 有状态应用(StatefulSet + PVC + 三探针)
- 7.1 创建 Secret(MySQL 账号密码)
- 7.2 StatefulSet + Service 完整定义
- mysql-statefulset.yaml
- 7.3 部署与基础验证
- 三、全流程测试验证 SOP
- SOP 1:存储层验证
- SOP 2:Nginx 无状态应用验证
- SOP 3:MySQL 有状态应用验证
- SOP 4:探针功能专项验证
- 4.1 Nginx 探针验证
- 4.2 MySQL 探针验证
- 四、最终交付物清单
一、前置内容错误检查与修正说明
我仔细核对了你的离线集群环境与全部配置步骤,共发现6 处关键问题/遗漏,已在下文全部修正:
| 序号 | 问题点 | 影响 | 修正方案 |
|---|---|---|---|
| 1 | 离线镜像准备步骤缺失 | 离线环境无法拉取镜像,Pod 直接启动失败 | 补充 containerd 离线镜像导入全步骤,适配你的 RPM 离线部署模式 |
| 2 | Nginx 配置不完整 | ConfigMap 仅挂载站点配置,log_format main未定义,Nginx 启动报错 | 修正 ConfigMap 结构,统一主配置与站点配置,确保语法完整 |
| 3 | MySQL 探针密码硬编码 | 探针命令直接写死明文密码,Secret 修改后探针失效,不符合安全规范 | 改为通过环境变量$MYSQL_ROOT_PASSWORD引用 Secret,使用 shell 解析变量 |
| 4 | NFS 权限适配遗漏 | 你的 NFS 开启了all_squash映射为 UID 1000,而 MySQL 容器默认用户为 UID 999,会导致数据目录无写入权限,启动失败 | 补充securityContext配置,统一容器运行 UID 与 NFS 匿名用户一致 |
| 5 | StatefulSet 静态 PV 绑定规则不明确 | volumeClaimTemplates生成的 PVC 名称为mysql-data-mysql-0,与静态 PV 标签匹配规则未说明,易导致 PVC 一直 Pending | 明确 PV 标签匹配规则,补充单实例固定 PVC 挂载方案 |
| 6 | 验证步骤不完整 | 缺少配置语法检查、服务连通性校验、权限验证等前置检查 | 补充全流程分级验证步骤,每步明确预期结果与排错方法 |
Kubernetes 存储实战:NFS PV/PVC、ConfigMap 与探针测试
1. 环境信息回顾
本次实战严格匹配你的离线高可用集群环境,核心参数如下:
| 类别 | 参数值 |
|---|---|
| Kubernetes 版本 | v1.24.17 |
| 容器运行时 | containerd 1.6.21 |
| 集群架构 | 3 Master + 2 Worker,三主两从高可用 |
| apiserver VIP | https://192.168.30.140:6443 |
| 网络插件 | Calico |
| Pod 网段 | 10.244.0.0/16 |
| Service 网段 | 10.96.0.0/12 |
| NFS 存储服务器 | 192.168.30.249,共享根路径/data/k8s |
| NFS 配置 | rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000 |
| 节点系统 | CentOS 7.9,全量 RPM 离线部署 |
2. 核心概念梳理
2.1 PV 与 PVC
- PV(PersistentVolume):集群级存储资源,由管理员提前创建,对应后端真实存储,生命周期独立于 Pod,数据不会随 Pod 删除而消失。
- PVC(PersistentVolumeClaim):用户对存储的需求声明(容量、访问模式、标签),K8s 自动匹配并绑定符合条件的 PV,Pod 通过 PVC 间接使用存储。
三种访问模式
| 模式 | 缩写 | 含义 | 适用场景 |
|---|---|---|---|
| ReadWriteOnce | RWO | 仅单个节点可挂载读写 | 单机数据库、单实例有状态应用 |
| ReadOnlyMany | ROX | 多节点只读挂载 | 静态资源共享、配置文件分发 |
| ReadWriteMany | RWX | 多节点同时读写 | 多实例共享日志、文件存储、静态资源 |
2.2 NFS 存储
NFS 是中小规模离线集群最常用的共享存储方案:
- 天然支持
ReadWriteMany多节点共享 - 静态供给:手动创建目录 + 手动创建 PV,适配离线环境
- 注意:
all_squash模式下所有客户端访问都会映射为指定 UID/GID,容器内用户必须与其匹配
2.3 ConfigMap 与 Secret
| 类型 | 用途 | 安全性 | 典型场景 |
|---|---|---|---|
| ConfigMap | 存储非敏感配置、脚本、环境变量 | 明文存储 | Nginx 配置、应用参数、启动脚本 |
| Secret | 存储密码、密钥、证书 | Base64 编码存储 | 数据库账号、认证令牌、TLS 证书 |
2.4 三类探针
| 探针类型 | 作用 | 失败触发行为 |
|---|---|---|
| startupProbe(启动探针) | 检测应用是否启动完成 | 超时未成功则重启容器;保护慢启动应用不被误杀 |
| livenessProbe(存活探针) | 检测容器进程是否正常运行 | 连续失败则重启容器,实现故障自愈 |
| readinessProbe(就绪探针) | 检测服务是否可对外提供流量 | 连续失败则将 Pod 从 Service 端点中摘除,停止转发流量 |
3. 前置准备工作
3.1 全节点 NFS 客户端安装与验证
所有 Master/Worker 节点必须安装 NFS 客户端,否则无法挂载 NFS 卷。
# 离线环境提前将 nfs-utils 及依赖 RPM 导入本地源 yum install -y nfs-utils # 验证 NFS 连通性与共享列表 showmount -e 192.168.30.249✅ 预期输出:
/data/k8s 192.168.30.0/243.2 离线镜像导入(containerd 运行时)
离线环境无法在线拉取镜像,必须提前将镜像包导入所有 Worker 节点的 containerd。
# 1. 提前准备镜像包(nginx:1.25.3、mysql:8.0.36),传输到所有 Worker 节点 # 2. 导入镜像到 k8s.io 命名空间(K8s 默认命名空间) ctr -n k8s.io images import nginx-1.25.3.tar ctr -n k8s.io images import mysql-8.0.36.tar # 3. 验证镜像导入成功 ctr -n k8s.io images list | grep -E "nginx|mysql"✅ 预期输出中包含docker.io/library/nginx:1.25.3、docker.io/library/mysql:8.0.36。
4. NFS 存储目录创建与权限配置
在NFS 服务器(192.168.30.249)上执行,按应用隔离目录并匹配 NFS 权限配置。
# 1. 创建应用独立目录 mkdir -p /data/k8s/nginx/logs /data/k8s/nginx/html mkdir -p /data/k8s/mysql/data # 2. 统一权限为 1000:1000,与 NFS anonuid/anongid 匹配 chown -R 1000:1000 /data/k8s/nginx /data/k8s/mysql chmod -R 755 /data/k8s/nginx /data/k8s/mysql # 3. 刷新 NFS 共享 exportfs -r5. 创建 PV 与 PVC(静态供给)
5.1 Nginx 共享存储 PV/PVC
Nginx 为无状态多实例部署,共享日志与静态资源,使用ReadWriteMany模式。
nginx-pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: nginx-pv labels: app: nginx type: nfs spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: server: 192.168.30.249 path: /data/k8s/nginxnginx-pvc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 5Gi selector: matchLabels: app: nginx5.2 MySQL 独占存储 PV/PVC
MySQL 为有状态单实例部署,数据独占,使用ReadWriteOnce模式。
mysql-pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: mysql-pv labels: app: mysql type: nfs spec: capacity: storage: 20Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain nfs: server: 192.168.30.249 path: /data/k8s/mysql/datamysql-pvc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi selector: matchLabels: app: mysql5.3 执行创建与绑定验证
# 批量创建 kubectl apply -f nginx-pv.yaml -f nginx-pvc.yaml kubectl apply -f mysql-pv.yaml -f mysql-pvc.yaml # 验证绑定状态,STATUS 为 Bound 即为成功 kubectl get pv,pvc✅ 预期结果:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv/nginx-pv 5Gi RWX Retain Bound default/nginx-pvc 10s pv/mysql-pv 20Gi RWO Retain Bound default/mysql-pvc 10s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/nginx-pvc Bound nginx-pv 5Gi RWX 10s persistentvolumeclaim/mysql-pvc Bound mysql-pv 20Gi RWO 10s6. 部署 Nginx 无状态应用(Deployment + PVC + 探针)
Nginx 是典型无状态应用:请求不依赖本地上下文,所有 Pod 完全对等,扩缩容灵活,标准部署方式为Deployment。
本次挂载 NFS 共享卷,实现日志统一收集、静态资源多实例共享。
6.1 创建 ConfigMap(Nginx 完整配置)
修正配置完整性,包含完整的主配置与站点配置,避免日志格式未定义错误。
nginx-configmap.yaml
apiVersion: v1 kind: ConfigMap metadata: name: nginx-conf data: nginx.conf: | user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; } default.conf: | server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; location / { try_files $uri $uri/ =404; } # 健康检查端点,用于探针 location = /healthz { access_log off; return 200 "ok\n"; add_header Content-Type text/plain; } }创建并验证:
kubectl apply -f nginx-configmap.yaml kubectl get configmap nginx-conf6.2 Deployment + Service 完整定义
nginx-deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: # 匹配 NFS 匿名用户 UID,确保写入权限 securityContext: runAsUser: 1000 fsGroup: 1000 containers: - name: nginx image: nginx:1.25.3 imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: http # 卷挂载 volumeMounts: - name: nginx-main-conf mountPath: /etc/nginx/nginx.conf subPath: nginx.conf - name: nginx-site-conf mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf - name: nginx-data mountPath: /var/log/nginx subPath: logs - name: nginx-data mountPath: /usr/share/nginx/html subPath: html # 存活探针:检测进程是否正常 livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 # 就绪探针:检测是否可承接流量 readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 3 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 2 # 卷声明 volumes: - name: nginx-main-conf configMap: name: nginx-conf items: - key: nginx.conf path: nginx.conf - name: nginx-site-conf configMap: name: nginx-conf items: - key: default.conf path: default.conf - name: nginx-data persistentVolumeClaim: claimName: nginx-pvc --- # 集群内访问服务 apiVersion: v1 kind: Service metadata: name: nginx spec: selector: app: nginx type: ClusterIP ports: - port: 80 targetPort: 806.3 部署与基础验证
kubectl apply -f nginx-deployment.yaml # 等待 Pod 全部 Ready kubectl get pods -l app=nginx -w7. 部署 MySQL 有状态应用(StatefulSet + PVC + 三探针)
MySQL 是典型有状态应用:依赖本地持久化数据、需要稳定的网络标识,标准部署方式为StatefulSet。
本次使用单实例部署,配置完整的三类探针,数据持久化到 NFS 独立卷。
7.1 创建 Secret(MySQL 账号密码)
敏感信息使用 Secret 存储,不使用 ConfigMap。
kubectl create secret generic mysql-secret \ --from-literal=root-password=Root@k8s2025 \ --from-literal=user-password=Ops@k8s2025验证:
kubectl get secret mysql-secret7.2 StatefulSet + Service 完整定义
修正探针密码硬编码问题,通过环境变量引用 Secret;补充安全上下文匹配 NFS 权限。
mysql-statefulset.yaml
# Headless Service:StatefulSet 必备,用于 Pod 固定域名解析 apiVersion: v1 kind: Service metadata: name: mysql-headless labels: app: mysql spec: selector: app: mysql clusterIP: None ports: - port: 3306 name: mysql --- # 普通业务服务:数据库访问入口 apiVersion: v1 kind: Service metadata: name: mysql spec: selector: app: mysql type: ClusterIP ports: - port: 3306 targetPort: 3306 --- apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: serviceName: "mysql-headless" replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: # 匹配 NFS 匿名用户 UID,解决 all_squash 权限问题 securityContext: runAsUser: 1000 fsGroup: 1000 containers: - name: mysql image: mysql:8.0.36 imagePullPolicy: IfNotPresent env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-secret key: root-password - name: MYSQL_DATABASE value: opsdb - name: MYSQL_USER value: opsuser - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: mysql-secret key: user-password ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-data mountPath: /var/lib/mysql # 启动探针:MySQL 启动慢,先等待启动完成再激活其他探针 startupProbe: tcpSocket: port: 3306 initialDelaySeconds: 15 periodSeconds: 5 failureThreshold: 30 # 存活探针:检测 MySQL 进程是否存活,通过环境变量读取密码 livenessProbe: exec: command: - /bin/sh - -c - mysqladmin ping -uroot -p$MYSQL_ROOT_PASSWORD periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 # 就绪探针:检测数据库是否可正常读写 readinessProbe: exec: command: - /bin/sh - -c - mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SELECT 1;" periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 2 # 单实例直接挂载预先创建的固定 PVC volumes: - name: mysql-data persistentVolumeClaim: claimName: mysql-pvc说明:单实例 MySQL 直接使用固定 PVC 更清晰;若需多实例主从集群,可改用
volumeClaimTemplates配合 StorageClass 动态创建。
7.3 部署与基础验证
kubectl apply -f mysql-statefulset.yaml # 等待 Pod 启动完成 kubectl get pods -l app=mysql -w三、全流程测试验证 SOP
按「存储层 → 配置层 → 应用层 → 功能层 → 持久化 → 探针」分级验证,每步明确预期结果与排错方法。
SOP 1:存储层验证
目标:确认 PV/PVC 绑定正常,NFS 挂载权限正确
| 步骤 | 操作命令 | 预期结果 | 异常排查 |
|---|---|---|---|
| 1-1 | kubectl get pv,pvc | 所有 PV/PVC 状态为 Bound | 若 Pending:检查容量、访问模式、标签是否匹配;检查节点 NFS 客户端是否安装 |
| 1-2 | 任选一个 Nginx Pod 进入,执行df -h /var/log/nginx | 显示 NFS 挂载路径192.168.30.249:/data/k8s/nginx | 若无挂载:查看 Pod events,检查 NFS 网络连通性 |
| 1-3 | 容器内执行touch /var/log/nginx/test.tmp && ls -l /var/log/nginx/test.tmp | 文件创建成功,权限为 1000:1000 | 若权限报错:检查 NFS 目录 owner 与 securityContext 配置 |
| 1-4 | NFS 服务器执行ls -l /data/k8s/nginx/logs/ | 能看到容器内创建的 test.tmp 文件 | 若看不到:检查 NFS 导出配置、IP 白名单 |
SOP 2:Nginx 无状态应用验证
目标:确认配置生效、服务可用、无状态特性正常
| 步骤 | 操作命令 | 预期结果 | 异常排查 |
|---|---|---|---|
| 2-1 | kubectl exec -it <nginx-pod> -- nginx -t | 输出test is successful,配置语法正确 | 若报错:检查 ConfigMap 挂载路径与配置内容 |
| 2-2 | kubectl run curl-test --rm -it --image=curlimages/curl -- curl http://nginx.default.svc.cluster.local/healthz | 返回ok,状态码 200 | 若失败:检查 Service 选择器、Pod 端口、就绪探针状态 |
| 2-3 | kubectl get endpoints nginx | ENDPOINTS 包含 3 个 Pod IP,与副本数一致 | 若数量不足:检查就绪探针是否通过 |
| 2-4 | 删除任意 Pod:kubectl delete pod <nginx-pod-name>,再执行kubectl get pods -l app=nginx | 自动重建新 Pod,名称与原 Pod 不同,总副本数保持 3 | 若不重建:检查 Deployment 配置、节点资源 |
| 2-5 | 重建后进入新 Pod,执行ls /var/log/nginx/ | 之前的日志文件依然存在 | 若丢失:检查 PVC 挂载是否正确 |
SOP 3:MySQL 有状态应用验证
目标:确认数据库可用、数据持久化、有状态特性正常
| 步骤 | 操作命令 | 预期结果 | 异常排查 |
|---|---|---|---|
| 3-1 | kubectl get statefulset mysql | Pod 名称固定为mysql-0,READY 为 1/1 | 若名称不对:检查 StatefulSet 配置 |
| 3-2 | 连接测试:kubectl run mysql-client --rm -it --image=mysql:8.0.36 -- mysql -h mysql -uroot -pRoot@k8s2025 -e "show databases;" | 显示opsdb等默认数据库 | 若连接失败:检查 Service、端口、就绪探针、密码 |
| 3-3 | 创建测试数据:kubectl run mysql-client --rm --image=mysql:8.0.36 -- mysql -h mysql -uroot -pRoot@k8s2025 -e "use opsdb; create table test(id int); insert into test values(1);" | 执行成功无报错 | 若表创建失败:检查数据目录写入权限 |
| 3-4 | 删除 Pod 重建:kubectl delete pod mysql-0,等待重建完成 | Pod 名称依然是mysql-0 | 若名称变化:检查是否误用 Deployment |
| 3-5 | 重建后验证数据:kubectl run mysql-client --rm --image=mysql:8.0.36 -- mysql -h mysql -uroot -pRoot@k8s2025 -e "use opsdb; select * from test;" | 查询到之前插入的数据1 | 若数据丢失:检查 PVC 挂载、PV 回收策略 |
SOP 4:探针功能专项验证
目标:确认三类探针按预期触发对应动作
4.1 Nginx 探针验证
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 4.1.1 | 修改 ConfigMap,将/healthz返回码改为 403,滚动更新 | Pod READY 变为 0/1,Endpoints 中移除该 Pod(就绪探针生效) |
| 4.1.2 | 进入容器执行nginx -s stop | 几秒后 Pod 自动重启,RESTARTS 计数 +1(存活探针生效) |
4.2 MySQL 探针验证
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 4.2.1 | 观察 MySQL 启动前 15 秒 | 存活/就绪探针不触发,仅启动探针检测(启动探针生效) |
| 4.2.2 | 进入容器执行killall mysqld | Pod 自动重启,RESTARTS 计数 +1(存活探针生效) |
| 4.2.3 | 模拟数据库锁表/只读 | Pod READY 变为 0/1,Service 端点移除(就绪探针生效) |
四、最终交付物清单
| 序号 | 文件名 | 类型 | 说明 |
|---|---|---|---|
| 1 | nginx-pv.yaml/nginx-pvc.yaml | PV/PVC 配置 | Nginx 共享存储卷 |
| 2 | mysql-pv.yaml/mysql-pvc.yaml | PV/PVC 配置 | MySQL 独占存储卷 |
| 3 | nginx-configmap.yaml | ConfigMap 配置 | Nginx 完整主配置+站点配置 |
| 4 | nginx-deployment.yaml | 应用部署配置 | Nginx Deployment + Service + 双探针 |
| 5 | mysql-statefulset.yaml | 应用部署配置 | MySQL StatefulSet + 双 Service + 三探针 |
| 6 | k8s-storage-test-sop.md | 验证文档 | 全流程分级测试验证步骤与排错指南 |
| 7 | 离线镜像包 | 二进制文件 | nginx-1.25.3.tar、mysql-8.0.36.tar |
需要我把所有 YAML 文件打包整理成一个可直接执行的部署脚本,或者补充 NFS StorageClass 动态供给的配置吗?