Initial commit: interview prep repo (resume, legend, stack cases, Kubernetes course)

This commit is contained in:
Tot Maxim
2026-07-18 00:37:41 +03:00
commit 9bb7f6263f
47 changed files with 3548 additions and 0 deletions

118
stack/docker/CASE.md Normal file
View File

@@ -0,0 +1,118 @@
# Кейс: 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 <compose-project>_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 <container> --format '{{.State.ExitCode}}'`.
### 5. Диагностика упавшего контейнера
```bash
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`.
```bash
docker inspect <container> --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 <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.

58
stack/docker/QUESTIONS.md Normal file
View File

@@ -0,0 +1,58 @@
# Вопросы: Docker
Опираются на кейс: [CASE.md](CASE.md). Источники — [VK Cloud.md](../../interview/VK%20Cloud.md), [Реалист банк.md](../../interview/Реалист%20банк.md).
### В чём разница между образом (image) и контейнером?
Образ — неизменяемый шаблон: слоёная файловая система + метаданные (какая команда запускается по умолчанию, какие переменные окружения и т.д.), результат `docker build`. Контейнер — запущенный (или остановленный) экземпляр образа с добавленным поверх write-слоем, где живут любые runtime-изменения — по сути процесс на хосте плюс собственная изолированная файловая система, сеть и т.д. Один образ может породить много независимых контейнеров.
### В чём отличия виртуализации от контейнеризации?
Виртуализация эмулирует отдельное «железо» через гипервизор — у каждой VM своё полноценное ядро ОС, изоляция максимальная, но накладные расходы выше (полный загруженный образ ОС, медленнее старт). Контейнеризация — все контейнеры хоста делят одно и то же ядро хоста; изоляция достигается через namespaces (что процесс видит — свои PID, сеть, файловую систему) и ограничение ресурсов через cgroups (сколько он может использовать), см. [../linux-bash/QUESTIONS.md](../linux-bash/QUESTIONS.md). Контейнеры легче и стартуют за секунды, но изоляция менее строгая (общее ядро — потенциальная поверхность атаки при уязвимостях в ядре).
### Как Docker понимает, что контейнер умер, и помечает его как exit?
Внутри Docker Engine работает `containerd` — реальный рантайм, управляющий процессами контейнеров. Он отслеживает главный процесс контейнера (PID 1 внутри контейнерного namespace); как только этот процесс завершается (штатно или с ошибкой), это фиксируется как событие завершения, и статус контейнера меняется на `Exited` с соответствующим exit-кодом (`docker inspect --format '{{.State.ExitCode}}'`).
### Что такое Docker runtime и зачем нужен containerd?
Runtime — компонент, который непосредственно создаёт и управляет изолированными процессами контейнеров на уровне ОС (namespaces, cgroups, файловая система из слоёв образа). `containerd` — низкоуровневый рантайм, на котором построен сам Docker Engine: Docker CLI/демон — это удобная надстройка (сборка образов, compose, registry-взаимодействие) поверх `containerd`, который непосредственно исполняет работу по управлению жизненным циклом контейнеров. Это разделение позволяет другим системам (например, Kubernetes через CRI) использовать `containerd` напрямую, без полного Docker Engine.
### Как зайти внутрь контейнера?
```bash
docker exec -it <container> sh # или bash, если есть в образе
```
`exec` запускает новый процесс внутри уже работающего контейнера (в отличие от `docker run`, который создаёт новый контейнер). Если контейнер уже остановлен, `exec` не сработает — нужно либо запустить его заново, либо использовать `docker cp` для файлов без запуска процесса внутри.
### Как скопировать файл в работающий контейнер?
```bash
docker cp ./local-file.txt <container>:/path/in/container
docker cp <container>:/path/in/container ./local-file.txt # и в обратную сторону, из контейнера на хост
```
### Если убить процесс Docker (сам демон) — запущенные контейнеры сдохнут?
Нет, не обязательно — сами контейнеры управляются `containerd`, а не напрямую демоном `dockerd`; при перезапуске `dockerd` уже запущенные контейнеры продолжают работать (это осознанное архитектурное решение, начиная с версии Docker, где Docker Engine был разделён с `containerd` — раньше, в старых версиях, где всё было завязано на монолитный демон, перезапуск демона действительно ронял контейнеры). После рестарта `dockerd` он заново подключается к уже работающим контейнерам через `containerd` и восстанавливает их видимость в `docker ps`.
### Как работает сеть в Docker?
По умолчанию Docker создаёт bridge-сеть (`docker0` для отдельных контейнеров, отдельная сеть на проект в Docker Compose), контейнеры получают виртуальные сетевые интерфейсы, подключённые к этому мосту, и приватные IP внутри него. Docker поднимает встроенный DNS внутри пользовательских (non-default) bridge-сетей — контейнеры резолвят друг друга по имени сервиса/контейнера, а не по IP. Наружу трафик пробрасывается через `-p host_port:container_port`, что настраивает NAT-правила (iptables) для перенаправления с порта хоста на IP контейнера внутри моста.
### Много ли процессов внутри одного контейнера? У контейнеров есть PID?
Технически контейнер может запускать сколько угодно процессов (например, если ENTRYPOINT — скрипт, который стартует несколько дочерних процессов), но общепринятая практика — один основной процесс на контейнер (принцип "один контейнер — одна ответственность"), чтобы жизненный цикл контейнера чётко соответствовал жизненному циклу этого процесса. У контейнера есть PID — как у любого обычного процесса хоста (см. `docker inspect --format '{{.State.Pid}}'` в кейсе), плюс внутри контейнера, благодаря PID namespace, главный процесс видит себя как PID 1 в своём изолированном пространстве.
### Entrypoint vs CMD — в чём разница?
`ENTRYPOINT` — фиксированная главная команда контейнера, `CMD` — аргументы к ней по умолчанию, которые можно переопределить, просто передав другие аргументы в `docker run image <новые аргументы>`. Если задан только `CMD` без `ENTRYPOINT`, вся команда целиком переопределяется тем, что передано в `docker run`. Практическая польза разделения — можно сделать образ, у которого фиксирована сама программа (`ENTRYPOINT ["myapp"]`), но легко менять флаги запуска без пересборки образа.
### Разница docker ps, docker ps -a, docker logs, docker images?
`docker ps` — только работающие контейнеры. `docker ps -a` — вообще все контейнеры, включая остановленные/завершившиеся. `docker logs <container>` — стандартный вывод (stdout/stderr) процесса внутри контейнера, накопленный с момента старта (`-f` — читать в реальном времени, как `tail -f`). `docker images` — список локально скачанных/собранных образов (в отличие от контейнеров — это шаблоны, а не запущенные экземпляры).
### Как сохранить логи из контейнера?
`docker logs <container> > output.log` — перенаправить stdout вывод команды в файл на хосте. Если логи пишутся не в stdout, а в файл внутри контейнера — `docker cp <container>:/path/to/log.file ./log.file`. Для постоянного сохранения логов за пределами жизни контейнера в проде обычно настраивают logging driver (например, `json-file` с ротацией, или отправку в централизованную систему — см. [../elk/CASE.md](../elk/CASE.md)) вместо ручного копирования.