Образ — снимок файлов и настроек. Контейнер — запущенный процесс из этого снимка. В прод ставят digest, не latest. PID 1 должен принимать SIGTERM, иначе приложение убивают посреди запроса.
Контейнеры и оркестрация docker собеседование
Образ Docker — готовый снимок: файлы слоями, манифест, переменные, команда по умолчанию. Его не запускают. Из образа стартуют контейнер.
Контейнер — обычный Linux-процесс с изоляцией: свои процессы, сеть, лимиты CPU и памяти. Сверху — тонкий слой, куда можно писать. Это не виртуальная машина. Своего ядра внутри нет.
В бэкенде образ собирают в CI. Потом его тянут на сервер. Если перепутать образ и контейнер, тег и digest, PID 1 и «просто процесс» — выкат и остановка сервиса ломаются.
Пример. Один образ api:1.2 лежит в registry. Из него можно запустить три контейнера на трёх машинах. Образ один. Процессов — три.
Слои и кэш
Dockerfile читается сверху вниз. Почти каждая инструкция даёт слой. Слои кэшируются. Изменили строку — кэш с этой строки и ниже сбрасывается.
Редко меняющееся кладут выше: зависимости, пакеты ОС. Исходники копируют ниже. Тогда правка кода не качает зависимости заново. Маленький базовый образ — меньше дыр и быстрее pull.
Удалили файл в следующем RUN — в истории образа он остался. Слой нельзя «подчистить». Секрет, попавший в слой, уедет вместе с docker pull.
Пример. Скопировали ключ, потом RUN rm /key. В текущем слое файла нет. В предыдущем — есть. docker history это покажет.
FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/web
FROM gcr.io/distroless/static:nonroot
COPY --from=build /out/app /app
USER nonroot
ENTRYPOINT ["/app"]
Multi-stage: в финальный образ попадает только бинарь, без компилятора.
PID 1 и сигналы
Главный процесс контейнера получает номер 1. Это PID 1. Когда контейнер останавливают, ядро шлёт SIGTERM именно ему.
Если PID 1 — оболочка без exec, сигнал часто глотается. Приложение не узнаёт. Через несколько секунд прилетает SIGKILL. Соединения рвутся посреди запроса.
Лечение простое: сам бинарь как PID 1 (типичный Go) или exec в entrypoint. Проверка: docker stop даёт процессу время закрыться, а не убивает сразу.
Пример. Kubernetes шлёт SIGTERM и ждёт terminationGracePeriodSeconds. Если PID 1 не слушает сигнал, приложение «внезапно» падает на каждом выкате.
Тег против digest
Тег — имя. app:1.2 сегодня одни байты, завтра другие с тем же именем. latest плавает ещё сильнее.
Digest — отпечаток конкретных слоёв: app@sha256:abc…. Это те же байты, что собрали и просканировали. Staging и prod тянут этот digest, не «свежий latest».
Пример. Собрали образ, прогнали тесты. В прод ставят registry/api@sha256:abc. Не api:latest. Иначе вы не знаете, что выкатили, и не откатитесь к тем же байтам.
Типичная ошибка
Ключ в слой «на минуту» и RUN rm. История хранит слой целиком. Вторая ошибка — ENTRYPOINT через shell без exec: SIGTERM не доходит до приложения.
На собеседовании
Образ — снимок, контейнер — процесс. PID 1 принимает SIGTERM. В прод — digest, не latest. Секрет в слое остаётся в истории.