Администрирование кеша образов в 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#
Хранилище: /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#
Хранилище: /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 |
|
CRI-O |
|
Docker |
|
Рекомендуемая частота: Еженедельно + после каждого обновления приложения.
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