Администрирование кеша образов в Kubernetes#

1. CRI-движки: обзор#

Kubernetes использует CRI (Container Runtime Interface) для работы с контейнерами. Основные реализации:

Движок

Описание

Где используется

Статус

containerd

Лёгкий, модульный

Большинство кластеров (EKS, GKE, AKS, kubeadm)

Активен

Docker Engine

Полный стек (dockerd + containerd)

Не используется в кластерах kubernetes

Deprecated в K8s 1.24+

CRI-O

Минимальный, только для K8s

OpenShift, SUSE RKE2

Активен

Какой движок используется в нашем кластере#

# Проверка CRI-движка на ноде
kubectl get nodes \
  -o custom-columns=NODE:.metadata.name,CONTAINER-RUNTIME:.status.nodeInfo.containerRuntimeVersion

2. Хранение образов по движкам#

2.1. containerd#

containerd документация

Хранилище: /var/lib/containerd/

/var/lib/containerd/
├── io.containerd.content.v1.content/     # Бинарные данные (blobs) образов
│   └── blobs/sha256/                     # Файлы слоёв по SHA256
├── io.containerd.snapshotter.v1.overlayfs/  # Распакованные слои (снапшоты)
│   └── snapshots/                        # Каждый снапшот = отдельный каталог
└── io.containerd.metadata.v1.bolt/       # Метаданные ( BoltDB )

Ключевая особененость: Слой snapshots/ растёт с каждым pull нового образа и не очищается автоматически при discard_unpacked_layers = false.

Проверка размера:

# Общий размер
du -sh /var/lib/containerd/

# Снапшоты (основной потребитель)
du -sh /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/
echo "Количество снапшотов: $(find /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots -mindepth 1 -maxdepth 1 -type d | wc -l)"

# Content (blobs)
du -sh /var/lib/containerd/io.containerd.content.v1.content/

Очистка:

# Список образов
crictl images

# Удалить неиспользуемые образы
crictl rmi --prune

Настройка GC:

# /etc/containerd/config.toml
[plugins.'io.containerd.cri.v1.images']
    # Удалять распакованные слои после использования
    discard_unpacked_layers = true
    
    # Максимум параллельных скачиваний
    max_concurrent_downloads = 3
[plugins.'io.containerd.gc.v1.scheduler']
    # Порог удаления (0 = выключено)
    deletion_threshold = 0
    
    # Задержка между запусками GC
    schedule_delay = '0s'

2.2. CRI-O#

CRI-O — это легковесный контейнерный движок (Container Runtime), специально разработанный для Kubernetes и реализующий интерфейс Container Runtime Interface (CRI).

Podman (сокращение от pod manager) — это открытый инструмент для управления контейнерами, образами, томами и подами (группами взаимосвязанных контейнеров). Он соответствует стандартам Open Container Initiative (OCI) и разработан компанией Red Hat.

Buildah — инструмент командной строки для создания образов контейнеров, совместимых с OCI. Позволяет собирать образы как с использованием Dockerfile, так и без него.

Хранилище: /var/lib/containers/storage/

/var/lib/containers/storage/
├── overlay/                              # Слои образов (overlayfs)
│   └── <layer-id>/                       # Каждый слой
│       ├── diff/                         # Изменения слоя
│       ├── link                          # Ссылка на родительский слой
│       └── lower                         # Нижние слои
├── overlay-images/                       # Метаданные образов
├── overlay-layers/                       # Метаданные слоёв
└── vfs/                                  # Alternative storage driver

Ключевая особенность: CRI-O хранит образы в 1.7x больше containerd. Нет встроенного --prune. Очистка через buildah или podman.

Проверка размера:

du -sh /var/lib/containers/storage/
du -sh /var/lib/containers/storage/overlay/
ls /var/lib/containers/storage/overlay/ | wc -l

Очистка:

crictl --runtime-endpoint=unix:///var/run/crio/crio.sock rmi --prune

# Через podman (если установлен)
podman system prune

# Через buildah
buildah rmi --prune

Настройка:

# /etc/containers/storage.conf
[storage]
driver = "overlay"
runroot = "/run/containers/storage"
graphroot = "/var/lib/containers/storage"

[storage.options.overlay]
# Удалять неиспользуемые слои
mountopt = "nodev,metacopy=on"

2.3. Docker Engine#

Управление ресурсами docker

Хранилище: /var/lib/docker/

/var/lib/docker/
├── overlay2/                             # Слои образов (overlay2)
│   ├── <layer-id>/                       # Каждый слой
│   │   ├── diff/                         # Содержимое слоя
│   │   ├── link                          # Короткий ID
│   │   ├── lower                         # Нижние слои
│   │   └── merged/                       # Объединённая ФС (монтируется в контейнер)
│   └── l/                                # Симлинки на слои
├── image/                                # Метаданные образов
│   └── overlay2/
│       ├── imagedb/                      # Описания образов
│       ├── layerdb/                      # Метаданные слоёв
│       └── repositories.json             # Реестр локальных образов
├── containers/                           # Конфигурации контейнеров
└── volumes/                              # Docker-тома

Ключевая особенность: Docker хранит merged/ директорию для каждого запущенного контейнера. Остановленные контейнеры сохраняют writable layer.

Проверка размера:

du -sh /var/lib/docker/
du -sh /var/lib/docker/overlay2/
docker system df
docker system df -v

Очистка:

# Удалить неиспользуемые образы
docker image prune -a

# Удалить неиспользуемые контейнеры
docker container prune

# Удалить неиспользуемые тома
docker volume prune

# Полная очистка (образы + контейнеры + тома + сеть)
docker system prune -a --volumes

# Статистика
docker system df

3. Kubelet: управление кешем (для всех движков)#

Настройка GC#

Garbage Collection (GC) в Kubernetes — это «сборщик мусора», механизм автоматического удаления объектов, которые больше не нужны или потеряли владельца, чтобы предотвратить накопление неиспользуемых ресурсов.

Основная задача GC — освобождение ресурсов кластера и поддержание его в чистом состоянии без ручного вмешательства.

# /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

# Порог начала GC (диск заполнен > этого %)
imageGCHighThresholdPercent: 70    # default: 85

# Порог остановки GC
imageGCLowThresholdPercent: 60     # default: 80

# Максимальный возраст неиспользуемых образов
imageMaximumGCAge: 24h             # default: 0 (без ограничения)

# Минимальный возраст (образы младше не удаляются)
imageMinimumGCAge: 2m              # default: 2m

Проверка состояния GC#

# Статус kubelet
kubectl describe node НОДА.internal | grep -A 5 "Conditions:"

# Логи kubelet (ошибки GC)
journalctl -u kubelet | grep -i "image\|gc\|prune" | tail -20

# Количество образов по мнению kubelet
kubectl get nodes -o json | jq '.items[].status.images | length'

4. Пример: переполнение диска#

Симптомы#

  • Поды в состоянии Evicted или CrashLoopBackOff

  • Ошибки No space left on device в логах kubelet

  • Новые поды не могут запуститься на worker-ноде

  • Приложения gs-ctk недоступны

Причина#

При обновлениях gs-ctk Kubelet скачивает новые образы приложений. Старые образы остаются в кеше и не удаляются автоматически. Со временем кеш распухает и заполняет диск.

Масштаб проблемы#

Сравнение containerd, CRI-O, Docker#

Проведены тесты: 10 последовательных pull различных образов через DaemonSet.

Метрика

containerd

CRI-O

Docker

Рост диска (10 обновлений)

+4.2G (52%→59%)

+4.4G (34%→43%)

+7G (71%→83%)

Рост хранилища

+4G

+16G

+7G

Размер образа gs-ctk (средний)

~1GB

~2.7GB

~3.8GB

Размер образов gs-ctk в разных движках#

Образ

containerd

CRI-O

Docker

ctk-restapi:6.0.0

969MB

2.63GB

3.69GB

nsctl:6.0.0

979MB

2.68GB

3.76GB

globalserver-java21:6.0.0

1.33GB

2.99GB

4.43GB

grafana:6.0.0

1.52GB

3.28GB

4.9GB

haproxy:6.0.0

958MB

2.62GB

3.67GB

haproxyagent:6.0.0

986MB

2.64GB

3.73GB

systemagent:6.0.0

1.27GB

3.24GB

4.61GB

rabbitmq:6.0.0

980MB

2.64GB

3.72GB

Вывод: containerd наиболее экономичен. CRI-O хранит образы в 2.7x больше containerd. Docker — самый прожорливый (3.8x больше containerd).

5. Административные шаги#

5.1. Экстренная очистка (при инциденте)#

containerd#

# Очистить неиспользуемые образы
crictl rmi --prune

# Очистить остановленные контейнеры
crictl rm $(crictl ps -a -q --state stopped 2>/dev/null) 2>/dev/null

# Проверить место
df -h /

# Если не помогло — перезапустить containerd
systemctl restart containerd

CRI-O#

crictl --runtime-endpoint=unix:///var/run/crio/crio.sock rmi --prune

# Очистить через podman
podman system prune

# Или через buildah
buildah rmi --prune

# Проверить место
df -h /

Docker#

# Полная очистка. УДАЛИТ ВСЕ! ВКЛЮЧАЯ ПОДЫ
docker system prune -a --volumes

# Проверить место
docker system df
df -h /

5.2. Профилактическая очистка#

Движок

Команда

containerd

crictl rmi --prune

CRI-O

podman system prune

Docker

docker system prune

Рекомендуемая частота: Еженедельно + после каждого обновления приложения.

5.3. Команды для проверки хранилища#

Проверка через kubectl debug#

# Определить CRI-движок
RUNTIME=$(kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.containerRuntimeVersion}')

# Проверить диск
kubectl debug node/НОДА.internal -it --image=busybox -- df -h /host

# Проверить containerd
kubectl debug node/НОДА.internal -it --image=busybox -- du -sh /host/var/lib/containerd/

# Проверить Docker
kubectl debug node/НОДА.internal -it --image=busybox -- du -sh /host/var/lib/docker/

6. Оптимизация дискового пространства#

По умолчанию контейнерные движки и kubelet хранят скачанные образы, слои и логи достаточно долго. При частых обновлениях приложений это может привести к постепенному заполнению диска.

Следующие настройки позволяют:

  • уменьшить объем занимаемого дискового пространства;

  • быстрее удалять неиспользуемые образы и слои;

  • сократить накопление контейнерных логов;

  • снизить вероятность остановки новых Pod’ов из-за нехватки места на узле.

containerd#

# /etc/containerd/config.toml
[plugins.'io.containerd.cri.v1.images']
    discard_unpacked_layers = true

CRI-O#

# /etc/containers/storage.conf
[storage.options.overlay]
mountopt = "nodev,metacopy=on"

Docker#

// /etc/docker/daemon.json
{
  "storage-driver": "overlay2",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Kubelet (общее)#

# /var/lib/kubelet/config.yaml
imageGCHighThresholdPercent: 70
imageGCLowThresholdPercent: 60
imageMaximumGCAge: 24h
imageMinimumGCAge: 2m

После изменения любого конфига:

systemctl restart containerd  # или crio, docker
systemctl restart kubelet