K3s镜像被驱逐案例
1.故障现象
正在运行中的大量pod突然异常,无法启动,状态是ImagePullBackOff或者ErrImagePull。
2.故障根因
当磁盘空间不够时,K3S会驱逐镜像,此时pod重启时就无法获取镜像、启动异常。
通过df -Th|head命令可确认空间不足;通过kubectl describe pod 命令可确认大量pod启动时无法获取镜像。
3.解决方案
此时请确保当前系统盘磁盘空间至少剩余15%,如果没有则先扩容或清理其他文件,然后按如下方法顺序逐一尝试:
方法1. 驱逐镜像自动恢复脚本(推荐)
当镜像被驱逐时,可以尝试使用官方提供的恢复脚本进行镜像恢复。
下载脚本命令如下:
# arm64/amd64通用
curl -O https://packages.ones.cn/release/k3s/v1.29.1%2Bk3s2/linux/amd64/load-images.sh
bash load-images.sh
脚本主要功能:
- k3s/ones 基础依赖镜像导入,通过递归扫描镜像目录。
- 完成配置滚动 kube-system/registry(高优先级,避免非关键组件被驱逐)。
- 删除 ones 命名空间下所有非 Running 状态的 pod,相关服务将自动重建并拉取新镜像。
下载后请根据实际情况执行脚本,帮助恢复被驱逐的镜像。
方法2:镜像仓服务5000端口访问失败,重启镜像仓
故障现象:kubectl -n ones describe pod xxxxxx 提示:dial tcp 127.0.0.1:5000: connect: connection refused。
验证方式:curl 127.0.0.1:5000 不通
处理方法: (1) 参考修复指南 第16节核对相关参数是否一致; 如果不一致,请修改。
(2) 重启CoreDNS和镜像仓代理服务
kubectl -n kube-system delete pod -l app=registry-proxy
kubectl -n kube-system delete pod -l app=registry
kubectl -n kube-system delete pod -l k8s-app=kube-dns
方法3:手动镜像重新推送
当自动恢复脚本执行失败,或者需要单独恢复基础镜像、ONES 业务镜像时,可以使用本方法。执行前请确保系统盘至少剩余 15%,并使用 root 权限操作。
3.1 获取 installer-api 信息
请按卷名称获取安装包目录,不要从 kubectl describe 输出中截取第一个 Path,否则可能误取 config-volume 的路径。
pod=$(kubectl -n ones-installer get pod \
-l app=installer-api \
-o jsonpath='{.items[0].metadata.name}')
pkg_path=$(kubectl -n ones-installer get deploy installer-api \
-o jsonpath='{.spec.template.spec.volumes[?(@.name=="pkg-volume")].hostPath.path}')
test -n "$pod" || {
echo "未找到 installer-api Pod"
exit 1
}
test -d "$pkg_path" || {
echo "安装包目录不存在:$pkg_path"
exit 1
}
printf 'installer-api pod: %s\n' "$pod"
printf 'pkg hostPath: %s\n' "$pkg_path"
3.2 导入基础镜像
优先使用安装时保留在 /data/ones/base-images 下的基础镜像包,不要硬编码 Registry 组件版本目录。
base_images_dir=/data/ones/base-images
test -f "$base_images_dir/k3s/k3s-images.tar" || exit 1
test -f "$base_images_dir/registry/registry_images.tar.gz" || exit 1
test -f "$base_images_dir/go-dnsmasq/go-dnsmasq.tar" || exit 1
k3s ctr image import --no-unpack \
"$base_images_dir/k3s/k3s-images.tar"
tar -zxOf \
"$base_images_dir/registry/registry_images.tar.gz" |
k3s ctr image import --no-unpack -
k3s ctr image import --no-unpack \
"$base_images_dir/go-dnsmasq/go-dnsmasq.tar"
导入后检查节点 containerd 中的基础镜像:
k3s ctr images list |
grep -E 'registry|registry-proxy|coredns|go-dnsmasq'
上述命令恢复的是节点 containerd 中的基础镜像,不能据此判断 ONES 业务镜像已经导入本地 Registry。
3.3 导入 ONES 业务镜像
先列出 installer-api 容器中实际存在的离线包清单:
kubectl -n ones-installer exec "$pod" -c installer-api -- \
find /data/ones/pkg \
-type f \
-path '*/images/images.yaml' \
-print
请人工确认离线包的目标版本;如果使用增量包,还需要确认来源版本。不要使用 ls -t | head -1 根据目录修改时间自动选择离线包。
确认离线包已经解压,并且同时包含 images/images.yaml 和 images/registry/docker.tar。例如:
offline_pkg_dir=/data/ones/pkg/upgrade/<已解压的离线包目录>
images_yaml="$offline_pkg_dir/images/images.yaml"
kubectl -n ones-installer exec "$pod" -c installer-api -- \
test -f "$images_yaml"
kubectl -n ones-installer exec "$pod" -c installer-api -- \
test -f "$offline_pkg_dir/images/registry/docker.tar"
确认无误后导入业务镜像:
kubectl -n ones-installer exec "$pod" -c installer-api -- \
sh -ec "
cd /data/ones/ones-ai-k8s
make import-images \
OFFLINE_PKG_DIR='$offline_pkg_dir' \
FILE='$images_yaml'
"
如果 /data/ones/pkg 中没有对应版本的离线包,请先重新上传并解压匹配当前 ONES 版本的完整包或增量包,不要继续执行导入命令。
3.4 分层验证
基础镜像、Registry 服务、业务镜像和工作负载版本需要分别验证:
# 验证基础镜像
k3s ctr images list |
grep -E 'registry|registry-proxy|coredns|go-dnsmasq'
# 验证 Registry 服务
curl -fsS http://127.0.0.1:5000/v2/ >/dev/null
kubectl -n kube-system get pod \
-l 'app in (registry,registry-proxy)'
# 查看工作负载实际使用的镜像版本
kubectl -n ones get deploy,statefulset \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .spec.template.spec.containers[*]}{.image}{" "}{end}{"\n"}{end}'
业务镜像还应通过 Registry manifest 接口验证,不能只检查 k3s ctr images list:
curl -fsSI \
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
http://127.0.0.1:5000/v2/<镜像仓库路径>/manifests/<标签>
方法4:单独推送dnsmasq镜像
故障现象:kubectl -n ones describe pod xxxxxx 提示: OCI runtime create failed: runc create failed: unable to start container process: exec: "--listen": executable file not found in $PATH: unknown
说明:无法启动的 Pod 都卡在 go-dnsmasq 镜像获取过程,说明节点上的 go-dnsmasq 镜像可能异常。请根据当前工作负载动态获取镜像版本,不要使用固定标签。
4.1 获取当前工作负载使用的镜像
dnsmasq_images=$(kubectl -n ones get deploy,statefulset \
-o jsonpath='{range .items[*]}{range .spec.template.spec.containers[*]}{.image}{"\n"}{end}{end}' |
grep 'go-dnsmasq' |
sort -u)
printf '%s\n' "$dnsmasq_images"
如果返回多个版本,请先确认不同工作负载的实际需求,不要自动选择第一个版本。确认只有一个版本后执行:
dnsmasq_image=$(printf '%s\n' "$dnsmasq_images" | head -1)
4.2 确认本地 Registry 中存在该镜像
image_ref=${dnsmasq_image#localhost:5000/}
image_repo=${image_ref%:*}
image_tag=${image_ref##*:}
curl -fsSI \
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
"http://127.0.0.1:5000/v2/${image_repo}/manifests/${image_tag}"
只有在 manifest 请求成功后,才能安全删除节点上的异常镜像引用。
4.3 删除并重新拉取镜像
crictl rmi "$dnsmasq_image"
crictl pull "$dnsmasq_image"
crictl images | grep go-dnsmasq
如果 manifest 请求失败,说明本地 Registry 中没有当前工作负载所需的镜像。此时不要导入固定的旧版本,应找到包含相同版本的 ONES 离线包,使用方法 3 导入业务镜像后再重新拉取。
4.4 重建受影响的 Pod
先列出使用该镜像的 Pod:
kubectl -n ones get pods -o json |
jq -r --arg image "$dnsmasq_image" '
.items[]
| select(any(.spec.containers[]; .image == $image))
| .metadata.name
'
人工确认列表后,再删除受影响的 Pod,让控制器自动重建。不要直接删除整个命名空间下的全部 Pod。
验证
上述方法处理完成后,请重启无法启动的 Pod,然后验证业务是否正常。
# 查看异常 Pod
kubectl get pods -A --no-headers |
awk '$4 != "Running" && $4 != "Completed"'
# 重启 ones 命名空间下的异常 Pod,其他命名空间请单独处理
kubectl -n ones get pods --no-headers |
awk '$3 != "Running" && $3 != "Completed" {print $1}' |
xargs -r kubectl -n ones delete pod