# Администрирование кеша образов в 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 | Активен |

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

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



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

### 2.1. containerd

[containerd документация](https://containerd.io/docs/main/)

**Хранилище:** `/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`.

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

```bash
# Общий размер
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/
```

**Очистка:**

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

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

**Настройка GC:**

```toml
# /etc/containerd/config.toml
[plugins.'io.containerd.cri.v1.images']
    # Удалять распакованные слои после использования
    discard_unpacked_layers = true
    
    # Максимум параллельных скачиваний
    max_concurrent_downloads = 3
```

```toml
[plugins.'io.containerd.gc.v1.scheduler']
    # Порог удаления (0 = выключено)
    deletion_threshold = 0
    
    # Задержка между запусками GC
    schedule_delay = '0s'
```

### 2.2. CRI-O

[CRI-O](https://docs.redhat.com/en/documentation/openshift_container_platform/4.10/html/support/troubleshooting#troubleshooting-crio-issues) — это легковесный контейнерный движок (Container Runtime), специально разработанный для Kubernetes и реализующий интерфейс Container Runtime Interface (CRI).



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



[Buildah](https://github.com/podman-container-tools/buildah/blob/main/docs/buildah-rmi.1.md) — инструмент командной строки для создания образов контейнеров, совместимых с 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`.

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

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



**Очистка:**

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

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

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

**Настройка:**

```bash
# /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](https://docs.docker.com/engine/manage-resources/pruning/)


**Хранилище:** `/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.

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

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


**Очистка:**

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

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

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

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

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


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

### Настройка GC

[Garbage Collection (GC)](https://kubernetes.io/docs/concepts/architecture/garbage-collection/) в Kubernetes — это "сборщик мусора", механизм автоматического удаления объектов, которые больше не нужны или потеряли владельца, чтобы предотвратить накопление неиспользуемых ресурсов.

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



```yaml
# /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

```bash
# Статус 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

```bash
# Очистить неиспользуемые образы
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

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

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

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

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

#### Docker

```bash
# Полная очистка. УДАЛИТ ВСЕ! ВКЛЮЧАЯ ПОДЫ
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

```bash
# Определить 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

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

### CRI-O

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

### Docker

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

### Kubelet (общее)

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

После изменения любого конфига:
```bash
systemctl restart containerd  # или crio, docker
systemctl restart kubelet
```
