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)
相关推荐
lichenyang4532 天前
Docker 学习笔记(五):Docker Compose,用一个 YAML 启动前端、后端和 MongoDB
docker
lichenyang4532 天前
Docker 学习笔记(四):Dockerfile,把项目打成自己的镜像
docker·容器
lichenyang4532 天前
Docker 学习笔记(三):Docker 网络、bridge、子网和容器互通
docker·容器
lichenyang4532 天前
Docker 学习笔记(二):docker run 的参数到底在控制什么?
docker·容器
运维开发故事5 天前
基于 Arthas 的多集群在线诊断系统设计与实现
kubernetes
Patrick_Wilson6 天前
从「改个端口」到 502:Next.js on k8s 的容器端口、Service 映射与 env 覆盖
docker·kubernetes·next.js
探索云原生7 天前
K8s 1.36 这个 GA 特性,把 initContainer 拉模型的 hack 干掉了
ai·云原生·kubernetes
Suroy7 天前
DockerView-Go:用 Go 写一个终端 Docker 监控工具,顺便做了个 Web 仪表盘
docker
云恒要逆袭7 天前
运行你的第一个Docker容器
后端·docker·容器