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 服务未启动或防火墙拦截 | systemctl status nfs, showmount -e 192.168.222.143 |
Pod 报错 Permission Denied |
目录权限不足或未加 no_root_squash |
检查 Node2 上的 /opt/k8s权限是否为 777 |
第二步:部署 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.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)
📍 主机/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.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,数据就丢了。数据必须跟着存储走。
- 因为 MySQL 数据文件存在
-
持久化声明 (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)
📍 主机/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.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)