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/k8sMaster 工作目录:
/root/k8s/nfs-provisioner
第一步:搭建 NFS 底层存储服务 (在 Node2 上)
📍 主机/IP:192.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 服务未启动或防火墙拦截 |
|
Pod 报错 | 目录权限不足或未加 | 检查 Node2 上的 |
第二步:部署 NFS Provisioner (在 Master 上)
📍 主机/IP:192.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.io2. 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: Immediatebash
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/k8svolumes: 这里挂载的其实是用来放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 |
|
PVC 一直 Pending | 检查 StorageClass 名字是否写错,或者 Provisioner 容器没跑起来 |
|
第三步:部署 MySQL (在 Master 上操作,调度到 Node2)
📍 主机/IP:192.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.yaml2. 关键 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: 33062. 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 目录权限不匹配 |
|
Pod 无法调度 (Pending) | 找不到带有 |
|
第四步:部署 Redis (在 Master 上操作,调度到 Node1)
📍 主机/IP:192.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.yaml2. 关键 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: 63792. 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 |
|
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)