跳到主要内容

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

脚本主要功能:

  1. k3s/ones 基础依赖镜像导入,通过递归扫描镜像目录。
  2. 完成配置滚动 kube-system/registry(高优先级,避免非关键组件被驱逐)。
  3. 删除 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