Перейти к содержанию
Бэкенд и системы

35 вопросов по теме «Docker, CI/CD и Linux» на собеседовании

В этом материале — 35 вопросов из русской колоды RecallDeck по теме «Docker, CI/CD и Linux». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

33 мин чтения35 подробных ответовПроверено 24 августа 2026
Главная мысль

Стройте ответ вокруг владения состоянием, лимитов, отказа и восстановления. Определение становится инженерным ответом только в конкретном production-сценарии.

Вопросы и ответы

35 подробных ответов

01

Что такое Docker и какую проблему он решает?

Короткий ответ: 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 виртуальная машина

Короткий ответ: 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.

03

Image vs container

Короткий ответ: 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) и кэширование

Короткий ответ: Образ собирается из слоёв — каждая инструкция в 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

Короткий ответ: 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 /appcd действует только в рамках одного RUN.

06

COPY vs ADD

Короткий ответ: 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, а распаковку делай отдельной командой, если она не нужна неявно.

07

CMD vs ENTRYPOINT

Короткий ответ: 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-форму.

08

ARG vs ENV

Короткий ответ: 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 или менеджер секретов.

09

Multi-stage builds

Короткий ответ: 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

Как уменьшить размер образа

Короткий ответ: Использовать лёгкие базовые образы (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.

11

Volumes vs bind mounts

Короткий ответ: И то, и другое — способ сохранять данные вне эфемерного слоя контейнера. 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).

12

Docker networking: bridge, host, порты

Короткий ответ: По умолчанию контейнеры подключены к сети 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?

Короткий ответ: 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

Короткий ответ: Конфигурацию передают через переменные окружения (-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, поэтому для высокочувствительных данных лучше файловые секреты или внешний менеджер.

15

Registry, теги и антипаттерн latest

Короткий ответ: 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.

16

Stateless контейнер и один процесс на контейнер

Короткий ответ: Контейнер должен быть 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?

Короткий ответ: 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

Короткий ответ: Типичный пайплайн: lint (статический анализ/стиль) → test (юнит/интеграционные тесты) → build (сборка артефакта/Docker-образа) → deploy (выкатка). Каждый следующий этап запускается, только если предыдущий прошёл — fail fast.

Подробно:

  1. Lint / static analysis — самый дешёвый и быстрый этап, ловит ошибки стиля и часть багов до запуска кода (ruff, eslint, gofmt, проверка типов mypy/tsc).
  2. Test — юнит-тесты, затем интеграционные. Часто с измерением покрытия. Падение тестов блокирует мердж.
  3. Build — сборка артефакта: Docker-образ, jar, бинарник, бандл фронта. Тегирование по версии/commit.
  4. Deploy — публикация образа в registry и выкатка на окружение (staging → prod). Часто с миграциями БД и smoke-тестами после.

Порядок продуман: дешёвые и быстрые проверки идут первыми, чтобы быстро отсеять очевидно сломанный код, не тратя время на дорогую сборку.

⚠️ Ловушка: миграции БД — отдельная боль. Их нужно делать совместимыми с предыдущей версией кода (expand/contract), иначе при rolling-деплое старые поды будут падать на новой схеме. Не «дропай колонку и деплой код одновременно».

19

GitHub Actions / GitLab CI кратко

Короткий ответ: Оба — системы 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

Короткий ответ: 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

Короткий ответ: Артефакт — результат работы пайплайна (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 продолжит тянуть старый кэш → нестабильные сборки. И не клади секреты или большие артефакты в кэш — он часто хранится без строгих ограничений и может «протухать».

22

Linux: файловая система и права

Короткий ответ: В 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

Полезные команды командной строки

Короткий ответ: Навигация (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

Короткий ответ: Каждый процесс имеет 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)

Короткий ответ: У процесса три стандартных потока: 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)

Короткий ответ: Переменные окружения — пары ключ=значение, доступные процессам. 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)

Короткий ответ: Узнать, какой процесс занял порт, можно через 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?

Короткий ответ: 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 со слэшем на конце и без — меняет, как переписывается путь.

29

nginx vs WSGI/ASGI сервер (gunicorn/uvicorn)

Короткий ответ: Это разные слои, нужны оба. 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, иначе память кончится.

30

Kubernetes кратко

Короткий ответ: 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)

Короткий ответ: Облако даёт инфраструктуру по запросу. Базовые категории: 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

Мониторинг и логи концептуально

Короткий ответ: Наблюдаемость стоит на трёх китах: метрики (числовые показатели во времени — 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?

Короткий ответ: 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?

Короткий ответ: Чтобы превратить релизы из редкого, ручного и страшного события в частые, автоматические и безопасные. CI/CD ловит баги рано (автотесты на каждый коммит), убирает ручные ошибки деплоя, ускоряет доставку фич и делает откат предсказуемым.

Подробно:

Без CI/CD: код мерджат большими кусками, тесты гоняют «когда вспомнят», деплой — ручной скрипт «по памяти», который запускает один сеньор по пятницам, и если что-то ломается — паника. Релиз редкий и рискованный → каждый релиз огромный → ещё рискованнее (порочный круг).

С CI/CD:

  • Раннее обнаружение — тесты/линт на каждый PR, сломанный код не доезжает до main.
  • Повторяемость — деплой описан кодом, делается одинаково каждый раз, не зависит от человека.
  • Скорость — фичи доезжают до пользователей быстрее, фидбэк быстрее.
  • Меньше риска на релиз — маленькие частые изменения проще откатить и проще найти причину поломки.
  • Документированность — пайплайн как код = понятно, что и как собирается/деплоится.

⚠️ Ловушка: CI/CD без хороших тестов и мониторинга — это просто «быстрая доставка багов в прод». Автоматизация усиливает и хорошие, и плохие практики. Сначала надёжные тесты и наблюдаемость, потом автоматический деплой в прод.

35

Что делает nginx перед приложением?

Короткий ответ: nginx стоит «фронтом» и берёт на себя то, с чем приложение справляется плохо или не должно заниматься: терминацию TLS, отдачу статики, балансировку между инстансами, защиту от медленных/злонамеренных клиентов, буферизацию, сжатие, rate limiting. Приложение занимается только бизнес-логикой.

Подробно:

Зачем не «приложение напрямую в интернет»:

  1. TLS termination — nginx расшифровывает HTTPS один раз, разгружая приложение от криптографии; сертификаты в одном месте.
  2. Статика — CSS/JS/картинки nginx отдаёт во много раз эффективнее application-сервера, не дёргая Python/Node.
  3. Балансировка — один публичный адрес, за ним пул инстансов приложения; nginx распределяет нагрузку и убирает упавшие.
  4. Защита — буферизация защищает воркеры приложения от медленных клиентов (slowloris), rate limiting от перебора, ограничение размера запроса.
  5. Единая точка — заголовки, 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, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку

Библиотека собеседований RecallDeck

Подробные русские ответы, разборы этапов найма и планы подготовки для российского IT-рынка.

RSS