Стройте ответ вокруг владения состоянием, лимитов, отказа и восстановления. Определение становится инженерным ответом только в конкретном production-сценарии.
Вопросы и ответы
35 подробных ответов
01Что такое Docker и какую проблему он решает?
junior
Короткий ответ: Docker — это платформа контейнеризации, которая упаковывает приложение вместе со всеми его зависимостями, библиотеками и конфигурацией в изолированный, переносимый образ. Решает проблему «у меня на машине работает, а на сервере нет».
Подробно:
Классическая боль: разработчик написал код на своём ноутбуке (Python 3.11, конкретная версия libpq, переменные окружения), а на сервере другая версия ОС, других библиотек нет, переменные не выставлены — приложение падает. Docker фиксирует всё окружение в образе, и этот образ одинаково запускается локально, в CI и на проде.
Что даёт Docker:
- Воспроизводимость — один и тот же образ везде. Окружение описано кодом (Dockerfile), его можно версионировать в git.
- Изоляция — контейнеры не мешают друг другу (зависимости, порты, процессы).
- Переносимость — образ работает на любой машине с Docker, независимо от хостовой ОС.
- Быстрый старт — контейнер поднимается за секунды (в отличие от VM).
# Собрать образ из Dockerfile в текущей директории
docker build -t myapp:1.0 .
# Запустить контейнер из образа, пробросить порт, в фоне
docker run -d -p 8000:8000 --name myapp myapp:1.0
# Посмотреть запущенные контейнеры
docker ps
# Зайти внутрь работающего контейнера
docker exec -it myapp bash
# Логи контейнера
docker logs -f myapp
⚠️ Ловушка: Docker не «виртуализирует железо» и не запускает полноценную ОС. Контейнеры используют ядро хоста. Поэтому Linux-образ нативно не запустится на ядре Windows — на Windows/macOS под капотом работает лёгкая Linux-VM (через WSL2 или гипервизор), и уже в ней крутятся контейнеры.
02Контейнер vs виртуальная машина
junior
Короткий ответ: VM виртуализирует железо и тащит за собой полноценную гостевую ОС со своим ядром. Контейнер виртуализирует ОС и разделяет ядро хоста, изолируясь только на уровне процессов. Контейнер легче, быстрее и меньше.
Подробно:
Виртуальная машина: Контейнер:
┌───────────────────────┐ ┌───────────────────────┐
│ App A │ App B │ │ App A │ App B │
│ Bins/Libs│ Bins/Libs │ │ Bins/Libs│ Bins/Libs │
│ Guest OS │ Guest OS │ ├──────────┴─────────────┤
│ (ядро) │ (ядро) │ │ Docker Engine │
├───────────┴────────────┤ ├────────────────────────┤
│ Hypervisor │ │ Host OS (общее ядро) │
├────────────────────────┤ ├────────────────────────┤
│ Host OS / железо │ │ Железо │
└────────────────────────┘ └────────────────────────┘
| Критерий | VM | Контейнер |
|---|---|---|
| Ядро | своё, гостевое | общее с хостом |
| Размер | гигабайты | мегабайты |
| Старт | минуты | секунды |
| Изоляция | сильная (на уровне железа) | слабее (на уровне процессов/ядра) |
| Накладные расходы | высокие | низкие |
Изоляция контейнеров строится на механизмах ядра Linux:
- namespaces — изоляция «что контейнер видит»: процессы (PID), сеть, точки монтирования, пользователи, hostname.
- cgroups (control groups) — ограничение «сколько контейнер может потребить»: CPU, память, IO.
# Ограничить контейнер по ресурсам через cgroups
docker run --memory=512m --cpus=1.5 myapp:1.0
⚠️ Ловушка: «слабее изоляция» — это реально важно для безопасности. Уязвимость в ядре может затронуть все контейнеры на хосте (escape). Поэтому не запускают доверенный и недоверенный код в контейнерах на одном ядре без дополнительных мер (gVisor, отдельные VM). И root внутри контейнера — это (по умолчанию) root на хосте при монтировании, отсюда правило не запускать процессы от root.
03Image vs container
junior
Короткий ответ: Image (образ) — это неизменяемый шаблон, «снимок» файловой системы с приложением. Container (контейнер) — это запущенный экземпляр образа, процесс с тонким записываемым слоем поверх образа. Аналогия: образ — класс, контейнер — объект.
Подробно:
- Образ read-only, состоит из слоёв. Один образ — много контейнеров.
- Контейнер — это образ + тонкий writable-слой сверху. Все изменения в файловой системе во время работы пишутся в этот слой. При удалении контейнера слой пропадает (если данные не вынесены в volume).
docker images # список образов
docker ps -a # все контейнеры (включая остановленные)
docker run myapp # создать и запустить контейнер
docker stop <id> # остановить
docker start <id> # запустить снова (тот же контейнер, тот же writable-слой)
docker rm <id> # удалить контейнер (writable-слой удаляется)
docker rmi myapp # удалить образ
⚠️ Ловушка: данные, записанные внутрь контейнера (не в volume), исчезают при docker rm. Частая ошибка джуна — положить базу данных в файловую систему контейнера и удивляться, что после пересоздания всё пропало. Контейнеры эфемерны.
04Слои образа (layers) и кэширование
middle
Короткий ответ: Образ собирается из слоёв — каждая инструкция в Dockerfile (FROM, RUN, COPY...) создаёт новый слой. Docker кэширует слои: при пересборке неизменившиеся слои берутся из кэша. Поэтому порядок инструкций критичен — нужно ставить редко меняющиеся слои выше часто меняющихся.
Подробно:
Кэш слоя инвалидируется, как только меняется инструкция или её вход (например, файл, который копируется через COPY). Все последующие слои тоже пересобираются.
Плохо — любое изменение кода ломает кэш установки зависимостей:
FROM python:3.12-slim
WORKDIR /app
COPY . . # код меняется часто → слой инвалидируется
RUN pip install -r requirements.txt # зависимости переустанавливаются каждый раз 😱
Хорошо — сначала зависимости, потом код:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt . # меняется редко
RUN pip install -r requirements.txt # кэшируется, пока requirements.txt не изменился ✅
COPY . . # код меняется часто — но это последний слой
Тот же приём для Node:
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
⚠️ Ловушка: объединяй связанные команды в одном RUN через &&, иначе apt-get update в одном слое и apt-get install в другом могут разойтись (кэшированный update + свежий install → устаревшие индексы пакетов). И каждый лишний слой увеличивает образ.
05Основные инструкции Dockerfile
junior
Короткий ответ: FROM — базовый образ, WORKDIR — рабочая директория, COPY/ADD — копирование файлов, RUN — выполнить команду при сборке, ENV — переменные окружения, ARG — аргументы сборки, EXPOSE — задокументировать порт, CMD/ENTRYPOINT — команда запуска контейнера.
Подробно:
# Базовый образ (обязательно, обычно первая инструкция)
FROM python:3.12-slim
# Метаданные
LABEL maintainer="team@example.com"
# Аргумент сборки (доступен только при build)
ARG APP_VERSION=1.0.0
# Переменная окружения (доступна и при сборке, и в рантайме контейнера)
ENV PYTHONUNBUFFERED=1 \
APP_HOME=/app
# Рабочая директория (создаётся, если нет; все следующие команды относительно неё)
WORKDIR $APP_HOME
# Копирование файлов с хоста в образ
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Создать непривилегированного пользователя и переключиться на него
RUN useradd --create-home appuser
USER appuser
# Задокументировать порт (само по себе порт НЕ публикует!)
EXPOSE 8000
# Команда запуска
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
Best practices:
- Конкретный тег базового образа (
python:3.12-slim, неpython:latest). .dockerignore, чтобы не тащить.git,node_modules,__pycache__.- Минимум слоёв, объединять
RUN. - Не запускать от root (
USER). - Файлы зависимостей копировать и устанавливать отдельным слоем до исходников: тогда изменение приложения переиспользует закэшированный слой зависимостей и не запускает дорогую установку заново.
⚠️ Ловушка: EXPOSE 8000 ничего не публикует наружу — это только документация/метаданные. Чтобы порт был доступен с хоста, нужен docker run -p 8000:8000. Также WORKDIR стоит использовать вместо RUN cd /app — cd действует только в рамках одного RUN.
06COPY vs ADD
middle
Короткий ответ: COPY просто копирует файлы/директории с хоста в образ. ADD умеет то же плюс распаковывать локальные tar-архивы и скачивать файлы по URL. Рекомендация: всегда используй COPY, а ADD — только для распаковки tar.
Подробно:
COPY ./src /app/src # просто копирование — предсказуемо
ADD app.tar.gz /app/ # автоматически распакует архив в /app/
ADD https://example.com/f.txt / # скачает по URL (но лучше curl/wget в RUN)
ADD с URL — антипаттерн: не кэширует адекватно, не проверяет контрольные суммы, и слой раздувается. Для скачивания лучше:
RUN curl -fsSL https://example.com/f.txt -o /app/f.txt
⚠️ Ловушка: ADD с tar-архивом «волшебно» распаковывает его, и это может удивить читателя Dockerfile. Принцип наименьшего удивления — используй явный COPY, а распаковку делай отдельной командой, если она не нужна неявно.
07CMD vs ENTRYPOINT
middle
Короткий ответ: ENTRYPOINT задаёт сам исполняемый файл/команду, которую контейнер «есть всегда». CMD задаёт аргументы по умолчанию (или команду целиком), которые легко переопределить в docker run. Часто используют вместе: ENTRYPOINT — программа, CMD — аргументы.
Подробно:
Если задан только CMD, его можно полностью заменить в docker run:
CMD ["python", "app.py"]
docker run myapp # → python app.py
docker run myapp python other.py # → python other.py (CMD заменён целиком)
Если задан ENTRYPOINT, аргументы из docker run дописываются к нему:
ENTRYPOINT ["python"]
CMD ["app.py"]
docker run myapp # → python app.py
docker run myapp other.py # → python other.py (заменилась только CMD-часть)
Exec-форма vs shell-форма (важно!):
CMD ["python", "app.py"] # exec-форма: PID 1 = python, сигналы доходят ✅
CMD python app.py # shell-форма: PID 1 = /bin/sh -c, python — дочерний ⚠️
Exec-форма (JSON-массив) — предпочтительна, потому что приложение становится PID 1 и корректно получает сигналы (SIGTERM при docker stop). В shell-форме сигналы получает shell, и приложение может не остановиться gracefully.
⚠️ Ловушка: в shell-форме приложение не получит SIGTERM, и docker stop будет ждать 10 секунд, а потом убьёт SIGKILL (потеря данных, отсутствие graceful shutdown). Всегда предпочитай exec-форму.
08ARG vs ENV
middle
Короткий ответ: ARG — переменная времени сборки, доступна только в Dockerfile при docker build и не сохраняется в образе. ENV — переменная окружения, доступна и при сборке, и в рантайме внутри контейнера, и сохраняется в образе.
Подробно:
ARG NODE_VERSION=20 # значение по умолчанию, переопределяется при build
FROM node:${NODE_VERSION}
ARG BUILD_ENV # без значения → передаётся через --build-arg
ENV APP_ENV=production # останется в образе и будет видна процессу
docker build --build-arg BUILD_ENV=staging -t myapp .
docker run -e APP_ENV=dev myapp # ENV можно переопределить при run
⚠️ Ловушка: не передавай секреты через ARG — даже если значение нигде не сохраняется в ENV, оно может остаться в истории слоёв и в docker history. Для секретов при сборке используй --secret (BuildKit), а в рантайме — переменные окружения через -e/--env-file или менеджер секретов.
09Multi-stage builds
middle
Короткий ответ: Multi-stage build — несколько стадий FROM в одном Dockerfile, где в финальный образ копируется только результат сборки, а тяжёлые инструменты сборки (компиляторы, dev-зависимости) остаются в промежуточных стадиях. Главная цель — резко уменьшить размер и поверхность атаки итогового образа.
Подробно:
Без multi-stage образ Go-приложения тащит весь компилятор Go (~300+ МБ). С multi-stage — только бинарник (несколько МБ).
# --- Стадия сборки ---
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server ./cmd/server
# --- Финальная стадия ---
FROM alpine:3.20
COPY --from=builder /app/server /usr/local/bin/server
ENTRYPOINT ["server"]
Для фронтенда (собрать статику, отдавать через nginx):
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
⚠️ Ловушка: в финальный образ попадает только то, что ты явно скопировал через COPY --from=.... Если забыть скопировать рантайм-зависимость (например, сертификаты CA, нужные для HTTPS-запросов в scratch/alpine) — приложение упадёт в проде, а не на сборке.
10Как уменьшить размер образа
middle
Короткий ответ: Использовать лёгкие базовые образы (alpine, slim, distroless), multi-stage builds, .dockerignore, объединять RUN и чистить кэш пакетных менеджеров в том же слое, не ставить dev-зависимости в прод.
Подробно:
FROM python:3.12-slim # slim вместо полного (~120 МБ vs ~1 ГБ)
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc \
&& pip install --no-cache-dir -r requirements.txt \
&& apt-get purge -y gcc \
&& rm -rf /var/lib/apt/lists/* # чистим в ТОМ ЖЕ слое, иначе вес останется
.dockerignore — не тащить лишнее в контекст сборки:
.git
node_modules
__pycache__
*.log
.env
dist
.venv
Сравнение баз:
alpine— ~5 МБ, musl libc (иногда несовместимость с библиотеками на glibc).slim— урезанный Debian, хороший баланс совместимости и размера.distroless— только рантайм без shell и пакетного менеджера (максимальная безопасность).
⚠️ Ловушка: удаление файлов в следующем слое не уменьшает образ — слой-предшественник уже зафиксировал их вес. Чистить надо в том же RUN. Также alpine использует musl вместо glibc: бинарные python-колёса (manylinux) могут не работать, придётся компилировать → может оказаться медленнее и больнее, чем slim.
11Volumes vs bind mounts
middle
Короткий ответ: И то, и другое — способ сохранять данные вне эфемерного слоя контейнера. Volume управляется Docker и хранится в его области (/var/lib/docker/volumes) — для продакшен-данных (БД). Bind mount монтирует конкретную папку с хоста в контейнер — удобно для разработки (живой код).
Подробно:
# Named volume — Docker сам управляет хранилищем
docker volume create pgdata
docker run -v pgdata:/var/lib/postgresql/data postgres:16
# Bind mount — конкретный путь хоста → путь в контейнере
docker run -v $(pwd)/src:/app/src myapp # код с хоста виден внутри (hot reload)
# tmpfs — в оперативке, не пишется на диск (секреты, временные данные)
docker run --tmpfs /tmp myapp
В compose:
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data # named volume
web:
build: .
volumes:
- ./src:/app/src # bind mount для разработки
volumes:
pgdata:
| Volume | Bind mount | |
|---|---|---|
| Управление | Docker | вручную (путь хоста) |
| Переносимость | высокая | привязан к структуре хоста |
| Применение | прод, данные БД | разработка, конфиги |
⚠️ Ловушка: bind mount «накрывает» содержимое целевой папки контейнера. Если смонтировать пустую папку хоста в /app/node_modules, установленные в образе модули исчезнут. Решение — анонимный volume для node_modules поверх bind mount кода. Также bind mount привязывает к путям хоста и может ломать переносимость и права (UID/GID).
12Docker networking: bridge, host, порты
middle
Короткий ответ: По умолчанию контейнеры подключены к сети bridge — у каждого свой внутренний IP, наружу порты пробрасываются через -p host:container. В пользовательской bridge-сети контейнеры видят друг друга по имени сервиса (встроенный DNS). Сеть host убирает изоляцию — контейнер использует сеть хоста напрямую.
Подробно:
# Проброс порта: хост:8080 → контейнер:80
docker run -p 8080:80 nginx
# Пользовательская сеть → контейнеры резолвят друг друга по имени
docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet myapi # внутри api: host "db" резолвится в IP db
# host-сеть (только Linux): без изоляции сети, без -p
docker run --network host nginx
Типы драйверов:
- bridge (по умолчанию) — изолированная виртуальная сеть, NAT наружу.
- host — общая сеть с хостом, нет проброса портов, выше производительность, ниже изоляция.
- none — без сети.
- overlay — для нескольких хостов (Swarm/k8s).
⚠️ Ловушка: в дефолтной bridge-сети DNS по именам контейнеров не работает — резолв по имени есть только в пользовательской сети (docker network create). Поэтому compose автоматически создаёт свою сеть, и сервисы видят друг друга по именам. Ещё: внутри контейнера localhost — это сам контейнер, а не хост; чтобы достучаться до хоста, на Mac/Windows используют host.docker.internal.
13Что такое docker-compose?
junior
Короткий ответ: docker-compose — инструмент для описания и запуска многоконтейнерных приложений одним YAML-файлом. Вместо длинных docker run команд для каждого сервиса описываешь все сервисы, сети, тома и зависимости декларативно, и поднимаешь всё одной командой docker compose up.
Подробно:
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/app
depends_on:
db:
condition: service_healthy
networks:
- appnet
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
retries: 5
networks:
- appnet
volumes:
pgdata:
networks:
appnet:
docker compose up -d # поднять всё в фоне
docker compose ps # статус сервисов
docker compose logs -f web # логи сервиса
docker compose down # остановить и удалить (тома сохраняются)
docker compose down -v # + удалить тома
⚠️ Ловушка: depends_on без condition: service_healthy гарантирует только порядок запуска, но не готовность сервиса. Контейнер БД «запущен», но Postgres внутри ещё инициализируется — приложение упадёт при подключении. Нужны healthcheck'и или retry-логика в приложении. Compose хорош для локальной разработки и небольших стендов, для прод-оркестрации с масштабированием берут Kubernetes.
14Переменные окружения и секреты в Docker
middle
Короткий ответ: Конфигурацию передают через переменные окружения (-e, --env-file, environment в compose). Секреты (пароли, токены) нельзя зашивать в образ — используют env во время запуска, Docker secrets, или внешние менеджеры (Vault, AWS Secrets Manager, k8s Secrets).
Подробно:
# По одной
docker run -e DATABASE_URL=postgres://... myapp
# Из файла
docker run --env-file .env myapp
# compose: ссылка на .env-файл, значения не в репозитории
services:
web:
env_file: .env
environment:
LOG_LEVEL: info
Docker secrets (Swarm) монтируют секрет файлом в /run/secrets/, а не в env:
services:
web:
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txt
⚠️ Ловушка: секрет, переданный через ENV в Dockerfile или --build-arg, остаётся в слоях образа и виден в docker history / docker inspect — кто угодно с доступом к образу его прочитает. Секреты передавай только в рантайме, а .env добавляй в .gitignore и .dockerignore. Переменные окружения видны процессам и в /proc/<pid>/environ, поэтому для высокочувствительных данных лучше файловые секреты или внешний менеджер.
15Registry, теги и антипаттерн latest
middle
Короткий ответ: Registry — хранилище образов (Docker Hub — публичный, GitHub/GitLab Container Registry, AWS ECR — приватные). Образы версионируются тегами (myapp:1.4.2). Использовать latest в проде — антипаттерн: тег плавающий, непредсказуемый, ломает воспроизводимость и откаты.
Подробно:
docker login registry.example.com
docker tag myapp:1.0 registry.example.com/team/myapp:1.0
docker push registry.example.com/team/myapp:1.0
docker pull registry.example.com/team/myapp:1.0
Хорошая стратегия тегирования:
myapp:1.4.2— семантическая версия (для откатов).myapp:git-a1b2c3d— тег по commit SHA (точная привязка к коду).myapp:1.4/myapp:1— плавающие для удобства.- Для максимальной строгости — pin по digest:
myapp@sha256:....
⚠️ Ловушка: latest — это просто тег по умолчанию, а не «самая свежая версия». Он не обновляется автоматически и указывает на тот образ, которому его в последний раз присвоили. В проде image: myapp:latest означает «непонятно что развёрнуто» — нельзя откатиться и нельзя воспроизвести инцидент. Всегда деплой по конкретному тегу/digest.
16Stateless контейнер и один процесс на контейнер
middle
Короткий ответ: Контейнер должен быть stateless — не хранить важное состояние внутри себя (его в любой момент можно убить и пересоздать). Состояние выносят в БД, кэш, volume, объектное хранилище. И принцип «один процесс (одна ответственность) на контейнер» — контейнер запускает один сервис, а не пачку демонов.
Подробно:
Почему stateless:
- Контейнеры эфемерны, оркестратор может убить и пересоздать их в любой момент (масштабирование, обновление, падение ноды).
- Stateless контейнеры легко масштабировать горизонтально — поднял ещё 5 копий за балансировщиком.
- Состояние → внешние сервисы: PostgreSQL, Redis, S3, named volumes.
Почему один процесс:
- Прозрачность логов (всё пишется в stdout/stderr → собирается централизованно).
- Корректная обработка сигналов и lifecycle.
- Изоляция и независимое масштабирование (веб, воркер, БД — отдельные контейнеры).
# Разные ответственности → разные контейнеры
services:
web: { build: ., command: gunicorn app:app }
worker: { build: ., command: celery -A app worker }
redis: { image: redis:7 }
⚠️ Ловушка: «один процесс» не означает буквально один PID — Gunicorn с воркерами это нормально (один менеджер процессов с одной ответственностью). Антипаттерн — пихать nginx + app + cron + sshd в один контейнер через supervisord. Также не пиши логи в файлы внутри контейнера — пиши в stdout/stderr, чтобы их подхватывала система сбора логов.
17Что такое CI и CD?
junior
Короткий ответ: CI (Continuous Integration) — частая автоматическая интеграция изменений в общую ветку с автоматической сборкой и прогоном тестов. CD расшифровывается двояко: Continuous Delivery — автоматически готовим релиз, но выкатываем по ручному подтверждению; Continuous Deployment — автоматически выкатываем в прод без ручного шага.
Подробно:
- CI — каждый push/PR запускает пайплайн: линт, тесты, сборка. Цель — ловить проблемы рано и не допускать «сломанной» главной ветки. Маленькие частые мерджи вместо «большого мерджа раз в месяц».
- Continuous Delivery — после CI артефакт всегда готов к деплою, но прод-выкатка требует нажатия кнопки (approve). Подходит, где нужен контроль момента релиза.
- Continuous Deployment — каждое прошедшее проверки изменение автоматически едет в прод. Требует высокой зрелости тестов, мониторинга и быстрых откатов.
Continuous Delivery: commit → CI → build → staging → [ручной approve] → prod
Continuous Deployment: commit → CI → build → staging → prod (автоматически)
⚠️ Ловушка: часто путают две «CD». На собеседовании уточни, что аббревиатура неоднозначна, и объясни разницу: ключевое отличие — есть ли ручной шаг перед продом. Delivery = «готово к выкатке в любой момент», Deployment = «выкатывается само».
18Этапы пайплайна: lint → test → build → deploy
junior
Короткий ответ: Типичный пайплайн: lint (статический анализ/стиль) → test (юнит/интеграционные тесты) → build (сборка артефакта/Docker-образа) → deploy (выкатка). Каждый следующий этап запускается, только если предыдущий прошёл — fail fast.
Подробно:
- Lint / static analysis — самый дешёвый и быстрый этап, ловит ошибки стиля и часть багов до запуска кода (
ruff,eslint,gofmt, проверка типовmypy/tsc). - Test — юнит-тесты, затем интеграционные. Часто с измерением покрытия. Падение тестов блокирует мердж.
- Build — сборка артефакта: Docker-образ, jar, бинарник, бандл фронта. Тегирование по версии/commit.
- Deploy — публикация образа в registry и выкатка на окружение (staging → prod). Часто с миграциями БД и smoke-тестами после.
Порядок продуман: дешёвые и быстрые проверки идут первыми, чтобы быстро отсеять очевидно сломанный код, не тратя время на дорогую сборку.
⚠️ Ловушка: миграции БД — отдельная боль. Их нужно делать совместимыми с предыдущей версией кода (expand/contract), иначе при rolling-деплое старые поды будут падать на новой схеме. Не «дропай колонку и деплой код одновременно».
19GitHub Actions / GitLab CI кратко
middle
Короткий ответ: Оба — системы CI/CD с описанием пайплайна в YAML внутри репозитория. В GitHub Actions: workflow содержит jobs, job содержит steps, jobs выполняются на runners. В GitLab CI: файл .gitlab-ci.yml со stages и jobs.
Подробно:
GitHub Actions (.github/workflows/ci.yml):
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -r requirements.txt
- run: ruff check .
- run: pytest
build:
needs: test # запустится только после успешного test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp:${{ github.sha }} .
GitLab CI (.gitlab-ci.yml):
stages: [test, build, deploy]
test:
stage: test
image: python:3.12
script:
- pip install -r requirements.txt
- pytest
build:
stage: build
script:
- docker build -t registry.example.com/myapp:$CI_COMMIT_SHA .
- docker push registry.example.com/myapp:$CI_COMMIT_SHA
⚠️ Ловушка: по умолчанию jobs внутри одной стадии выполняются параллельно, а каждый job стартует в чистом окружении — артефакты между jobs не передаются автоматически. Нужно явно объявлять зависимости (needs) и пробрасывать artifacts/кэш. Секреты храни в защищённых переменных/secrets, а не в YAML.
20Стратегии деплоя: rolling, blue-green, canary
middle
Короткий ответ: Rolling — постепенно заменяем инстансы новой версией по одному. Blue-green — поднимаем полную новую среду (green) рядом со старой (blue) и переключаем трафик целиком. Canary — пускаем новую версию на маленькую долю трафика, наблюдаем, постепенно увеличиваем.
Подробно:
| Стратегия | Как | Плюсы | Минусы |
|---|---|---|---|
| Rolling | заменяем поды поэтапно | без простоя, без двойных ресурсов | две версии работают одновременно, откат медленный |
| Blue-green | две полные среды, переключение трафика | мгновенный откат (переключить назад), нет смешения версий | нужно 2x ресурсов |
| Canary | 5% → 25% → 100% трафика на новое | раннее обнаружение проблем на малой аудитории | сложнее настроить (роутинг, метрики) |
Rolling: [v1][v1][v1] → [v2][v1][v1] → [v2][v2][v1] → [v2][v2][v2]
Blue-green: blue(v1) активна | green(v2) прогрета → переключили LB на green
Canary: 95% трафика → v1, 5% → v2; растим долю при хороших метриках
⚠️ Ловушка: при rolling и canary одновременно работают две версии приложения — API и схема БД должны быть обратно совместимы. Blue-green с общей БД тоже требует совместимости схемы. Canary без хорошего мониторинга бессмыслен: некому решать, что выкатка «здорова».
21Артефакты и кэш зависимостей в CI
middle
Короткий ответ: Артефакт — результат работы пайплайна (Docker-образ, бинарник, бандл, отчёт о покрытии), который сохраняется и передаётся между jobs или скачивается. Кэш — переиспользуемые между запусками данные (скачанные зависимости, слои сборки), чтобы пайплайн шёл быстрее.
Подробно:
Разница: артефакты — это выход пайплайна (нужны как результат), кэш — это ускорение (можно потерять без вреда, просто пересоберётся медленнее).
# GitHub Actions: кэш зависимостей
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ hashFiles('requirements.txt') }}
# Артефакт между jobs
- uses: actions/upload-artifact@v4
with: { name: dist, path: dist/ }
# GitLab CI
build:
cache:
key: { files: [package-lock.json] }
paths: [node_modules/]
artifacts:
paths: [dist/]
expire_in: 1 week
Docker layer caching в CI ускоряет сборку образов:
- uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=max
⚠️ Ловушка: ключ кэша должен включать хэш lock-файла (requirements.txt, package-lock.json), иначе обновишь зависимость, а CI продолжит тянуть старый кэш → нестабильные сборки. И не клади секреты или большие артефакты в кэш — он часто хранится без строгих ограничений и может «протухать».
22Linux: файловая система и права
junior
Короткий ответ: В Linux единое дерево от корня / (нет дисков C:/D:). Каждый файл имеет владельца (user), группу (group) и права для трёх категорий — owner/group/others — по три бита rwx (read/write/execute). Права меняют chmod, владельца — chown.
Подробно:
Ключевые директории: /etc (конфиги), /var (логи, переменные данные), /home (домашние), /usr (программы), /tmp (временное), /proc и /sys (виртуальные ФС ядра), /bin, /opt.
Права в выводе ls -l:
-rwxr-xr-- 1 alice devs 4096 Jun 24 10:00 script.sh
│└┬┘└┬┘└┬┘
│ │ │ └── others: r-- (чтение)
│ │ └───── group: r-x (чтение + выполнение)
│ └──────── owner: rwx (всё)
└────────── тип: - файл, d директория, l ссылка
Числовая запись (r=4, w=2, x=1):
chmod 755 script.sh # rwx r-x r-x — исполняемый файл/скрипт
chmod 644 file.txt # rw- r-- r-- — обычный файл
chmod 600 secret.key # rw- --- --- — только владелец (приватный ключ)
chmod +x deploy.sh # добавить право выполнения
chown alice file.txt # сменить владельца
chown alice:devs file.txt # владелец и группа
chown -R www-data:www-data /var/www # рекурсивно для всей директории
⚠️ Ловушка: chmod 777 («всем всё») — почти всегда плохой совет из интернета, дыра в безопасности; настоящая причина проблемы обычно в неверном владельце (chown). Для директории бит x означает «можно зайти внутрь / получить доступ к файлам», а не «выполнить». SSH-ключ с правами шире 600 будет отвергнут клиентом.
23Полезные команды командной строки
junior
Короткий ответ: Навигация (ls/cd/cp/mv/rm), просмотр процессов (ps/top/htop), поиск (find/grep), обработка текста (awk/sed), логи (tail -f), диск (df/du), сеть (curl/wget/ssh), архивы (tar), управление процессами (kill).
Подробно:
# Навигация и файлы
ls -lah # подробно, скрытые, человекочитаемые размеры
cp -r src/ dst/ # рекурсивное копирование
mv old new # переместить/переименовать
rm -rf dir/ # удалить рекурсивно (ОСТОРОЖНО)
# Процессы
ps aux | grep nginx # все процессы, фильтр по имени
top # мониторинг в реальном времени (htop удобнее)
# Поиск
find /var/log -name "*.log" -mtime -1 # .log изменённые за сутки
find . -type f -size +100M # файлы больше 100 МБ
grep -rni "error" /var/log/app/ # рекурсивный поиск без учёта регистра, с номерами строк
# Обработка текста
awk '{print $1, $7}' access.log # вывести 1-й и 7-й столбцы
sed 's/foo/bar/g' file.txt # заменить foo на bar
cut -d: -f1 /etc/passwd # первая колонка по разделителю ":"
# Логи
tail -f /var/log/app.log # следить за добавлением строк
tail -n 100 app.log | grep ERROR
journalctl -u myapp -f # логи systemd-сервиса в реальном времени
# Диск
df -h # свободное место по разделам
du -sh ./* # размер каждого элемента в текущей папке
du -sh /var/log # суммарный размер директории
# Сеть
curl -s https://api.example.com/health # запрос (тихий режим)
curl -i -X POST -d '{"a":1}' -H "Content-Type: application/json" URL # POST с заголовком
wget https://example.com/file.tar.gz
ssh -i ~/.ssh/key.pem user@host # подключение по ключу
# Архивы
tar -czf backup.tar.gz dir/ # создать gzip-архив (create zip file)
tar -xzf backup.tar.gz # распаковать (extract zip file)
tar -tzf backup.tar.gz # посмотреть содержимое
⚠️ Ловушка: rm -rf не спрашивает подтверждения и не имеет корзины — rm -rf / или случайный пробел (rm -rf / tmp/foo) уничтожит систему. Мнемоника tar: create / extract, z = gzip, f = file. И kill <pid> шлёт SIGTERM, а не «убивает мгновенно».
24Процессы, сигналы, демоны, systemd
middle
Короткий ответ: Каждый процесс имеет PID. Сигналы — способ управлять процессами: SIGTERM (15) просит завершиться корректно, SIGKILL (9) убивает немедленно и неотменяемо, SIGINT (2) — это Ctrl+C, SIGHUP (1) часто значит «перечитай конфиг». Демоны — фоновые сервисы; на современных Linux ими управляет systemd.
Подробно:
# Сигналы
kill <pid> # SIGTERM — вежливо попросить завершиться (graceful)
kill -9 <pid> # SIGKILL — убить немедленно (нельзя проигнорировать/обработать)
kill -HUP <pid> # SIGHUP — часто перезагрузка конфига (например, nginx -s reload)
pkill -f gunicorn # по имени/паттерну
# Фоновые процессы
./long-task & # запустить в фоне
jobs # фоновые задачи текущей сессии
nohup ./task & # не завершать при выходе из сессии (игнор SIGHUP)
disown # отвязать задачу от shell
SIGTERM vs SIGKILL:
- SIGTERM — процесс может перехватить, доделать запросы, закрыть соединения, сохранить состояние → graceful shutdown. Это «правильный» способ остановки.
- SIGKILL — отправляется ядром, процесс не может перехватить или проигнорировать; мгновенная смерть без cleanup → возможна потеря данных, «висячие» соединения.
docker stop шлёт SIGTERM, ждёт grace-период (по умолчанию 10 с), затем SIGKILL.
systemd — система инициализации и менеджер сервисов (PID 1):
systemctl start myapp
systemctl stop myapp
systemctl restart myapp
systemctl enable myapp # автозапуск при загрузке
systemctl status myapp
journalctl -u myapp --since "1 hour ago" # логи сервиса
Unit-файл /etc/systemd/system/myapp.service:
[Unit]
Description=My App
After=network.target
[Service]
ExecStart=/usr/bin/gunicorn app:app --bind 0.0.0.0:8000
Restart=always
User=appuser
[Install]
WantedBy=multi-user.target
⚠️ Ловушка: kill -9 — крайняя мера, а не первый шаг. Если хватать SIGKILL, приложение не успеет gracefully завершить транзакции/закрыть файлы → повреждение данных, незакрытые lock'и. Сначала SIGTERM, и только если процесс «завис» и не реагирует — SIGKILL.
25Пайпы и перенаправление (stdin, stdout, stderr)
junior
Короткий ответ: У процесса три стандартных потока: stdin (0, ввод), stdout (1, обычный вывод), stderr (2, ошибки). Пайп | направляет stdout одной команды в stdin другой. Перенаправления > (перезаписать в файл), >> (дописать), 2>&1 (слить stderr в stdout).
Подробно:
# Пайп: stdout слева → stdin справа
cat access.log | grep ERROR | wc -l # посчитать строки с ERROR
# Перенаправление stdout в файл
echo "hello" > out.txt # перезаписать
echo "more" >> out.txt # дописать в конец
# Потоки по номерам: 0=stdin, 1=stdout, 2=stderr
command > out.log 2> err.log # вывод и ошибки в разные файлы
command > all.log 2>&1 # и stdout, и stderr в один файл
command 2>/dev/null # выбросить ошибки в «никуда»
command &> all.log # bash: всё в один файл (сокращение для >file 2>&1)
# stdin из файла
sort < names.txt
# Комбинации
ps aux | grep python | awk '{print $2}' | xargs kill # найти и убить процессы
journalctl -u myapp 2>&1 | tail -50
⚠️ Ловушка: порядок в > file 2>&1 важен! Это значит «stdout → file, затем stderr → туда же, куда сейчас указывает stdout (file)». А 2>&1 > file сначала направит stderr на текущий stdout (терминал), и только потом stdout в файл — stderr останется на экране. Также cmd | grep теряет код возврата cmd (берётся код последней команды; см. set -o pipefail).
26Переменные окружения (export, .bashrc, PATH)
junior
Короткий ответ: Переменные окружения — пары ключ=значение, доступные процессам. export VAR=value делает переменную доступной дочерним процессам. PATH — список директорий, где shell ищет исполняемые команды. Постоянные настройки кладут в .bashrc/.profile/.zshrc.
Подробно:
# Установить переменную (видна только текущему shell)
MY_VAR=hello
# Экспортировать (видна дочерним процессам)
export MY_VAR=hello
export DATABASE_URL="postgres://localhost/app"
echo $MY_VAR # прочитать
env # все переменные окружения
printenv PATH # конкретную
# Только для одной команды
DEBUG=1 python app.py
# PATH — где искать команды
echo $PATH # /usr/local/bin:/usr/bin:/bin:...
export PATH="$HOME/bin:$PATH" # добавить свою папку в начало
which python # показать, какой бинарник запустится
Постоянные переменные — в файлах инициализации shell:
# ~/.bashrc (bash, интерактивный) или ~/.zshrc (zsh)
export PATH="$HOME/.local/bin:$PATH"
export EDITOR=vim
source ~/.bashrc # применить изменения без перелогина
⚠️ Ловушка: без export переменная видна только текущему shell и не наследуется дочерними процессами/скриптами. Изменения в .bashrc не применяются к уже открытым сессиям — нужно source или новый терминал. Если в PATH добавить «.» (текущую папку), это риск безопасности — можно случайно запустить вредоносный ls из текущей директории.
27Какой процесс слушает порт (lsof, netstat, ss)
middle
Короткий ответ: Узнать, какой процесс занял порт, можно через ss, lsof или netstat. Современная рекомендация — ss (быстрее, заменяет устаревший netstat).
Подробно:
# ss — современный инструмент
ss -tulpn # TCP+UDP, listening, с PID и портами (без резолва имён)
ss -tlpn | grep :8000 # кто слушает порт 8000
# lsof — список открытых файлов/сокетов
lsof -i :8000 # процесс на порту 8000
lsof -i -P -n | grep LISTEN
# netstat (устаревший, но часто встречается)
netstat -tulpn | grep :80
# Флаги ss/netstat: t=tcp, u=udp, l=listening, p=process(pid), n=numeric, a=all
Типичный сценарий «Address already in use»:
ss -tlpn | grep :8000 # найти PID, занявший порт
# users:(("python",pid=12345,fd=3))
kill 12345 # освободить порт
⚠️ Ловушка: чтобы увидеть чужой процесс (PID) на порту, часто нужны права root (sudo ss -tulpn), иначе колонка с процессом будет пустой. На macOS флаги отличаются — там надёжнее lsof -i :PORT. Порты ниже 1024 (привилегированные, например 80/443) требуют root для прослушивания.
28Что такое nginx?
junior
Короткий ответ: nginx — высокопроизводительный веб-сервер и обратный прокси (reverse proxy). Используется для: отдачи статики, проксирования запросов к бэкенду, балансировки нагрузки между несколькими инстансами, терминации TLS (HTTPS), кэширования и ограничения нагрузки. Построен на событийной (async) модели, держит десятки тысяч соединений.
Подробно:
Основные роли:
- Reverse proxy — принимает запросы от клиентов и перенаправляет их на бэкенд-приложение, скрывая его.
- Балансировщик нагрузки — распределяет запросы между несколькими инстансами приложения (
upstream). - Отдача статики — CSS/JS/изображения nginx отдаёт сам, эффективнее, чем приложение.
- TLS termination — расшифровывает HTTPS, дальше к приложению идёт обычный HTTP внутри защищённой сети.
Базовый конфиг:
# Пул бэкенд-серверов для балансировки
upstream backend {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
# least_conn; # стратегия: к наименее загруженному
}
server {
listen 80;
server_name example.com;
# Отдача статики напрямую
location /static/ {
root /var/www/myapp;
expires 30d;
}
# Проксирование на бэкенд
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTPS + TLS termination
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://backend;
}
}
nginx -t # проверить синтаксис конфига
nginx -s reload # перечитать конфиг без даунтайма (SIGHUP)
systemctl reload nginx
⚠️ Ловушка: proxy_pass http://backend без передачи заголовков (Host, X-Forwarded-For, X-Forwarded-Proto) приводит к тому, что приложение видит IP nginx вместо реального клиента и не знает, что соединение было по HTTPS (ломаются редиректы, генерация ссылок, rate limiting по IP). Различие в proxy_pass со слэшем на конце и без — меняет, как переписывается путь.
29nginx vs WSGI/ASGI сервер (gunicorn/uvicorn)
middle
Короткий ответ: Это разные слои, нужны оба. nginx — внешний веб-сервер/прокси (TLS, статика, балансировка, защита). gunicorn/uvicorn — application server, который запускает сам Python-код через стандарт WSGI (синхронный, Django/Flask) или ASGI (асинхронный, FastAPI). Связка: клиент → nginx → gunicorn/uvicorn → приложение.
Подробно:
Кто что делает:
- nginx — быстро принимает множество соединений, отдаёт статику, терминирует TLS, защищает от медленных клиентов (slowloris), буферизует, балансирует. На C, событийная модель.
- gunicorn/uvicorn — запускает воркеры с твоим кодом, управляет их жизненным циклом, общается с приложением по WSGI/ASGI. Сам по себе не предназначен «смотреть в интернет».
Клиент → [nginx :443] TLS, статика, балансировка
│ HTTP
▼
[gunicorn :8000] менеджер воркеров
│ WSGI/ASGI
▼
[Django / FastAPI приложение]
# WSGI (Django/Flask) — синхронно
gunicorn myproject.wsgi:application --bind 127.0.0.1:8000 --workers 4
# ASGI (FastAPI/Starlette) — асинхронно
uvicorn app:app --host 127.0.0.1 --port 8000 --workers 4
# или gunicorn с uvicorn-воркерами в проде:
gunicorn app:app -k uvicorn.workers.UvicornWorker --workers 4
WSGI vs ASGI: WSGI — синхронный стандарт (один запрос на воркер в момент времени), ASGI — асинхронный, поддерживает websockets и долгие соединения.
⚠️ Ловушка: не выставляй gunicorn/uvicorn dev-сервером напрямую в интернет без nginx — встроенный dev-сервер (flask run, python manage.py runserver, uvicorn --reload) вообще только для разработки, не держит нагрузку и небезопасен. И число воркеров не «чем больше тем лучше»: типовая формула 2 * CPU + 1, иначе память кончится.
30Kubernetes кратко
middle
Короткий ответ: Kubernetes (k8s) — система оркестрации контейнеров: автоматически разворачивает, масштабирует, перезапускает и распределяет контейнеры по кластеру машин. Базовые объекты: Pod (минимальная единица — один или несколько контейнеров), Deployment (управляет репликами подов и обновлениями), Service (стабильная сетевая точка доступа к подам).
Подробно:
- Pod — наименьшая разворачиваемая единица, один или несколько тесно связанных контейнеров с общей сетью/хранилищем. Эфемерен.
- Deployment — декларативно описывает «хочу N реплик такого-то образа», следит за их количеством, делает rolling-обновления и откаты.
- Service — стабильный виртуальный IP/DNS-имя для группы подов (поды приходят и уходят, Service остаётся). Балансирует трафик между подами.
- Ingress — маршрутизация HTTP(S)-трафика снаружи в сервисы (часто на базе nginx).
apiVersion: apps/v1
kind: Deployment
metadata: { name: myapp }
spec:
replicas: 3
selector: { matchLabels: { app: myapp } }
template:
metadata: { labels: { app: myapp } }
spec:
containers:
- name: myapp
image: registry.example.com/myapp:1.4.2
ports: [{ containerPort: 8000 }]
Когда нужен: много сервисов, требуется автоскейлинг, самовосстановление, нулевой даунтайм при выкатке, работа на кластере из многих машин. Когда не нужен: один-два контейнера, маленький проект — k8s избыточен, хватит docker-compose или managed-платформы (PaaS).
⚠️ Ловушка: Kubernetes — это огромная сложность (сеть, RBAC, storage, мониторинг). Тащить его в маленький проект — частая ошибка переинженеринга. На собеседовании джуна достаточно понимать pod/deployment/service и что k8s решает оркестрацию; глубоко лезть не нужно.
31Облака кратко (compute, storage, managed db)
middle
Короткий ответ: Облако даёт инфраструктуру по запросу. Базовые категории: compute (вычисления — VM/инстансы, например AWS EC2), storage (хранилище — объектное S3, блочное EBS), managed database (управляемая БД — RDS). Managed-сервис — провайдер берёт на себя рутину (установку, бэкапы, обновления, отказоустойчивость), ты пользуешься.
Подробно:
Категории на примере AWS (аналоги есть у GCP/Azure):
- Compute — EC2 (виртуалки), Lambda (serverless-функции), ECS/EKS (контейнеры).
- Storage — S3 (объектное, для файлов/бэкапов/статики), EBS (диски к VM), EFS (сетевая ФС).
- Managed DB — RDS (PostgreSQL/MySQL), DynamoDB (NoSQL), ElastiCache (Redis).
- Networking — VPC, Load Balancer, CloudFront (CDN).
Managed vs self-hosted:
- Self-hosted — сам ставишь PostgreSQL на VM, сам настраиваешь репликацию, бэкапы, апдейты, мониторинг. Дешевле в деньгах, дороже во времени и риске.
- Managed (RDS) — провайдер делает бэкапы, патчи, failover, масштабирование одним кликом. Дороже, но снимает операционную нагрузку. Ты не имеешь доступа к ОС сервера.
⚠️ Ловушка: managed-сервисы дают vendor lock-in и иногда скрытую дороговизну (egress-трафик, IOPS). Также «managed» не значит «не надо думать о бэкапах/безопасности» — за конфигурацию доступа (security groups, публичность бакета S3) отвечаешь ты. Публичный S3-бакет с данными — классическая утечка.
32Мониторинг и логи концептуально
middle
Короткий ответ: Наблюдаемость стоит на трёх китах: метрики (числовые показатели во времени — CPU, RPS, latency), логи (текстовые события), трейсы (путь запроса через сервисы). Типовой стек: Prometheus (сбор метрик) + Grafana (дашборды/визуализация), ELK (Elasticsearch + Logstash + Kibana) или Loki — для логов.
Подробно:
- Метрики (Prometheus + Grafana) — Prometheus периодически опрашивает (pull) метрики приложений по HTTP-эндпоинту
/metrics, хранит как time series. Grafana строит графики и алерты поверх. Метрики — для трендов и алертинга («latency вырос», «диск заполняется»). - Логи (ELK / Loki) — централизованный сбор логов со всех сервисов в одно место, поиск, фильтрация (Kibana/Grafana). Контейнеры пишут в stdout, сборщик (Fluentd/Filebeat/Promtail) отправляет в хранилище.
- Трейсы (Jaeger / OpenTelemetry) — отслеживают путь одного запроса через микросервисы, находят узкое место.
- Алертинг (Alertmanager) — уведомления (Slack/PagerDuty) при срабатывании правил.
Полезные понятия: SLI/SLO/SLA (показатели/цели/обязательства по надёжности), «четыре золотых сигнала» — latency, traffic, errors, saturation.
⚠️ Ловушка: логирование без структуры и централизации бесполезно при инциденте — нужны структурированные (JSON) логи с correlation/request id, чтобы связать события одного запроса между сервисами. И не логируй секреты/персональные данные (пароли, токены, номера карт) — это и утечка, и нарушение требований (GDPR/PCI).
33Зачем Docker, если есть venv?
concept
Короткий ответ: venv изолирует только Python-пакеты в рамках одной ОС. Docker изолирует всё окружение целиком: версию Python/ОС, системные библиотеки (libpq, libssl), системные пакеты, переменные окружения, сетевую конфигурацию. venv не спасёт, если на сервере другая версия системной библиотеки или вообще другая ОС.
Подробно:
venv отвечает на вопрос «какие Python-пакеты и какой интерпретатор» внутри уже существующей системы. Но реальные приложения зависят от большего:
- системных библиотек (
libpqдля psycopg,libjpeg,ffmpeg); - версии самой ОС и её пакетов;
- сервисов рядом (Postgres, Redis) — Docker/compose поднимает и их;
- одинаковости между ноутбуком разработчика, CI и продом.
| venv | Docker | |
|---|---|---|
| Python-пакеты | да | да |
| Версия Python | да (если стоит) | да (в образе) |
| Системные библиотеки | нет | да |
| ОС/окружение целиком | нет | да |
| Соседние сервисы (БД) | нет | да (compose) |
| Переносимость на другую ОС | нет | да |
⚠️ Ловушка: Docker не отменяет venv внутри образа в некоторых случаях, но обычно внутри контейнера venv не нужен — контейнер сам по себе изоляция. Главный поинт для собеседования: venv — изоляция зависимостей языка, Docker — изоляция всего окружения и инфраструктуры, это разные уровни задачи «у меня работает».
34Зачем вообще CI/CD?
concept
Короткий ответ: Чтобы превратить релизы из редкого, ручного и страшного события в частые, автоматические и безопасные. CI/CD ловит баги рано (автотесты на каждый коммит), убирает ручные ошибки деплоя, ускоряет доставку фич и делает откат предсказуемым.
Подробно:
Без CI/CD: код мерджат большими кусками, тесты гоняют «когда вспомнят», деплой — ручной скрипт «по памяти», который запускает один сеньор по пятницам, и если что-то ломается — паника. Релиз редкий и рискованный → каждый релиз огромный → ещё рискованнее (порочный круг).
С CI/CD:
- Раннее обнаружение — тесты/линт на каждый PR, сломанный код не доезжает до main.
- Повторяемость — деплой описан кодом, делается одинаково каждый раз, не зависит от человека.
- Скорость — фичи доезжают до пользователей быстрее, фидбэк быстрее.
- Меньше риска на релиз — маленькие частые изменения проще откатить и проще найти причину поломки.
- Документированность — пайплайн как код = понятно, что и как собирается/деплоится.
⚠️ Ловушка: CI/CD без хороших тестов и мониторинга — это просто «быстрая доставка багов в прод». Автоматизация усиливает и хорошие, и плохие практики. Сначала надёжные тесты и наблюдаемость, потом автоматический деплой в прод.
35Что делает nginx перед приложением?
concept
Короткий ответ: nginx стоит «фронтом» и берёт на себя то, с чем приложение справляется плохо или не должно заниматься: терминацию TLS, отдачу статики, балансировку между инстансами, защиту от медленных/злонамеренных клиентов, буферизацию, сжатие, rate limiting. Приложение занимается только бизнес-логикой.
Подробно:
Зачем не «приложение напрямую в интернет»:
- TLS termination — nginx расшифровывает HTTPS один раз, разгружая приложение от криптографии; сертификаты в одном месте.
- Статика — CSS/JS/картинки nginx отдаёт во много раз эффективнее application-сервера, не дёргая Python/Node.
- Балансировка — один публичный адрес, за ним пул инстансов приложения; nginx распределяет нагрузку и убирает упавшие.
- Защита — буферизация защищает воркеры приложения от медленных клиентов (slowloris), rate limiting от перебора, ограничение размера запроса.
- Единая точка — заголовки, gzip, CORS, редиректы http→https, healthcheck'и — всё на краю.
location /static/ { root /var/www; expires 1y; } # статику — сам
location / {
limit_req zone=api burst=20; # rate limit
proxy_pass http://app_backend; # динамику — приложению
client_max_body_size 10M; # лимит размера
}
⚠️ Ловушка: распространённое заблуждение, что nginx «ускоряет Python-код». Он не ускоряет вычисления приложения — он снимает с него непрофильную работу (IO, статика, TLS, соединения) и распределяет нагрузку. Узкое место в самом коде/БД nginx не починит.
Источники
Источники и редакционная политика
Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.
От чтения к воспроизведению
Отрепетируйте полный цикл интервью.
RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.