# K3s镜像被驱逐案例

URL: https://docs.ones.cn/deploy/troubleshooting/faq/Image-Eviction-Issues

## 1.故障现象

正在运行中的大量pod突然异常，无法启动，状态是ImagePullBackOff或者ErrImagePull。

## 2.故障根因

当磁盘空间不够时，K3S会驱逐镜像，此时pod重启时就无法获取镜像、启动异常。

通过df -Th|head命令可确认空间不足；通过kubectl describe pod 命令可确认大量pod启动时无法获取镜像。

## 3.解决方案

此时请确保当前系统盘磁盘空间至少剩余15%，如果没有则先扩容或清理其他文件，然后按如下方法顺序逐一尝试：

## 方法1. 驱逐镜像自动恢复脚本（推荐）

当镜像被驱逐时，可以尝试使用官方提供的恢复脚本进行镜像恢复。

下载脚本命令如下：

```bash
# 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) 参考 [修复指南](https://docs.ones.cn/deploy/prepare/linux-fix/) 第16节核对相关参数是否一致; 如果不一致，请修改。

(2) 重启CoreDNS和镜像仓代理服务

```bash
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` 的路径。

```bash
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 组件版本目录。

```bash
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 中的基础镜像：

```bash
k3s ctr images list |
grep -E 'registry|registry-proxy|coredns|go-dnsmasq'
```

备注
上述命令恢复的是节点 containerd 中的基础镜像，不能据此判断 ONES 业务镜像已经导入本地 Registry。

### 3.3 导入 ONES 业务镜像

先列出 `installer-api` 容器中实际存在的离线包清单：

```bash
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` 。例如：

```bash
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"
```

确认无误后导入业务镜像：

```bash
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 服务、业务镜像和工作负载版本需要分别验证：

```bash
# 验证基础镜像
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` ：

```bash
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 获取当前工作负载使用的镜像

```bash
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"
```

如果返回多个版本，请先确认不同工作负载的实际需求，不要自动选择第一个版本。确认只有一个版本后执行：

```bash
dnsmasq_image=$(printf '%s\n' "$dnsmasq_images" | head -1)
```

### 4.2 确认本地 Registry 中存在该镜像

```bash
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 删除并重新拉取镜像

```bash
crictl rmi "$dnsmasq_image"
crictl pull "$dnsmasq_image"
crictl images | grep go-dnsmasq
```

如果 manifest 请求失败，说明本地 Registry 中没有当前工作负载所需的镜像。此时不要导入固定的旧版本，应找到包含相同版本的 ONES 离线包，使用方法 3 导入业务镜像后再重新拉取。

### 4.4 重建受影响的 Pod

先列出使用该镜像的 Pod：

```bash
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，然后验证业务是否正常。

```bash
# 查看异常 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
```
