# Кейс: Docker Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «АО ТНИИС», контейнеризация сервисов мониторинга и вспомогательных инструментов. Практика опирается на уже готовые стенды [../monitoring/CASE.md](../monitoring/CASE.md) и [../nginx/CASE.md](../nginx/CASE.md) — оба используют Docker Compose. ## Что нужно реально сделать ### 1. Dockerfile для собственного простого сервиса (multi-stage build) ```dockerfile # build stage FROM golang:1.22-alpine AS build WORKDIR /src COPY . . RUN go build -o /app ./... # итоговый образ — только бинарник, без всей тулчейна сборки FROM alpine:3.19 COPY --from=build /app /app ENTRYPOINT ["/app"] ``` ``` # .dockerignore .git *.md node_modules ``` Multi-stage build: первый этап содержит весь тяжёлый тулчейн сборки (компилятор, зависимости), второй — только финальный артефакт. Итоговый образ в разы меньше и не тащит в прод лишние инструменты сборки. Слои кешируются построчно — `COPY . .` перед `RUN build` означает, что при изменении любого файла кеш слоя сборки инвалидируется; на реальных проектах порядок команд специально выстраивают так, чтобы редко меняющиеся зависимости копировались раньше исходного кода. ### 2. Volumes: bind mount vs named volume ```bash # bind mount — конкретный путь на хосте, удобно для конфигов и разработки docker run -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro nginx # named volume — управляется самим Docker, удобно для данных (БД, персистентное состояние) docker volume create pgdata docker run -v pgdata:/var/lib/postgresql/data postgres ``` Bind mount используется в кейсах [../monitoring/CASE.md](../monitoring/CASE.md) и [../nginx/CASE.md](../nginx/CASE.md) для конфигов (`prometheus.yml`, `nginx.conf`) — удобно редактировать файл на хосте и сразу видеть изменения в контейнере. Named volume лучше подходит для данных, которые не нужно редактировать руками с хоста и которые должны переживать пересоздание контейнера (в [../databases/CASE.md](../databases/CASE.md) для этого пригодился бы именно named volume под `/var/lib/postgresql/data`). ### 3. Сети Docker: как контейнеры находят друг друга ```bash docker network ls docker network inspect _default ``` В Docker Compose по умолчанию создаётся отдельная bridge-сеть на проект, и все сервисы внутри неё резолвят друг друга по имени сервиса через встроенный DNS Docker — именно поэтому в [../monitoring/CASE.md](../monitoring/CASE.md) Prometheus обращается к `node-exporter:9100`, а не по IP: имя сервиса из `docker-compose.yml` работает как hostname внутри этой сети. ### 4. Ограничение ресурсов и что происходит при превышении ```yaml services: app: image: myapp deploy: resources: limits: cpus: "0.5" memory: 256M ``` ```bash docker run --memory=256m --cpus=0.5 myapp ``` При превышении лимита памяти ядро (через cgroups, см. [../linux-bash/QUESTIONS.md](../linux-bash/QUESTIONS.md)) убивает процесс через OOM killer — контейнер завершается с кодом 137 (128 + сигнал 9 SIGKILL). Воспроизвести: запустить в контейнере с `--memory=50m` процесс, который выделяет память сверх лимита (`stress --vm 1 --vm-bytes 100M`), и увидеть код выхода 137 через `docker inspect --format '{{.State.ExitCode}}'`. ### 5. Диагностика упавшего контейнера ```bash docker logs docker logs --tail 50 -f docker exec -it sh docker inspect docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}}' ``` ### 6. Внутренности: виртуализация vs контейнеризация, containerd, PID контейнера Виртуализация (VM) — эмулирует полное отдельное «железо» с собственным ядром ОС через гипервизор (VMware/KVM/Hyper-V) — тяжелее, но полная изоляция вплоть до ядра. Контейнеризация — все контейнеры на хосте используют одно и то же ядро хоста, изоляция обеспечивается namespaces (что процесс видит) и ограничение ресурсов — cgroups (сколько он может использовать), без отдельного гостевого ядра — отсюда контейнеры легче и стартуют быстрее VM. `containerd` — низкоуровневый контейнерный рантайм, которым Docker Engine пользуется под капотом для реального управления жизненным циклом контейнеров (запуск, остановка, управление образами) — сам Docker CLI/демон — более высокоуровневая обвязка поверх containerd с удобным UX (`docker build`, `docker compose` и т.п.). Docker понимает, что контейнер «умер», потому что containerd отслеживает главный процесс контейнера (PID 1 внутри контейнера) — как только этот процесс завершается (сам или из-за ошибки), containerd фиксирует это событие и помечает контейнер как `Exited`. ```bash docker inspect --format '{{.State.Pid}}' # PID процесса контейнера на хосте ps aux | grep <тот же PID> # виден и на хосте — контейнер это просто изолированный процесс хоста ``` ### 7. Entrypoint vs CMD ```dockerfile ENTRYPOINT ["myapp"] CMD ["--config", "/etc/myapp/default.yml"] ``` `ENTRYPOINT` задаёт неизменяемую (без явного `--entrypoint` при запуске) главную команду контейнера. `CMD` задаёт аргументы по умолчанию к ней, которые легко переопределить при `docker run myapp --config /other.yml` — эта строка заменит именно `CMD`, не трогая `ENTRYPOINT`. Если задан только `CMD` без `ENTRYPOINT` — вся строка `CMD` целиком заменяется аргументами `docker run`. ### 8. Копирование файлов в работающий контейнер ```bash docker cp ./local-file.txt :/app/file.txt docker cp :/app/output.log ./output.log ``` ## Что это даёт в разговоре с интервьюером - Понимание, что контейнер технически — обычный процесс хоста с изоляцией через namespaces/cgroups, а не мини-VM. - Практика multi-stage build и осознанного слоёного кеширования, а не просто «Dockerfile работает». - Знание разницы bind mount/named volume не абстрактно, а с привязкой к тому, где какой тип реально применён в других кейсах репозитория. - Воспроизведённый вживую OOM kill (код 137) — понимание на практике, а не только в теории. ## Как это ложится в легенду В реальной работе (АО ТНИИС) Docker упоминается в стеке отдела как средство контейнеризации сервисов сопровождения. Этот кейс показывает собственный опыт сборки, сетевого взаимодействия и диагностики контейнеров поверх уже готовых стендов мониторинга и nginx.