K8s(10)NFS 的动态 PV 创建数据库给k8s的mysql和redis

K8s 本地集群实战全流程复盘

(基于你的网络拓扑:141 Master / 142 Node1 / 143 Node2)

环境基线

  • Master (控制机): 192.168.222.141(操作机,存放 yaml)

  • Node1 (工作节点): 192.168.222.142(运行 Pod)

  • Node2 (NFS 服务器): 192.168.222.143(提供存储)

  • NFS 共享目录: /opt/k8s

  • Master 工作目录: /root/k8s/nfs-provisioner


第一步:搭建 NFS 底层存储服务 (在 Node2 上)

📍 主机/IP192.168.222.143(Node2)

📂 当前目录:任意(直接在命令行操作)

1. 核心动作

安装 NFS 服务,创建共享目录,并配置 exports 文件。

复制代码

bash

bash

复制代码
# 1. 安装软件
yum install -y nfs-utils rpcbind

# 2. 创建共享目录
mkdir -p /opt/k8s

# 3. 授权 (关键!K8s Pod 内 UID 通常是 root 或非 root,必须给足权限)
chmod 777 /opt/k8s

# 4. 配置 exports
echo "/opt/k8s *(rw,sync,no_root_squash)" > /etc/exports

# 5. 启动服务
systemctl start rpcbind && systemctl enable rpcbind
systemctl start nfs && systemctl enable nfs

🧠 核心知识点

  • NFS 原理 :Node1/Node2 作为 Client,通过 RPC 协议挂载 Node2 的 /opt/k8s

  • 权限 no_root_squash :如果不加这个,K8s Pod 内的进程(即使是 root)在 NFS 目录下写入会被拒绝(变成 nfsnobody),导致 PV 挂载进去但无法读写。

🔥 排错核心

现象 原因 排查命令
其他节点挂载失败 NFS 服务未启动或防火墙拦截 systemctl status nfs, showmount -e 192.168.222.143
Pod 报错 Permission Denied 目录权限不足或未加 no_root_squash 检查 Node2 上的 /opt/k8s权限是否为 777

第二步:部署 NFS Provisioner (在 Master 上)

📍 主机/IP192.168.222.141(Master)

📂 当前目录/root/k8s/nfs-provisioner

1. 核心动作

编写并应用 YAML,目的是让 K8s 拥有一个能自动创建 PV 的"动态供应器"。

存放路径/root/k8s/nfs-provisioner

1. RBAC 权限 (rbac.yaml)

赋予 Provisioner 操作 PV/PVC 的权限

复制代码

yaml

yaml

复制代码
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
  resources: ["nodes", "persistentvolumes", "persistentvolumeclaims"]
  verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims/status"]
  verbs: ["update"]
- apiGroups: ["storage.k8s.io"]
  resources: ["storageclasses"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
  name: nfs-client-provisioner
  namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

2. Deployment (deployment.yaml)

注意修改 NFS_SERVER为你的 Node2 IP

复制代码

yaml

yaml

复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
      - name: nfs-client-provisioner
        image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2
        volumeMounts:
        - name: nfs-client-root
          mountPath: /persistentvolumes
        env:
        - name: PROVISIONER_NAME
          value: fuseim.pri/ifs  # 必须与 StorageClass 里的 provisioner 一致
        - name: NFS_SERVER
          value: 192.168.222.143  # 👈 改成你的 Node2 IP
        - name: NFS_PATH
          value: /opt/k8s         # 👈 改成你的 NFS 目录
      volumes:
      - name: nfs-client-root
        nfs:
          server: 192.168.222.143 # 👈 改成你的 Node2 IP
          path: /opt/k8s          # 👈 改成你的 NFS 目录

3. StorageClass (class.yaml)

复制代码

yaml

yaml

复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage
provisioner: fuseim.pri/ifs  # 👈 必须与 Deployment 里的 PROVISIONER_NAME 一致
parameters:
  archiveOnDelete: "false"
reclaimPolicy: Delete
volumeBindingMode: Immediate
复制代码

bash

bash

复制代码
cd /root/k8s/nfs-provisioner
# 这里准备好 deployment.yaml, rbac.yaml, class.yaml
kubectl apply -f .

2. 关键 YAML 配置点

  • Deployment (nfs-client-provisioner)

    • env: NFS_SERVER=192.168.222.143, NFS_PATH=/opt/k8s

    • volumes: 这里挂载的其实是用来放 deployment.yaml等文件的本地目录(如果用了 hostPath)。

  • StorageClass

    • provisioner: fuseim.pri/ifs(名字自己起,但要和 Deployment 里环境变量一致)

🧠 核心知识点

  • StorageClass 的作用 :取代静态 kubectl create -f pv.yaml。以后只要 PVC 申请了这种 StorageClass,Provisioner 就会自动在 Node2 的 /opt/k8s下建一个子目录作为 PV。

  • RBAC (Role/ClusterRole):Provisioner 需要权限去操作 K8s 的 PVC 和 PV,这是新手最容易漏的。

🔥 排错核心

现象 原因 排查命令
Provisioner Pod 一直 Pending 可能是 NFS 连不上,或者 imagePullBackOff kubectl describe pod nfs-client-provisioner-xxx -n default看 Events
PVC 一直 Pending 检查 StorageClass 名字是否写错,或者 Provisioner 容器没跑起来 kubectl get sc, kubectl get pvc

第三步:部署 MySQL (在 Master 上操作,调度到 Node2)

📍 主机/IP192.168.222.141(Master)

📂 当前目录/root/k8s/mysql(假设你新建了这个目录)

1. 核心动作

编写 Deployment 和 Service。这里利用 NodeSelector​ 强制把 MySQL 调度到 143 (NFS 服务器) 上。

复制代码

bash

bash

复制代码
cd /root/k8s/mysql
kubectl apply -f mysql-deploy.yaml
kubectl apply -f mysql-svc.yaml

2. 关键 YAML 配置点

  • Deployment:

    • volumes: 定义一个 PersistentVolumeClaim,指向第二步创建的 StorageClass

    • nodeSelector: disktype: nfs(假设你在 143 上打了这个标签)。

  • Service:

    • ClusterIP: 暴露内部端口。

    • NodePort(可选): 如果想从宿主机 141 访问,开 30036 端口。

存放路径/root/k8s/mysql

1. Service (mysql-svc.yaml)

使用 ClusterIP,让 Redis 能通过 mysql这个名字访问

复制代码

yaml

yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None  # Headless Service
  selector:
    app: mysql
  ports:
  - port: 3306

2. StatefulSet (mysql-sts.yaml)

注意修改 nodeSelector的 IP 或标签

复制代码

yaml

yaml

复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      # 强制调度到 Node2 (NFS 服务器)
      nodeSelector:
        kubernetes.io/hostname: "k8s-node2" # 或者 node-143,看你实际的 hostname
      securityContext:
        runAsUser: 999
        fsGroup: 999
      containers:
      - name: mysql
        image: mysql:5.7
        imagePullPolicy: IfNotPresent
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "123456"
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: nfs-storage  # 👈 使用上面定义的 StorageClass
      resources:
        requests:
          storage: 5Gi

🧠 核心知识点

  • 为什么要把 MySQL 放在 NFS 节点?

    • 因为 MySQL 数据文件存在 /opt/k8s下。如果 Pod 飘到 Node1,而 Node1 没有挂载 NFS,数据就丢了。数据必须跟着存储走
  • 持久化声明 (PVC):它像一个"申请单",告诉 Provisioner:"给我一块硬盘,类型是 nfs"。

🔥 排错核心

现象 原因 排查命令
MySQL Pod CrashLoopBackOff 通常是权限问题。Pod 里的 mysql 用户 UID 和 NFS 目录权限不匹配 kubectl logs mysql-pod-name,看是否有 Permission denied
Pod 无法调度 (Pending) 找不到带有 disktype: nfs标签的节点 kubectl get nodes -o wide检查标签是否打上

第四步:部署 Redis (在 Master 上操作,调度到 Node1)

📍 主机/IP192.168.222.141(Master)

📂 当前目录/root/k8s/redis

1. 核心动作

与 MySQL 类似,但因为 Redis 不需要强依赖 NFS(可以存内存或存本地盘,这里为了练手也可以存 NFS),我们可以把它调度到 Node1 (142)。

复制代码

bash

bash

复制代码
cd /root/k8s/redis
kubectl apply -f redis-deploy.yaml
kubectl apply -f redis-svc.yaml

2. 关键 YAML 配置点

  • Deployment:

    • nodeSelector: disktype: ssd(假设你在 142 上打了这个标签)。

    • command: 如果是 Redis,可能需要设置密码或持久化路径。

存放路径/root/k8s/redis

1. Service (redis-svc.yaml)

复制代码

yaml

yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: redis
spec:
  clusterIP: None  # Headless Service
  selector:
    app: redis
  ports:
  - port: 6379

2. StatefulSet (redis-sts.yaml)

极简版,无 ConfigMap,避免挂载错误

复制代码

yaml

yaml

复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      # 强制调度到 Node1 (142)
      nodeSelector:
        kubernetes.io/hostname: "k8s-node1" # 或者 node-142
      securityContext:
        runAsUser: 0
        fsGroup: 0
      containers:
      - name: redis
        image: redis:6.2
        imagePullPolicy: IfNotPresent
        command:
        - redis-server
        - "--appendonly yes"
        ports:
        - containerPort: 6379
        volumeMounts:
        - name: redis-data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: nfs-storage
      resources:
        requests:
          storage: 2Gi

🧠 核心知识点

  • Service 的 ClusterIP :MySQL 和 Redis 的 Service 名字,可以在同一个 Namespace 下互相解析(比如 Redis 连 mysql.default.svc.cluster.local)。

🔥 排错核心

现象 原因 排查命令
Redis 连接不上 MySQL 服务名写错,或者 Service 没有 Endpoints kubectl get endpoints看后端有没有 Pod
Redis 数据重启后丢失 没有配置 PVC 或 RDB/AOF 持久化 检查 Deployment 是否挂载了 Volume

总结:排错思维导图

复制代码

text

text

复制代码
1. 先看 Pod 状态 (kubectl get pod -o wide)
   ├── Pending?  --> 检查节点标签 (kubectl get node) 或 资源不够
   ├── ImagePullBackOff? --> 检查 Docker 镜像是否 load 进去了
   └── CrashLoopBackOff? --> 看日志 (kubectl logs)

2. 日志报 Permission Denied?
   ├── 查 NFS Server (143) 的 /opt/k8s 权限 (chmod 777)
   └── 查 NFS 导出配置 (exports) 是否有 no_root_squash

3. PVC 一直 Pending?
   ├── 查 StorageClass 是否存在 (kubectl get sc)
   └── 查 Provisioner Pod 是否活着

4. 服务连不上?
   ├── 查 Service 是否有 Endpoint (kubectl get endpoints)
   └── 查 Pod 是否 Ready (kubectl get pod)
相关推荐
Plastic garden1 小时前
k8s(11) Pod 控制器,服务发现与存储管理
kubernetes
与海boy2 小时前
docker compose minio
docker·容器·eureka
JimCarter2 小时前
使用Azure Devops Pipeline将Docker应用部署到你的Raspberry Pi上
docker·azure·树莓派·devops·orangepi·香橙派·raspberrypi
武子康2 小时前
调查研究-167 Docker Compose 详解:从单容器到多服务编排的工程化入口
运维·docker·云原生·容器·kubernetes·k8s·docker-compose
旅僧3 小时前
Ubantu docker环境配置(前置)
运维·docker·容器
“码”力全开4 小时前
解耦异构算力:基于 Docker 与边缘计算的 AI 视频管理平台,实现 GB28181/RTSP 统一接入与源码交付深度解析
人工智能·docker·边缘计算
正经教主5 小时前
【docker基础】第六课:Web应用与数据库容器部署
网络·docker·容器
Shacoray5 小时前
K8s 中 Ingress 的 HTTPS 证书 如何生成?
容器·https·kubernetes