En corto
En la máquina donde construyes:
| |
En cada nodo de k3s donde pueda caer el pod:
| |
Y en el Deployment, que kubelet no intente descargarla:
| |
Por qué
k3s corre sobre containerd, no sobre Docker. Una imagen que existe en el Docker de la misma máquina es invisible para k3s, y la primera vez que un pod la pide kubelet intenta bajarla de Docker Hub y falla con ErrImagePull. Las salidas son tres: un registry (lo correcto a partir de cierto tamaño), importar el tar en cada nodo, o dejar que k3s la importe sola desde un directorio.
k3s ctr es el ctr de containerd ya apuntado al socket de k3s y al namespace k8s.io, que es donde miran kubelet y crictl. Si usas el binario ctr a pelo necesitas --address /run/k3s/containerd/containerd.sock -n k8s.io.
Alternativas para más de un nodo
Directorio de imágenes. Todo .tar, .tar.gz o .tar.zst que dejes en /var/lib/rancher/k3s/agent/images/ se importa cada vez que arranca k3s (es el mecanismo documentado para instalaciones sin Internet):
| |
Ventaja sobre el import a mano: si el recolector de imágenes de kubelet la borra, vuelve en el siguiente arranque.
Registry embebido (Spegel). Desde k3s 1.29 los servidores pueden arrancar con --embedded-registry (o embedded-registry: true en /etc/rancher/k3s/config.yaml); cada nodo publica en la red P2P (puerto 5001) las imágenes que ya tiene y los demás las bajan de él en vez de Internet. Requiere listar los registries a espejar en /etc/rancher/k3s/registries.yaml:
| |
Con eso, importas la imagen en un nodo y el resto la consigue solo, siempre que los nodos se vean entre sí por 5001 y 6443.
Registry propio. Un registry:2 o Harbor en el clúster, y docker push. Es el camino cuando hay CI de por medio.
Listar y limpiar
| |
kubelet también recolecta solo: cuando el disco de /var/lib/rancher/k3s/agent/containerd supera el 85 % borra imágenes sin uso hasta bajar del 80 %. Los umbrales se cambian en el servicio:
| |
Si ese disco se queda corto, mover el data-dir es otro post.
Trampas
- Cada nodo. Un pod programado en un nodo sin la imagen da
ErrImageNeverPull. O importas en todos, o etiquetas el nodo y usasnodeSelector, o pasas al registry embebido. - Arquitectura. Para nodos ARM (Raspberry Pi) la imagen tiene que ser
linux/arm64:docker buildx build --platform linux/arm64. latesttira de la red. Con taglatest, la política por defecto esAlwaysy kubelet intenta bajarla aunque exista. Usa tags explícitos yNeveroIfNotPresent.- El nombre completo de una imagen importada es
docker.io/library/test-app:v1.0.0; en el manifiestotest-app:v1.0.0resuelve a lo mismo. - podman:
podman save --format docker-archive -o app.tar app:v1, el formato quectrimporta sin sorpresas. - Un
crictl rmi --pruneborra también lo que importaste si en ese momento ningún contenedor lo usa. Para lo que quieres que sobreviva, el directorioagent/images/.
Referencias: Air-Gap Install y Embedded Registry Mirror en la documentación de k3s.