9.1 KiB
Кейс: Docker
Связь с легендой: ../../legend/LEGEND.md — раздел «АО ТНИИС», контейнеризация сервисов мониторинга и вспомогательных инструментов. Практика опирается на уже готовые стенды ../monitoring/CASE.md и ../nginx/CASE.md — оба используют Docker Compose.
Что нужно реально сделать
1. Dockerfile для собственного простого сервиса (multi-stage build)
# 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
# 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 и ../nginx/CASE.md для конфигов (prometheus.yml, nginx.conf) — удобно редактировать файл на хосте и сразу видеть изменения в контейнере. Named volume лучше подходит для данных, которые не нужно редактировать руками с хоста и которые должны переживать пересоздание контейнера (в ../databases/CASE.md для этого пригодился бы именно named volume под /var/lib/postgresql/data).
3. Сети Docker: как контейнеры находят друг друга
docker network ls
docker network inspect <compose-project>_default
В Docker Compose по умолчанию создаётся отдельная bridge-сеть на проект, и все сервисы внутри неё резолвят друг друга по имени сервиса через встроенный DNS Docker — именно поэтому в ../monitoring/CASE.md Prometheus обращается к node-exporter:9100, а не по IP: имя сервиса из docker-compose.yml работает как hostname внутри этой сети.
4. Ограничение ресурсов и что происходит при превышении
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
docker run --memory=256m --cpus=0.5 myapp
При превышении лимита памяти ядро (через cgroups, см. ../linux-bash/QUESTIONS.md) убивает процесс через OOM killer — контейнер завершается с кодом 137 (128 + сигнал 9 SIGKILL). Воспроизвести: запустить в контейнере с --memory=50m процесс, который выделяет память сверх лимита (stress --vm 1 --vm-bytes 100M), и увидеть код выхода 137 через docker inspect <container> --format '{{.State.ExitCode}}'.
5. Диагностика упавшего контейнера
docker logs <container>
docker logs --tail 50 -f <container>
docker exec -it <container> sh
docker inspect <container>
docker inspect <container> --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.
docker inspect <container> --format '{{.State.Pid}}' # PID процесса контейнера на хосте
ps aux | grep <тот же PID> # виден и на хосте — контейнер это просто изолированный процесс хоста
7. Entrypoint vs CMD
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. Копирование файлов в работающий контейнер
docker cp ./local-file.txt <container>:/app/file.txt
docker cp <container>:/app/output.log ./output.log
Что это даёт в разговоре с интервьюером
- Понимание, что контейнер технически — обычный процесс хоста с изоляцией через namespaces/cgroups, а не мини-VM.
- Практика multi-stage build и осознанного слоёного кеширования, а не просто «Dockerfile работает».
- Знание разницы bind mount/named volume не абстрактно, а с привязкой к тому, где какой тип реально применён в других кейсах репозитория.
- Воспроизведённый вживую OOM kill (код 137) — понимание на практике, а не только в теории.
Как это ложится в легенду
В реальной работе (АО ТНИИС) Docker упоминается в стеке отдела как средство контейнеризации сервисов сопровождения. Этот кейс показывает собственный опыт сборки, сетевого взаимодействия и диагностики контейнеров поверх уже готовых стендов мониторинга и nginx.