K8s 集群部署排错
本文基于一次真实的 KubeKey 部署 KubeSphere 环境排错过程整理,涵盖 NFS 服务启动失败、NFS Provisioner 调度失败、Worker 节点反复 NotReady、noexec 挂载导致脚本无法执行、sudo 权限配置缺失、节点标签缺失等 6 类问题的完整排查路径与解决方案。
问题一:nfs-server 服务启动失败(unit is masked)
现象
执行启动 NFS 服务时报错:
failed to restart nfs-server.service
unit nfs-server.service is masked原因
masked 是比 disable 更彻底的状态,表示该服务被系统管理员主动屏蔽,目的是阻止它被任何方式启动。屏蔽通常是通过创建指向 /dev/null 的符号链接实现的。
解决方案
# 1. 解除屏蔽
sudo systemctl unmask nfs-server.service
# 2. 启动服务
sudo systemctl restart nfs-server.service
# 3. 设置开机自启(可选)
sudo systemctl enable nfs-server.service补充排查
如果解除屏蔽后仍然启动失败,检查是否有其他依赖服务也被屏蔽:
# 查看是否有指向 /dev/null 的符号链接
ls -l /etc/systemd/system/ | grep nfs
# 检查相关依赖
systemctl status nfs-mountd rpc-statdNFS 服务依赖于 nfs-mountd、rpc-statd 等组件,如果有类似的 masked 状态,同样用 unmask 解除。
问题二:NFS Provisioner 调度失败
现象
kubectl describe pod -n kube-system nfs-subdir-external-provisioner-xxx输出显示:
Conditions:
PodScheduled False
Events:
Warning FailedScheduling
0/2 nodes are available:
1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: },
1 node(s) had untolerated taint {node.kubernetes.io/unreachable: }
preemption: 0/2 nodes are available
Node-Selectors: rise.io/manager=true原因
有两个独立原因叠加:
- master 节点有
control-plane污点,Pod 没有对应容忍,无法调度。 - worker 节点有
unreachable污点(因节点 NotReady 产生),Pod 同样无法调度。 - Pod 的 Node-Selector 要求
rise.io/manager=true标签,但节点上缺少该标签。
解决方案
第一步:修复 worker 节点 NotReady(见问题三)
节点恢复 Ready 后,unreachable 污点会自动消失。
第二步:给节点打标签
# 在 master 上执行
kubectl label node 110236p.corp.scaniagroup.cn rise.io/manager=true
# 验证标签
kubectl get node 110236p.corp.scaniagroup.cn --show-labels
# 检查 Pod 是否正常调度
kubectl get pod -n kube-system | grep nfs补充说明
rise.io/manager=true 是 KubeKey 或存储组件用来指定管理节点的标签,让 NFS Provisioner 固定跑在特定节点上。
问题三:Worker 节点因 8081 端口占用导致反复 NotReady
现象
Worker 节点 110236p.corp.scaniagroup.cn 状态在 Ready 与 NotReady 之间反复横跳。
kubectl describe node 显示:
Conditions:
Type Status Reason
Ready Unknown NodeStatusUnknown
MemoryPressure Unknown Kubelet stopped posting node status
DiskPressure Unknown Kubelet stopped posting node status
PIDPressure Unknown Kubelet stopped posting node status
Taints:
node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unreachable:NoSchedule
Lease:
Failed to get lease: leases.coordination.k8s.io "..." not foundkubelet 日志关键报错:
E0914 ... kubelet_node_status.go] "Unable to register node with API server"
err="Post \"https://127.0.0.1:6443/api/v1/nodes\": dial tcp 127.0.0.1:6443: connect: connection refused"
E0914 ... controller.go:145] "Failed to ensure lease exists, will retry"
err="Get \"https://127.0.0.1:6443/apis/coordination.k8s.io/v1/...\":
dial tcp 127.0.0.1:6443: connect: connection refused"根因分析
故障链如下:
McAfee 端点安全软件占用 8081 端口
↓
HAProxy 静态 Pod 无法绑定 8081,启动即崩
↓
本地 127.0.0.1:6443 代理不可用
↓
kubelet 无法连接 API Server → 节点 NotReady
↓
节点被打上 unreachable 污点 → 其他 Pod 无法调度此外还发现一个附带问题:
- HAProxy 配置文件权限问题:宿主机配置文件属主为 LDAP 用户
gqu1vx,权限 640,容器内 HAProxy 进程以非 root 运行,读取报Permission denied。
排查步骤
步骤 1:从集群侧观察
kubectl get nodes
kubectl describe node 110236p.corp.scaniagroup.cn步骤 2:登录节点检查 kubelet
systemctl status kubelet
journalctl -u kubelet --no-pager -n 100步骤 3:定位 API Server 连接问题
grep server /etc/kubernetes/kubelet.conf预期结果:KubeKey 高可用部署中应为 https://127.0.0.1:6443。
步骤 4:检查本地 HAProxy 状态
crictl ps -a | grep haproxy
crictl logs <haproxy-container-id>发现 1:配置文件权限问题
[ALERT] (1) : config : Could not open configuration file
/usr/local/etc/haproxy/haproxy.cfg : Permission deniedls -l /etc/haproxy/haproxy.cfg
# -rwxr-xr-x. 1 gqulvx ot010005-scan iausers 1096 Sep 14 15:06 haproxy.cfg发现 2:端口冲突
[ALERT] (1) : Binding [/usr/local/etc/haproxy/haproxy.cfg:24]
for frontend healthz: cannot bind socket (Address in use) for [0.0.0.0:8081]
[ALERT] (1) : [haproxy.main()] Some protocols failed to start their listeners! Exiting.netstat -anput | grep 8081
# tcp6 0 0 :::8081 :::* LISTEN 3384/macmnsvc解决方案
修复 1:配置文件权限
chown root:root /etc/haproxy/haproxy.cfg
chmod 644 /etc/haproxy/haproxy.cfg注意:不要修改
/etc/kubernetes/manifests/haproxy.yaml的权限,那是 kubelet 读取的静态 Pod 清单,与容器内进程无关。
修复 2:解决端口冲突
修改宿主机配置文件中的健康检查端口:
sed -i 's/bind :8081/bind :8082/' /etc/haproxy/haproxy.cfg同时修改 /etc/kubernetes/manifests/haproxy.yaml 中的探针端口:
containers:
- name: haproxy
image: ...
livenessProbe:
httpGet:
path: /healthz
port: 8082 # 与 haproxy.cfg 中保持一致
readinessProbe:
httpGet:
path: /healthz
port: 8082 # 与 haproxy.cfg 中保持一致关键原则:HAProxy 的 healthz 绑定端口必须与 kubelet 探针的目标端口一致,改一处必须同步改另一处。
验证修复效果
# 1. 检查端口监听
ss -tlnp | grep -E '8082|6443'
# 2. 模拟健康检查
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8082/healthz
# 预期输出:200
# 3. 验证 6443 代理
curl -k https://127.0.0.1:6443/healthz
# 预期输出:ok
# 4. 检查容器状态
crictl ps | grep haproxy
# 预期:Running
# 5. 在 master 上观察节点状态
kubectl get nodes -wKubeKey 高可用架构说明
KubeKey 部署高可用集群时,每个节点都会运行一个本地 HAProxy 静态 Pod:
kubelet → 127.0.0.1:6443 (本地 HAProxy) → master:6443 (真实 API Server)- 设计意图:kubelet 不需要知道 master 的真实 IP,统一走本地回环地址,master 变更时只需改 HAProxy 配置。
- 副作用:本地 HAProxy 一旦故障,该节点的 kubelet 立即失联,节点变为
NotReady。
问题四:/tmp 分区 noexec 导致脚本无法执行
现象
以 root 权限执行 ./run.sh 提示:
bash: ./run.sh: Permission denied但用 bash ./run.sh 就没问题。
原因
./run.sh 与 bash ./run.sh 的本质区别:
| 方式 | 需要 x 权限 | 需要 r 权限 | 是否用 shebang |
|---|---|---|---|
./run.sh | ✅ 必须 | 视情况 | ✅ 需要 |
bash ./run.sh | ❌ 不需要 | ✅ 必须 | ❌ 不需要 |
即使文件有执行权限、shebang 也正确(#!/usr/bin/env bash),如果文件位于 noexec 挂载的分区(如 /tmp),内核仍然拒绝执行。
排查步骤
# 1. 确认文件权限
ls -l ./run.sh
# 2. 确认文件所在分区的挂载选项
findmnt -T ./run.sh -o TARGET,SOURCE,FSTYPE,OPTIONS
# 3. 检查是否包含 noexec
mount | grep -E "$(df --output=target run.sh | tail -1)"如果 OPTIONS 中出现 noexec,即为此原因。
解决方案
方案一:临时重新挂载(立即生效,重启失效)
sudo mount -o remount,exec /tmp方案二:永久修改(编辑 /etc/fstab)
# 先备份
sudo cp /etc/fstab /etc/fstab.bak
# 编辑 fstab,将 /tmp 行中的 noexec 删除或替换为 exec
sudo vi /etc/fstab将:
/dev/vdb1 /tmp ext4 rw,noexec,relatime 0 2改为:
/dev/vdb1 /tmp ext4 rw,exec,relatime 0 2方案三:把脚本移出 /tmp(最安全)
cp /tmp/run.sh /root/run.sh
cd /root && ./run.sh如果安全合规要求 /tmp 必须保持 noexec,推荐使用此方案。
补充说明
即使有 x 权限,如果脚本第一行缺少 shebang,./run.sh 会默认用 /bin/sh 执行,可能导致语法不兼容。
其他可能触发 Permission denied 的原因还包括:文件系统不支持执行权限(vfat/ntfs/exfat)、ACL 限制、SELinux/AppArmor 拦截、容器内 noexec 挂载等。
问题五:sudo 用户缺少 sudoers 配置导致执行报错
现象
用 KubeKey 部署时提示:
Sorry, user gqu1vx is not allowed to execute '/bin/bash -c echo greetings,kubekey' as root on master, process exited with status 1原因
该报错说明 sudo 本身能正常运行,但用户没有匹配到任何免密 sudo 规则。
对于 LDAP/AD 用户,sudoers 文件里的用户名必须与 id 或 getent passwd 返回的登录名完全一致。
如果 SSSD 配置了 use_fully_qualified_names = True,系统识别的用户名可能是 gqu1vx@your.domain.com 格式,而不是短名 gqu1vx。
排查步骤
# 1. 确认 sudo 实际识别的用户名
id
whoami
# 2. 确认 sudoers 规则是否匹配
sudo -l -U gqu1vx
# 3. 检查 sudoers 文件语法
sudo visudo -c -f /etc/sudoers.d/kubekey解决方案
方案一:按短名配置
echo "gqu1vx ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/kubekey
sudo chmod 440 /etc/sudoers.d/kubekey方案二:按全限定名配置(如果系统用 FQDN 识别用户)
echo "gqu1vx@your.domain.com ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/kubekey方案三:通过组授权(更稳妥)
# 先确认用户所属组
id gqu1vx
# 假设属于 wheel 或 sudo 组
echo "%wheel ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/kubekey注意:组名前必须加
%。
验证
# 非交互模式测试,不需要密码即返回成功
sudo -n true && echo "免密 sudo 配置成功" || echo "配置未生效"
# 校验 sudoers 语法
sudo visudo -c -f /etc/sudoers.d/kubekey
# 应输出 parsed OK补充排查
检查 /etc/sudoers 是否包含了 /etc/sudoers.d/:
grep includedir /etc/sudoers应输出类似 #includedir /etc/sudoers.d。如果没有,需要改用 visudo 直接在主文件末尾追加规则。
检查 LDAP 用户解析:
id gqu1vx
getent passwd gqu1vx如果这两条命令返回空,说明问题在 LDAP 客户端配置(/etc/nsswitch.conf、/etc/sssd/sssd.conf 等),而不是 sudoers 本身。
为什么 KubeKey 要求免密 sudo
KubeKey 在执行远程命令时使用 sudo -E 方式,且通过 SSH 非交互式通道运行。当 sudo 需要密码时,密码提示无法正常传递,会导致 sudo: 3 incorrect password attempts 错误,即使密码完全正确。因此官方和社区的一致做法是:在目标节点的 /etc/sudoers 中为部署用户配置免密 sudo。
问题六:节点缺少 rise.io/manager=true 标签导致组件调度失败
现象
部分组件(如 NFS Provisioner)Pod 一直处于 Pending 状态,kubectl describe pod 显示:
Node-Selectors: rise.io/manager=true
Events:
Warning FailedScheduling
0/2 nodes are available: 1 node(s) didn't match Pod's node affinity/selector原因
该 Pod 的 Node-Selector 要求节点必须带有 rise.io/manager=true 标签,但当前集群中没有节点满足此条件。
这个标签通常由 KubeKey 或存储组件用来指定管理节点,让特定组件固定跑在某一台节点上。
解决方案
在 master 节点上执行:
# 给目标节点打标签
kubectl label node 110236p.corp.scaniagroup.cn rise.io/manager=true
# 如果标签已存在但值不同,加 --overwrite 覆盖
kubectl label node 110236p.corp.scaniagroup.cn rise.io/manager=true --overwrite验证
# 查看节点全部标签
kubectl get node 110236p.corp.scaniagroup.cn --show-labels
# 只查看该标签的值
kubectl get node 110236p.corp.scaniagroup.cn -o jsonpath='{.metadata.labels.rise\.io/manager}'
# 预期输出:true
# 检查 Pod 是否正常调度
kubectl get pod -n kube-system | grep nfs
# 预期:从 Pending 变为 Running补充说明
如果后续还有其他 Pod 也要求这个标签,打一次就够了,所有需要它的 Pod 都能匹配上。如果该标签是由 KubeKey 在特定阶段自动添加的,也可以考虑排查为何没有自动添加(比如部署流程中断、某个 addon 未执行)。
附录 A:常见陷阱速查表
| 陷阱 | 说明 |
|---|---|
| 改错文件 | haproxy.yaml(静态 Pod 清单)和 haproxy.cfg(HAProxy 配置文件)是两个不同文件 |
| 端口改一处 | 改 HAProxy 的 bind 端口后,必须同步改 YAML 探针端口 |
| LDAP 用户权限 | LDAP 用户的 UID/GID 在容器内可能对不上,导致文件读取失败 |
| 安全软件干扰 | McAfee/Trellix 等安全软件常与 K8s 组件抢占端口、hook 系统调用 |
| 污点残留 | 节点恢复 Ready 后,unreachable 污点不会立即消失,需等待控制平面处理 |
| 脚本执行权限 | ./run.sh 需要 x 权限,bash ./run.sh 只需要 r 权限,noexec 挂载会拒绝执行 |
| sudo 用户名匹配 | LDAP 用户的短名/全限定名必须与系统识别的一致 |
| 标签缺失 | Node-Selector 依赖的标签如果缺失,Pod 会一直 Pending |
附录 B:Worker 节点 NotReady 快速检查清单
当 Worker 节点出现 NotReady 时,按以下顺序快速排查:
- [ ]
kubectl describe node <node>— 查看 Conditions 和 Taints - [ ]
systemctl status kubelet— 确认 kubelet 进程状态 - [ ]
journalctl -u kubelet --no-pager | grep -i error— 查看关键错误 - [ ]
grep server /etc/kubernetes/kubelet.conf— 确认 API Server 地址 - [ ]
crictl ps -a | grep haproxy— 检查本地 HAProxy 状态 - [ ]
crictl logs <haproxy-id>— 查看 HAProxy 日志 - [ ]
ls -l /etc/haproxy/haproxy.cfg— 检查配置文件权限 - [ ]
netstat -anput | grep <port>— 检查端口占用 - [ ]
ss -tlnp | grep 6443— 确认本地代理监听 - [ ]
curl -k https://127.0.0.1:6443/healthz— 验证代理连通性
附录 C:完整故障链示意
McAfee 占用 8081 端口
↓
HAProxy 无法绑定端口 → 启动失败
↓
本地 127.0.0.1:6443 代理不可用
↓
kubelet 连不上 API Server → 节点 NotReady
↓
节点被打上 unreachable 污点
↓
NFS Provisioner 等 Pod 无法调度
↓
同时:节点缺少 rise.io/manager=true 标签 → 调度进一步失败文档信息
| 项目 | 内容 |
|---|---|
| 文档版本 | v2.0 |
| 适用环境 | KubeKey 部署的 KubeSphere / Kubernetes 集群 |
| 涵盖问题 | 6 类 |
| 最后更新 | 2026-09-15 |