Docker и Kubernetes: containerd, миграция, kompose

Рейтинг: 59.6% · 10 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Docker и Kubernetes: containerd, миграция, kompose

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose (вы здесь)
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Помнишь декабрь 2020-го, когда полинтернета кричало "Kubernetes выпиливает Docker, все пропало, переписывайте"? А потом в 1.24 (2022) dockershim реально вырезали - и опять волна паники. Так вот: 99 процентов этой паники было на пустом месте. Твои образы, собранные через docker build, как работали в кластере, так и работают. Ничего переписывать не надо. В этом уроке разберем связку docker kubernetes по-честному: что именно убрали, почему образы выживают, как образ из Docker попадает в k8s, и как перетащить твой compose в кластер - через kompose и руками. Это мост от мира Compose к миру оркестрации, и проходить его надо с холодной головой.

Что на самом деле убрали: dockershim, а не Docker

Чтобы понять, надо разделить две вещи, которые новички постоянно путают: формат образа и рантайм, который этот образ запускает.

Когда ты делаешь docker build, на выходе - образ в формате OCI (Open Container Initiative). Это открытый стандарт: спецификация на то, как устроены слои файловой системы, манифест, конфиг. Docker его не придумывал в одиночку и не владеет им. Точно так же OCI описывает рантайм - как из распакованного образа сделать живой процесс в namespaces и cgroups. Эталонная реализация низкоуровневого рантайма - runc.

Теперь сам Docker. Docker Engine (демон dockerd) - это не монолит. Под ним давно живет containerd - отдельный демон, который и занимается реальной работой: тянет образы из реестра, распаковывает слои, управляет жизненным циклом контейнеров, дергает runc. dockerd сверху - это удобная обертка: CLI, build, сеть, тома, REST API. То есть containerd Docker уже использует много лет, ты просто этого не видел.

А что такое kubelet? Это агент Kubernetes на каждой ноде, который запускает и следит за контейнерами пода. Чтобы говорить с рантаймом, у Kubernetes есть стандартный интерфейс - CRI (Container Runtime Interface), это grpc-контракт. containerd и CRI-O умеют CRI нативно. А вот dockerd CRI не умел никогда. Поэтому в Kubernetes жил костыль под названием dockershim - прослойка внутри kubelet, которая переводила вызовы CRI в API Docker.

Получалась дикая цепочка: kubelet -> dockershim -> dockerd -> containerd -> runc. Лишние два звена в горячем пути. Команда Kubernetes годами тащила этот код, он усложнял kubelet и тормозил развитие. В 1.24 dockershim удалили. И теперь честный путь короче:

Код: Выделить всё

# Было (до 1.24):
kubelet -> dockershim -> dockerd -> containerd -> runc -> [контейнер]

# Стало (containerd kubernetes напрямую):
kubelet --CRI--> containerd -> runc -> [контейнер]
Убрали прослойку dockershim, а не Docker как технологию и не его образы. containerd, который и так делал всю работу, остался - просто теперь kubelet ходит в него напрямую по CRI, минуя dockerd. Вот и вся "трагедия".

Изображение

Почему docker-образы работают в kubernetes без изменений

Ключевая мысль, которую надо вбить намертво: docker build делает не "docker-образ", а OCI-образ. Слово "docker" в имени - историческое. Контейнерный рантайм в кластере (containerd или CRI-O) точно так же понимает OCI-манифест, те же слои в tar+gzip, тот же конфиг с ENTRYPOINT и переменными окружения.

Проверь сам, что образ из Docker Hub - это OCI:

Код: Выделить всё

docker buildx imagetools inspect nginx:1.27 --format '{{json .Manifest.MediaType}}'
# "application/vnd.oci.image.index.v1+json"
Media type с пометкой oci - это и есть стандарт, который читают все CRI-рантаймы. Поэтому образ, собранный у тебя на ноутбуке через docker build, без единой правки поедет в любой кластер. Docker в kubernetes как рантайм ушел, а Docker как инструмент сборки образов - остался и прекрасно живет. Ты по-прежнему собираешь образы локально через Docker (или buildx), пушишь в реестр, а кластер их запускает на containerd. Граница проходит ровно по формату OCI - он и есть тот контракт, который всех мирит.

Маленькая оговорка про containerd: для k8s включается CRI-плагин containerd, и образы он хранит в своем сторадже, а не в /var/lib/docker. На ноде это видно так:

Код: Выделить всё

# на k8s-ноде с containerd
crictl images
# IMAGE                     TAG       IMAGE ID       SIZE
# registry.k8s.io/pause     3.10      873ed7510279   320kB
# docker.io/library/nginx   1.27      5ef79149e0ec   72.1MB
crictl - это CLI для CRI, аналог docker ps/images, но для кластерного рантайма. Если на ноде нет dockerd, привычный docker там просто не установлен - и это норма.

Как образ из Docker попадает в кластер: реестр и imagePullSecrets

Локально ты привык: docker build, docker run - образ уже на машине, рантайм берет его из локального стора. В кластере так не выйдет: под может приехать на любую из десятков нод, и собранного локально образа там нет. Поэтому канонический поток - через реестр:
  • docker build -t registry.example.ru/team/api:1.4.2 .
  • docker push registry.example.ru/team/api:1.4.2
  • в манифесте пода указываешь image: registry.example.ru/team/api:1.4.2
  • kubelet на ноде дергает containerd, тот тянет образ из реестра и запускает
Если реестр приватный (а в продакшене почти всегда так - свой Harbor, GitLab Registry, Yandex Container Registry, VK Cloud), kubelet надо дать креды. Это делается через объект Secret типа docker-registry и поле imagePullSecrets:

Код: Выделить всё

kubectl create secret docker-registry regcred \
  --docker-server=registry.example.ru \
  --docker-username=ci-bot \
  --docker-password='***' \
  --docker-email=ci@example.ru

Код: Выделить всё

# в Pod/Deployment
spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - name: api
      image: registry.example.ru/team/api:1.4.2
Под капотом этот Secret - тот же самый ~/.docker/config.json с base64-кредами, что Docker пишет при docker login. kubelet берет его и аутентифицируется в реестре ровно как docker pull. RU-реалия: Docker Hub из РФ нестабилен и лимитирует анонимные пуллы, поэтому в проде заводят зеркало или приватный реестр (Yandex/VK Container Registry) и тянут оттуда - меньше боли с rate limit и доступностью.

Отдельная грабля - imagePullPolicy. Если тег :latest, политика по умолчанию Always - kubelet лезет в реестр на каждый старт пода. Для фиксированного тега вроде :1.4.2 дефолт - IfNotPresent (есть локально - не качает). В продакшене не используй :latest: непредсказуемо, какой именно образ поднимется, и откат превращается в гадание. Тегируй по версии или по git sha.

compose в kubernetes: kompose и ручное переписывание

Самый частый запрос на этом мосту - как затащить compose в kubernetes. У тебя есть рабочий compose.yaml, и хочется не переписывать сотню строк руками. Тут на сцену выходит kompose - официальный инструмент проекта Kubernetes, который конвертирует Compose в манифесты.

Код: Выделить всё

# актуальная версия на 2026 - v1.38
kompose version

kompose convert -f compose.yaml
# INFO Kubernetes file "api-service.yaml" created
# INFO Kubernetes file "api-deployment.yaml" created
# INFO Kubernetes file "db-deployment.yaml" created
# INFO Kubernetes file "db-persistentvolumeclaim.yaml" created
kompose разбирает compose и раскладывает его на родные объекты Kubernetes. Маппинг примерно такой:
  • каждый service -> Deployment (контроллер, который держит N реплик пода живыми)
  • ports/expose -> Service (стабильный внутрикластерный адрес и балансировка на поды)
  • volumes (именованный том) -> PersistentVolumeClaim (PVC, заявка на постоянный диск)
  • environment -> env прямо в контейнере, а через аннотации можно вытащить в ConfigMap
  • restart: always -> дефолтное поведение Deployment, отдельной настройки не требует
Важно трезво оценивать результат: kompose дает примерно 70-80 процентов автоконвертации. Он снимает рутину и боль с boilerplate, но прод-готовых манифестов не выдает. После него руками доводишь: liveness/readiness-пробы, requests/limits на CPU и память, секреты (kompose оставляет пароли в открытом env - это надо вынести в Secret), сетевые политики, корректный StorageClass для PVC. Учитывай и контекст 2026: Ingress-NGINX дошел до end-of-life, и для входящего трафика смотрят в сторону Gateway API - свежий kompose это уже учитывает, но проверяй сгенерированное.

Теперь почему стоит хотя бы раз переписать руками, а не только гонять конвертер. Несколько мест, где Compose и Kubernetes мыслят по-разному:
  • depends_on. В Compose это "запусти db раньше api". В Kubernetes порядка запуска нет в принципе - поды поднимаются параллельно и переезжают независимо. Поэтому depends_on не конвертируется один-в-один. Правильный путь: readiness-проба на api, чтобы трафик не шел, пока зависимости не готовы, и при необходимости initContainer, который блокирует старт, пока, скажем, не отвечает порт БД. Приложение должно само переживать недоступность зависимости и ретраить - это и есть смена мышления.
  • volumes. Бинд-маунт хоста (./src:/app) в кластере смысла не имеет: нет "хоста", под живет на случайной ноде. Именованные тома становятся PVC, а PVC требует динамического провижининга через StorageClass. Конфиги едут не томом, а через ConfigMap/Secret, смонтированные как файлы.
  • сеть. В Compose сервисы видят друг друга по имени в общей сети. В k8s роль DNS-имени играет Service: обращение к http://db:5432 работает, только если есть Service с именем db. kompose их создает, но проверь, что имена сошлись.
Минимальный ручной аналог для одного сервиса из compose выглядит так:

Код: Выделить всё

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      containers:
        - name: api
          image: registry.example.ru/team/api:1.4.2
          ports: [{ containerPort: 8080 }]
          readinessProbe:
            httpGet: { path: /health, port: 8080 }
            initialDelaySeconds: 3
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector: { app: api }
  ports: [{ port: 80, targetPort: 8080 }]
Сравни с тремя строчками в compose - и поймешь, что k8s многословнее не из вредности, а потому что описывает то, что Compose делал неявно: масштабирование, обнаружение, здоровье.

Локальная разработка: kind и minikube тянут локальные образы

Раз образы в кластер обычно едут через реестр, встает вопрос: как локально гонять только что собранный образ, не пушая его в интернет на каждый чих? Для этого есть kind (Kubernetes in Docker - кластер из контейнеров) и minikube. Оба умеют забирать образ прямо из твоего локального Docker.

Код: Выделить всё

# kind: грузим локально собранный образ в ноды кластера
docker build -t api:dev .
kind load docker-image api:dev

# minikube: аналогично
minikube image load api:dev
И тут главная грабля, на которой теряют по полдня: даже загрузив образ, ты получаешь ErrImagePull. Причина - imagePullPolicy. С тегом :latest политика Always, kubelet игнорирует локальный образ и лезет в реестр, которого нет. Лечится двумя способами: тегируй образ не через :latest (тогда дефолт IfNotPresent и локальный образ подхватится), либо явно ставь imagePullPolicy: Never для уже загруженного образа:

Код: Выделить всё

containers:
  - name: api
    image: api:dev
    imagePullPolicy: Never   # бери только локальный, в реестр не ходи
minikube умеет и красивее: minikube docker-env переключает твой docker CLI на демон внутри minikube, и docker build собирает образ сразу там, где он нужен - грузить ничего не надо. Это самый быстрый dev-цикл.

Что меняется в мышлении

Compose - это "запусти вот эти контейнеры на этой машине". Kubernetes - это "поддерживай вот такое желаемое состояние в кластере, а как и где - твоя забота". Поды эфемерны: их убивают, пересоздают, переносят на другие ноды без спроса. Поэтому никаких локальных файлов на хосте, никакого "контейнер с таким IP", никакого порядка запуска. Состояние - только в PVC и внешних хранилищах. Связь - только через Service по DNS-имени. Готовность - только через пробы, а не через depends_on. Когда это уложится в голове, перенос compose в kubernetes перестает быть переводом строчка-в-строчку и становится переосмыслением: ты описываешь не процесс запуска, а инвариант, который кластер обязан держать.

Типичные грабли и антипаттерны
  • "Надо срочно переезжать с Docker, его выпилили". Нет. Из kubelet убрали dockershim. Образы и docker build живут.
  • Тег :latest в манифестах. Непредсказуемые деплои и невозможность нормального отката. Тегируй по версии/sha.
  • Слепое доверие kompose. Сгенерил, kubectl apply, "работает" - а секреты в открытом env, проб нет, лимитов нет. Это не прод, а демо.
  • Перенос бинд-маунтов хоста в кластер. На случайной ноде твоего ./src нет. Используй ConfigMap/PVC.
  • Расчет на depends_on. Порядка старта в k8s нет. Делай ретраи в приложении и readiness-пробы.
  • Локальный образ без imagePullPolicy: Never (или с тегом :latest) в kind/minikube. ErrImagePull на ровном месте.
Мини-лаба: повтори руками
  • Возьми любой свой compose.yaml с двумя сервисами (например api + db с томом). Прогони kompose convert -f compose.yaml и изучи, во что превратился каждый service, ports и volume.
  • Найди в выводе, где остались переменные окружения с секретами, и мысленно (или руками) вынеси их в объект Secret.
  • Собери образ: docker build -t api:dev . Подними kind-кластер, выполни kind load docker-image api:dev.
  • Напиши минимальный Deployment+Service для api с imagePullPolicy: Never, примени kubectl apply -f, проверь kubectl get pods и kubectl logs. Поймай ErrImagePull, если забыл про политику, и почини.
  • Сравни: что в compose занимало 4 строки, в k8s заняло два объекта. Выпиши для себя, что именно k8s сделал явным.
Контрольные вопросы
  • Что конкретно удалили в Kubernetes 1.24 и почему это не сломало образы, собранные через docker build?
  • Опиши путь образа из docker build на ноутбуке до запущенного пода в кластере. Где тут imagePullSecrets и зачем?
  • Почему depends_on из Compose нельзя перенести в k8s один-в-один и чем его заменяют?
  • Ты загрузил локальный образ в minikube, но получаешь ErrImagePull. Две причины и как починить.
Итог

Связка docker kubernetes - это не конфликт, а разделение труда: Docker (и buildx) собирает OCI-образы, containerd в кластере их запускает, dockershim как лишнюю прослойку убрали - вот и весь сыр-бор. Образы переносятся без правок, а реальная работа при миграции - не образы, а переписывание compose в манифесты: service в Deployment+Service, volumes в PVC, depends_on в пробы и ретраи. kompose снимет 70-80 процентов рутины, остальное - руками и головой. Дальше - отдельный курс Kubernetes, где разберем поды, контроллеры, сеть и хранилища вглубь. Этот урок - мост: ты уже понимаешь, как твой контейнерный мир стыкуется с оркестрацией.
👍2 ❤️ 🔥1 😄 🤔1
Аватара пользователя
m4ttendo
Сообщения: 1
Зарегистрирован: 30 май 2026, 11:44

Re: Docker и Kubernetes: containerd, миграция, kompose

Сообщение m4ttendo »

Полгода назад чуть не запаниковал из-за 'kubernetes убрал docker', а оказалось просто шим выкинули. Спасибо что разжевали разницу между образом и рантаймом, наконец отпустило.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
Nosliw
Сообщения: 1
Зарегистрирован: 28 май 2026, 23:02

Re: Docker и Kubernetes: containerd, миграция, kompose

Сообщение Nosliw »

Прогнал kompose на своем compose - реально секреты остались в открытом env и проб нет вообще. Без этого урока залил бы как есть в прод. Вопрос: а Secret кто-то генерит автоматом или всегда руками выносить?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Контейнеризация приложений: бэкенд, фронтенд, БД
Следующая глава →
Лучшие практики и антипаттерны Docker

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: podman vs docker что выбрать в продеdocker swarm встроенная оркестрация или kubernetesminikube или kind что выбрать для локального кластера kuberneteskubectl get describe logs основные команды для работы с кластеромчем отличается deployment от pod в kubernetes простыми словамичто такое service в kubernetes и как поды находят друг друга

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость