← 返回首页 K8S Web / 文档 / K8S 使用问题总汇

🧯 K8S 使用问题总汇 安装失败 / 磁盘 / 挂载 / 权限 / 拉不下镜像 / 线上数据库磁盘 —— 现象 → 原因 → 解决方案,按报错关键词对号入座

01集群安装失败 —— kubeadm 高频报错

90% 的安装失败都卡在 kubeadm init 的预检(preflight)阶段,报错会直接告诉你原因,逐条修复即可:

报错关键词根因解决方案
[ERROR Swap]: running with swap on没关 swap,kubelet 起不来swapoff -a + 注释 /etc/fstab swap 行
[ERROR DirAvailable-/var/lib/etcd]: is not empty上次 init 失败的数据残留kubeadm reset -f 后清空 /var/lib/etcd/*
[ERROR Port-10250]: already in use端口被老 kubelet/别的进程占用ss -lntp | grep 10250 找到并停掉
failed to pull image ... no space left on device磁盘满,组件镜像拉不下来按第 02 节清磁盘,再重跑 init
container runtime is not runningcontainerd 没起 / sock 路径配错systemctl enable --now containerd,用 crictl ps 验证
connect: connection refused (6443)apiserver 没起来journalctl -u kubelet -n 100 看真实原因

标准修复流程(命令可直接复制)

# 1. 关闭 swap(新手第一大坑,处理完重启也不会复活)
swapoff -a
sed -ri 's@^(/.*\sswap\s.*)@#\1@' /etc/fstab
free -h                        # Swap 一行必须全是 0

# 2. 容器运行时自检
systemctl enable --now containerd
crictl ps                      # 能正常执行(哪怕空列表)说明 runtime OK

# 3. 上次失败后的"彻底重来"(试错阶段最常用)
kubeadm reset -f
rm -rf /var/lib/etcd/* /etc/cni/net.d/* $HOME/.kube/config
iptables -F && ipvsadm -C 2>/dev/null

# 4. 重新 init(image-repository 建议直接给国内源,避免第 05 节的坑)
kubeadm init \
  --image-repository=registry.aliyuncs.com/google_containers \
  --pod-network-cidr=10.244.0.0/16 \
  --service-cidr=10.96.0.0/12

# 5. init 成功后按提示三连(否则 kubectl 连不上)
mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
kubectl get nodes              # 能打印节点表即成功
⚠️ 注意:kubeadm reset 只回滚集群组件,不会删除 PVC 数据、NFS 目录和手动装的 CNI 二进制;重装前请单独确认。

02磁盘问题 —— 磁盘满 / DiskPressure 驱逐

节点磁盘可用率低于阈值(默认 nodefs.available<10%、imagefs.available<15%)时,kubelet 会把节点标记为 DiskPressure 并驱逐 Pod。

典型现象

# 节点被打了磁盘压力标签
kubectl describe node qingfeng | grep -A2 DiskPressure
  DiskPressure   True   KubeletHasDiskPressure   kubelet has disk pressure

# 大批 Pod 被驱逐(Evicted)
kubectl get pods -A | grep -i evicted
harbor   harbor-core-5d7c9f8b-x2k9p   Evicted   0   23h

# 应用写文件直接报错(数据库最常见,见第 06 节)
IOError: [Errno 28] No space left on device

排查:先找到磁盘被谁吃掉

# 1. 总览:运行时与 kubelet 所在分区
df -h /var/lib/containerd /var/lib/kubelet

# 2. 三大户逐一定位(镜像层 / 容器日志 / journald)
du -h --max-depth=1 /var/lib/containerd | sort -rh | head
du -h --max-depth=1 /var/log/pods     | sort -rh | head   # 容器 stdout 日志
journalctl --disk-usage

清理(按安全顺序执行)

# 1. 删"没有任何 Pod 在用"的镜像(最安全、见效最快)
crictl rmi --prune

# 2. 收缩 journald 系统日志,只保留 3 天
journalctl --vacuum-time=3d

# 3. 清理已被驱逐/失败的 Pod 残留
kubectl delete pods -A --field-selector=status.phase=Failed

# 4. 长效:限制单容器日志体积(/var/lib/kubelet/config.yaml)
containerLogMaxSize: 50Mi
containerLogMaxFiles: 3
# 改完 systemctl restart kubelet

兜底:临时下调驱逐阈值(只救急,清完就改回)

# /var/lib/kubelet/config.yaml
evictionHard:
  nodefs.available: "5%"
  imagefs.available: "5%"
# systemctl restart kubelet 后 DiskPressure 会自动恢复为 False
⚠️ 千万别:直接 rm -rf /var/lib/containerd/io.containerd.* 里的目录 —— 会把正在用的镜像/快照删坏;删镜像必须走 crictl rmi。

03挂载问题 —— Volume / PVC 挂载失败

Pod 卡在 ContainerCreating 十有八九是挂载问题,Events 里必有答案:

第一步:看 Events 定位

kubectl describe pod mysql-0 | sed -n '/Events:/,$p'
kubectl get events -A --sort-by=.lastTimestamp | grep -iE 'volume|mount|attach'

# PVC 是否 Bound?Pending 多半是 PV/StorageClass 没配好
kubectl get pvc -A

高频报错对照表

Events 报错根因解决方案
mount.nfs: access denied by serverNFS exports 白名单没放行 / root_squash编辑 /etc/exports 加节点网段 + no_root_squash,exportfs -rv
mount: wrong fs type, bad option节点没装 NFS 客户端yum install -y nfs-utils(Debian 系:nfs-common)
hostPath path check failed ... not exist宿主机目录不存在提前 mkdir -p;或 hostPath type 用 DirectoryOrCreate
Multi-Attach error for volume ... exclusively attachedRWO 卷还被旧 Pod(或残留 attachment)占着删旧 Pod;kubectl get volumeattachments 查残留并删
subPath ... is not foundsubPath 指向的文件/目录不存在initContainer 先创建;kubectl explain pod.spec.containers.volumeMounts.subPath
ConfigMap 改了 Pod 里不生效subPath 挂载不会热更新(设计如此)重启 Pod;或改用目录挂载(自动更新,延迟 ~1 分钟)
# NFS 服务端标准配置(/etc/exports)示例
/data/nfs/mysql  *(rw,sync,no_subtree_check,no_root_squash)

# 服务端生效 + 客户端验证(节点上先手工 mount 一次,能通 K8S 里才能通)
exportfs -rv
mount -t nfs 192.168.10.10:/data/nfs/mysql /mnt && ls /mnt && umount /mnt
💡 经验:凡是挂载报错,先在Pod 所在节点上用同样的参数手工 mount 一次 —— 手工能挂上说明是 K8S 配置问题;手工也挂不上就是 NFS/存储端问题,思路立刻清晰。

04权限问题 —— Permission denied / RBAC

① 挂载目录没写权限(数据库类应用第一名坑)

# 现象:Pod 反复 CrashLoopBackOff,logs 里是经典的 permission denied
kubectl logs mysql-0 -n prod
mkdir: cannot create directory '/var/lib/mysql': Permission denied

# 解决:Pod securityContext 加 fsGroup(让 kubelet 把卷 chown 给该组)
#      fsGroup 的值 = 镜像内运行业务的系统用户组 id(mysql 官方镜像为 999)
spec:
  securityContext:
    fsGroup: 999
    runAsUser: 999
    runAsGroup: 999

② SELinux 拦截(CentOS / RHEL 常见)

# 快速判断:临时关闭后能写了,就是 SELinux 的锅
setenforce 0          # 临时 Permissive(重启失效)

# 正确做法(别长期关闭):给目录打容器标签
chcon -Rt svirt_sandbox_file_t /data/nfs/mysql
# 永久生效用 semanage fcontext -a -t svirt_sandbox_file_t "/data/nfs/mysql(/.*)?" && restorecon -Rv /data/nfs/mysql

③ RBAC / 认证类报错

报错根因解决方案
Forbidden: User "system:anonymous" cannot get ...kubeconfig 没带凭证 / 权限未绑定检查 KUBECONFIG;给 SA 绑 Role/ClusterRole
x509: certificate has expired集群证书到期(常伴随节点时间不准)ntpdate 校时;kubeadm certs renew all + 重启控制面
You must be logged in to the server (Unauthorized)client 证书/ token 失效重新分发 admin.conf 或重新签发用户证书
# 权限自检两连:我是谁 / 我能不能
kubectl config current-context
kubectl auth can-i get pods -n prod          # 回答 yes/no,一目了然
kubectl auth can-i '*' '*' --as=system:serviceaccount:prod:deploy-sa

# 看证书还剩多久
kubeadm certs check-expiration
💡 定位口诀:容器内报 Permission denied → 先 fsGroup;节点上手工写也失败 → 查 SELinux/目录属主;kubectl 报 Forbidden/Unauthorized → 查 RBAC/证书。三类权限别混。

05拉不下镜像 —— ImagePullBackOff

典型现象与四类根因

kubectl get pods -n web
nginx-6b7f5d8c-x9k2p   0/1   ImagePullBackOff   0   3m

kubectl describe pod nginx-6b7f5d8c-x9k2p -n web | grep -A3 "Failed to pull"
  Failed to pull image "nginx:1.27": failed to pull and unpack ... i/o timeout
Events 里的报错根因跳转方案
manifest unknown / not found镜像名或 Tag 写错方案 A
i/o timeout / no route to host国内网络拉不到 Docker Hub方案 B(加速器)
toomanyrequests / rate limitedDocker Hub 限流方案 B / C
unauthorized: authentication required私有仓库没带凭证方案 D(imagePullSecrets)
exec format error架构不匹配(x86 机器拉了 arm64 镜像)换 nginx:1.27-amd64 类带架构 Tag

方案 A:先确认镜像真的存在

# 在任意节点上手工拉一次,报 not found 就是名字/Tag 写错
crictl pull nginx:1.27

方案 B:containerd 配置国内加速(一劳永逸)

# 1. /etc/containerd/config.toml 启用 certs.d 目录(v2 推荐方式)
[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = "/etc/containerd/certs.d"

# 2. 给 docker.io 配加速源:/etc/containerd/certs.d/docker.io/hosts.toml
server = "https://docker.io"
[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
#   ★ 加速器对某些仓库(如 bitnamilegacy)可能挂起/不回源 → pull 卡住不动:
#     先 rm hosts.toml 恢复直连(机器 curl 通 docker.io 就直连可拉);
#     直连也不行再换源:docker.1ms.run / dockerproxy.net 等,改 [host."..."] 后重启 containerd

# 3. 重启生效(改了配置不重启 = 白改)
systemctl restart containerd
crictl pull nginx:1.27        # 现在应该秒拉

# 4. sandbox/pause 镜像也超时的话,init 第一步就卡死 —— 顺手改同文件里
#    sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.10"

方案 C:离线导入(内网/保密环境终极方案)

# 有网机器上导出,拷进内网节点导入(注意必须是 k8s.io 命名空间)
ctr -n k8s.io images import nginx-1.27.tar
crictl images | grep nginx   # 能看到即成功,Pod 会自动重试拉起

方案 D:私有仓库认证(Harbor 等)

# 1. 建拉取凭证(用户名里 $ 记得转义,Robot 账号常见)
kubectl create secret docker-registry regcred -n web \
  --docker-server=harbor.qingfeng.local \
  --docker-username='robot$ci' \
  --docker-password='Harbor12345'

# 2. Pod/Deployment spec 里引用
spec:
  imagePullSecrets:
  - name: regcred
💡 私仓完整实践(Robot 账号、CI 推送、admission webhook 排障)见 Harbor 安装与使用;修改镜像后不想等退避重试,kubectl delete pod xxx 重建最快。

06线上数据库磁盘满 —— 止血 → 扩容 → 防复发

数据库是最怕磁盘满的工作负载:一旦写不进 binlog/数据文件,MySQL 会把自己挂起来,业务直接中断。处理顺序永远是先止血保服务,再扩容,最后防复发。

现象确认(告警 90%+ 或已写入失败)

# 1. PVC 使用率告警
kubectl get pvc data-mysql -n prod
NAME        STATUS   VOLUME     CAPACITY   ...
data-mysql  Bound    pvc-8f3..  20Gi

# 2. 进容器看实际占用(看挂载点,不是根分区)
kubectl exec -it mysql-0 -n prod -- df -h /var/lib/mysql
/dev/sdb   20G   18.5G   1.5G  93% /var/lib/mysql

# 3. 是数据涨了还是日志涨了?
kubectl exec -it mysql-0 -n prod -- sh -c 'du -sh /var/lib/mysql/* | sort -rh | head'
# binlog.0000xx  动辄几十 G —— 最常见的元凶
# ibdata1 / ib_logfile0 —— 真实数据/redo,不能删

第一步:止血(只清日志,绝不动数据文件)

# 1. binlog 立刻瘦身:只保留最近 2 天
kubectl exec -it mysql-0 -n prod -- \
  mysql -uroot -p"$PW" -e "PURGE BINARY LOGS BEFORE NOW() - INTERVAL 2 DAY;"

# 2. 慢日志/错误日志轮转(>> 清空不换句柄,不断连接)
kubectl exec -it mysql-0 -n prod -- sh -c '> /var/log/mysql/slow.log'

# 3. 临时表/备份残留确认后再删(先看时间戳,别一把梭)

第二步:PVC 在线扩容(业务不中断)

# 前提 1:StorageClass 必须允许扩容
kubectl get sc
NAME       PROVISIONER      ALLOWVOLUMEEXPANSION
nfs-sc     nfs-provisioner  true        ← 允许,继续
local-sc   kubernetes.io/no-provisioner  false   ← 本地盘不支持在线扩,只能迁移

# 直接 patch 目标容量(只能扩大,永远不能缩小!)
kubectl patch pvc data-mysql -n prod \
  -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

# 盯进度:先等 controller 扩 PV,再等文件系统在线 expand(xfs/ext4 均支持)
kubectl get pvc data-mysql -n prod -w

# 容器内验证(无需重启 Pod)
kubectl exec -it mysql-0 -n prod -- df -h /var/lib/mysql
/dev/sdb   50G   18.5G   32G  37% /var/lib/mysql

第三步:防复发

# 1. binlog 自动过期(my.cnf [mysqld],3 天)
binlog_expire_logs_seconds = 259200
max_binlog_size            = 512M

# 2. 容器日志限制(见第 02 节 containerLogMaxSize)
# 3. 监控告警阈值提前到 75%,别等 90% 才动手
⚠️ 两条红线:① 永远不要手工删除 /var/lib/mysql 下的 ibdata1、binlog.index 等文件(想清 binlog 用 SQL PURGE);② PVC 扩容只能扩大,缩容不支持;local PV / hostPath 无法在线扩,唯一路径是"新 PVC + 逻辑备份迁移 + 切换"。

07通用排障三板斧 + 状态速查表

不管什么问题,先跑这三条,80% 的答案就在输出里:

kubectl describe pod <pod> -n <ns>                    # Events 区 = 病历本
kubectl logs <pod> -n <ns> --tail=100 --previous   # 应用自己的报错(--previous 看上一次崩溃)
kubectl get events -A --sort-by=.lastTimestamp | tail -20   # 全集群最近发生了什么
Pod 状态大概率方向跳转
ImagePullBackOff / ErrImagePull镜像拉取失败第 05 节
ContainerCreating 卡住挂载失败 / 拉镜像慢第 03 节 / 05
CrashLoopBackOff权限 / 配置 / 应用启动报错第 04 节 + logs
Evicted磁盘 / 内存压力驱逐第 02 节
Pending资源不足 / PVC 没绑定 / 节点选择器不匹配第 03 节
CreateContainerConfigErrorsecret/configmap/subPath 不存在第 03 节
💡 更多命令细节(查看全家桶、exec、rollout、jsonpath)见 kubectl 常用命令速查;Harbor Pending 23h / webhook 断链两大实录见 Harbor 安装与使用。

08节点能跑多少 Pod —— maxPods 查询与调整

查询命令(两条就够)

# 1. 最多能跑几个:读节点 status.allocatable.pods(kubelet 上报的可分配上限)
kubectl get node qingfeng -o jsonpath='{.status.allocatable.pods}'; echo
110

# 2. 已经用了多少:describe 的 Allocated resources 段,盯 pods 那一行
kubectl describe node qingfeng | grep -A8 'Allocated resources'
  Resource           Requests      Limits
  cpu                850m (21%)    300m (7%)
  memory             690Mi (9%)    512Mi (7%)
  ephemeral-storage  0 (0%)        0 (0%)
  pods               7 (6%)        0 (0%)      ← 已分配 7 个 / 上限 110

# 附加:所有节点容量一览表
kubectl get nodes -o custom-columns='NODE:.metadata.name,MAX-PODS:.status.allocatable.pods'
NODE       MAX-PODS
qingfeng   110

# 多节点集群:全部节点的 Pod 容量"总和"(jsonpath 逐行取出再 awk 累加)
kubectl get nodes -o jsonpath='{range .items[*]}{.status.allocatable.pods}{"\n"}{end}' \
  | awk '{s+=$1} END{print "集群 Pod 总容量:", s}'

# 明细 + 合计一起看(表格末尾追加 TOTAL 行)
kubectl get nodes -o custom-columns='NODE:.metadata.name,MAX-PODS:.status.allocatable.pods' \
  | awk 'NR>1{print; s+=$2} END{print "--------"; printf "TOTAL\t%d\n", s}'

# 对比"已经用了多少":集群当前 Pod 总数 / 只数 Running
kubectl get pods -A --no-headers | wc -l
kubectl get pods -A --field-selector=status.phase=Running --no-headers | wc -l

110 是怎么评估出来的?

后期可以修改吗 —— 可以,只改 kubelet,不用动集群

# 1. 先确认当前生效值(节点上的 kubelet 配置)
grep maxPods /var/lib/kubelet/config.yaml
maxPods: 110

# 2. 改成目标值(如 200)
sed -i 's/^maxPods:.*/maxPods: 200/' /var/lib/kubelet/config.yaml

# 3. 重启 kubelet 生效(节点上执行,秒级,不影响已运行 Pod)
systemctl restart kubelet

# 4. 验证:上报的 allocatable.pods 已变成新值
kubectl get node qingfeng -o jsonpath='{.status.allocatable.pods}'; echo
200
⚠️ 调大之前先确认:maxPods 只解"数量名额",不解资源 —— CPU/内存(第 02 节磁盘同理)、CNI IP 池、PID 上限真的撑得住才能调;云托管集群(ACK/EKS/GKE)的默认值和改法由厂商参数/CNI 决定,别直接改托管节点。调小则要谨慎:低于当前已运行 Pod 数会导致节点被判超卖。

易混点:node 和 namespace 是两种东西,别搞混

kubectl get node qingfeng 查的是节点(一台真实的机器,本集群单节点就叫 qingfeng) —— maxPods 是节点属性,所以要 get node;而 namespace 是集群里的逻辑分区(default / kube-system / harbor ...),只负责把资源分组隔离,和节点没有包含关系。

node 节点namespace 命名空间
是什么一台跑容器的机器(kubelet 所在)集群内的逻辑分组/虚拟小集群
典型名字qingfeng、worker-01default、kube-system、harbor、prod
查看命令kubectl get nodeskubectl get ns
Pod 数量上限maxPods(kubelet,默认 110)ResourceQuota 的 pods: N(不配则不限)
# 查看某个命名空间
kubectl get ns                       # 列出全部命名空间
kubectl get ns harbor -o wide        # 看某个 ns 的状态
kubectl describe ns harbor           # 详情:标签、资源配额(Quota)、状态

# "看一个 ns 里的东西"(最常见诉求,用 -n 指定)
kubectl get pods -n harbor           # 该 ns 的 Pod
kubectl get all -n harbor            # Pod/Svc/Deploy 一次全看
kubectl get pods -A                  # 想全看就去掉 -n 加 -A

# ns 级别的 Pod 数上限 = ResourceQuota(不建就没有限制)
kubectl get resourcequota -n harbor
# 给 harbor ns 限制最多 50 个 Pod:
kubectl create quota pod-limit -n harbor --hard=pods=50
💡 一句话分清:node 管"在哪台机器上跑、最多跑几个"(maxPods);namespace 管"资源属于哪个组、组里能用多少"(ResourceQuota) —— 查前者 get node,查后者 get ns,查"某个组里的资源"要加 -n。