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

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

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

33 мин чтения35 подробных ответовОбновлено
Главная мысль

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

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

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

Проверка по первоисточникам опубликована для 35 из 35 ответов. Дата и область проверки указаны у каждого проверенного ответа.

01

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

Короткий ответ: Docker упаковывает приложение и его зависимости пользовательского пространства в образ, а затем запускает из него изолированный процесс. Это уменьшает различия между разработкой, CI и продакшеном.

Для Go-сервиса образ может содержать бинарник, сертификаты CA и необходимые динамические библиотеки. Совместимые ядро и архитектуру CPU предоставляет хост; конфигурация, секреты, лимиты ресурсов и внешние сервисы задаются отдельно.

docker build -t myapp:1.0 .
docker run -d -p 127.0.0.1:8000:8000 --name myapp myapp:1.0
docker logs -f myapp

Приложение должно слушать интерфейс контейнера, например :8000. Здесь порт опубликован только на loopback хоста для локальной работы.

Ограничение: одинаковый образ не гарантирует одинаковое поведение на любой машине. Linux-контейнеры на macOS/Windows обычно работают внутри Linux-VM; архитектура CPU, возможности ядра, сеть и внешняя конфигурация остаются важны. Собирай варианты для нужных платформ и проверяй реальное окружение запуска.

Источники: Документация 1, Документация 2.

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). Без user namespace UID 0 контейнера соответствует UID 0 хоста, но доступ всё ещё ограничен namespaces и capabilities. Bind mounts хоста и privileged-режим могут ослабить границу; используй непривилегированного пользователя, rootless-режим или user namespaces.

Источники: Документация 1.

03

Image vs container

Короткий ответ: Image (образ) — это неизменяемый шаблон, «снимок» файловой системы с приложением. Container (контейнер) — экземпляр образа, который может работать или быть остановлен, процесс с тонким записываемым слоем поверх образа. Аналогия: образ — класс, контейнер — объект.

Подробно:

  • Образ read-only, состоит из слоёв. Один образ — много контейнеров.
  • Контейнер — это образ + тонкий writable-слой сверху. Записи в файловую систему вне смонтированных путей идут в этот слой; volumes, bind mounts и tmpfs используют отдельное хранилище. При удалении контейнера слой пропадает (если данные не вынесены в 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. Частая ошибка джуна — положить базу данных в файловую систему контейнера и удивляться, что после пересоздания всё пропало. Контейнеры эфемерны.

Источники: Документация 1, Документация 2.

04

Слои образа (layers) и кэширование

Короткий ответ: Образ собирается из слоёв — инструкции вроде RUN и COPY меняют файловую систему, а CMD и ENV в основном задают метаданные образа. 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 → устаревшие индексы пакетов). Измеряй добавленные байты: одно число слоёв не определяет размер. Изменения в удалённом репозитории пакетов также не инвалидируют закэшированный RUN автоматически.

Источники: Документация 1, Документация 2.

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

# Рабочая директория (создаётся, если нет; задаёт каталог для последующих RUN, CMD, ENTRYPOINT и относительных путей назначения COPY/ADD)
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:

  • Выбирайте поддерживаемую версию базового образа; теги изменяемы. Для точной фиксации используйте digest и предусмотрите обновления безопасности.
  • .dockerignore, чтобы не тащить .git, node_modules, __pycache__.
  • Сохраняйте удобные для кэша шаги и удаляйте временные файлы пакетов в том же RUN, где они созданы. Меньшее число инструкций само по себе не гарантирует меньший образ.
  • Не запускать от root (USER).
  • Файлы зависимостей копировать и устанавливать отдельным слоем до исходников: тогда изменение приложения переиспользует закэшированный слой зависимостей и не запускает дорогую установку заново.

⚠️ Ловушка: EXPOSE 8000 ничего не публикует наружу — это только документация/метаданные. Чтобы порт был доступен с хоста, нужен docker run -p 8000:8000. Также WORKDIR стоит использовать вместо RUN cd /app — cd действует только в рамках одного RUN.

06

COPY vs ADD

Короткий ответ: Для обычных локальных файлов предпочитай COPY. У ADD есть дополнительные возможности: распаковка локальных tar-архивов и получение удалённых источников. Используй его осознанно, когда это нужно.

COPY ./src/ /app/src/
ADD app.tar.gz /app/

COPY --from=builder также копирует из стадии сборки или другого образа. ADD локального архива может распаковать его, а COPY сохраняет архив файлом. Скачивание через ADD само по себе не антипаттерн: современный синтаксис Dockerfile поддерживает ADD --checksum=sha256:<digest> для HTTP-источников с проверкой целостности.

Выбор: простое локальное копирование — COPY; удалённый источник — закрепить и проверить содержимое; сложная загрузка с авторизацией — подходящая команда с build secret mount. Обычный RUN curl ... тоже не проверяет целостность автоматически. Безопасность и кэширование зависят от инструкции и её входных данных, а не только от названия COPY или ADD.

Источники: Документация 1, Документация 2.

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-обёртка без exec и передачи сигналов может мешать корректному завершению. Exec-форма убирает эту обёртку, но приложению всё равно нужна обработка сигналов. docker stop использует настроенные stop signal и период ожидания перед SIGKILL. ENTRYPOINT также можно переопределить через --entrypoint.

Источники: Документация 1.

08

ARG vs ENV

Короткий ответ: ARG — переменная времени сборки, доступна только в Dockerfile при docker build и не добавляется автоматически в окружение запущенного контейнера; значение может остаться в истории образа, provenance или созданных файлах. 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 или менеджер секретов.

Источники: Документация 1, Документация 2.

09

Multi-stage builds

Короткий ответ: Multi-stage build — несколько стадий FROM в одном Dockerfile, где в финальный образ копируется только результат сборки, а тяжёлые инструменты сборки (компиляторы, dev-зависимости) остаются в промежуточных стадиях. Главная цель — резко уменьшить размер и поверхность атаки итогового образа.

Подробно:

Если поставлять стадию сборки, компилятор и файлы сборки останутся в образе. Отдельная runtime-стадия позволяет оставить бинарник и нужные зависимости; измеряй фактический размер.

# --- Стадия сборки ---
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=...; файловая система builder автоматически не наследуется. Если забыть скопировать рантайм-зависимость (например, сертификаты CA, нужные для HTTPS-запросов в scratch/alpine) — проверка HTTPS или другая зависимая операция может завершиться ошибкой в рантайме, хотя сборка прошла.

Указанные теги версий иллюстрируют разделение стадий, а не поддерживаемый production baseline. Выбирай поддерживаемые базы под версию модуля и фиксируй проверенные digest для воспроизводимости; проверяй сертификаты, cgo и архитектуру в рантайме.

Источники: Документация 1.

10

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

Короткий ответ: Использовать лёгкие базовые образы (alpine, slim, distroless), multi-stage builds, .dockerignore, объединять RUN и чистить кэш пакетных менеджеров в том же слое, не ставить dev-зависимости в прод.

Подробно:

FROM python:3.12-slim       # пример базы; измеряй выбранный образ
WORKDIR /app
COPY requirements.txt .

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.

Источники: Документация 1.

11

Volumes vs bind mounts

Короткий ответ: И то, и другое — способ сохранять данные вне эфемерного слоя контейнера. Volume управляется Docker и хранится через настроенный volume driver (путь зависит от установки Docker) — для продакшен-данных (БД). Bind mount монтирует конкретную папку с хоста в контейнер — удобно для разработки (живой код).

Подробно:

# Named volume — Docker сам управляет хранилищем
docker volume create pgdata
docker run --env-file ./postgres.env -v pgdata:/var/lib/postgresql/data postgres:16

# Bind mount — конкретный путь хоста → путь в контейнере
docker run -v $(pwd)/src:/app/src myapp        # код с хоста виден внутри (hot reload)

# tmpfs — временное хранилище в памяти; страницы могут попасть на диск через swap
docker run --tmpfs /tmp myapp

В compose:

services:
  db:
    image: postgres:16
    env_file: ./postgres.env
    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).

Перед запуском создай postgres.env с локальным POSTGRES_PASSWORD и исключи файл из git. Локальный Docker volume переживает удаление контейнера, но не переносится автоматически на другой хост и не заменяет резервные копии.

Источники: Документация 1, Документация 2.

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 --env-file ./postgres.env postgres:16
docker run -d --name api --network appnet myapi   # внутри api: host "db" резолвится в IP db

# host-сеть: Linux Engine или опция Docker Desktop 4.34+; без -p
docker run --network host nginx

Типы драйверов:

  • bridge (по умолчанию) — изолированная виртуальная сеть, NAT наружу.
  • host — общая сеть с хостом, без проброса портов; убирает границу network namespace и может уменьшить сетевые накладные расходы.
  • none — только loopback, без внешнего сетевого доступа.
  • overlay — сеть Docker между хостами с обязательным Swarm mode; сеть Kubernetes настраивается отдельно через её сетевую реализацию.

⚠️ Ловушка: в дефолтной bridge-сети DNS по именам контейнеров не работает — резолв по имени есть только в пользовательской сети (docker network create). Поэтому compose автоматически создаёт свою сеть, и сервисы видят друг друга по именам. Ещё: внутри контейнера localhost — это сам контейнер, а не хост; чтобы достучаться до хоста, на Mac/Windows используют host.docker.internal.

Для локального примера БД создай неотслеживаемый postgres.env с POSTGRES_PASSWORD. Публикуй порты только при необходимости: -p 127.0.0.1:8080:80 ограничивает доступ loopback-интерфейсом хоста.

Для Docker overlay участвующие хосты должны войти в swarm, даже если соединяются standalone-контейнеры. Документация.

Источники: Документация 1.

13

Что такое docker-compose?

Короткий ответ: Docker Compose (современная команда docker compose; docker-compose — прежняя команда) — инструмент для описания и запуска многоконтейнерных приложений одним YAML-файлом. Вместо длинных docker run команд для каждого сервиса описываешь все сервисы, сети, тома и зависимости декларативно, и поднимаешь всё одной командой docker compose up.

Подробно:

services:
  web:
    build: .
    ports:
      - "127.0.0.1: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 помогает с начальной готовностью; приложению всё равно нужна подходящая логика повторов и переподключения при последующих сбоях. Пароли в примере — одноразовые локальные значения, не production-секреты. Compose подходит и для некоторых production-развёртываний; выбор оркестрации зависит от надёжности, масштаба и поддержки, а не от правила «production требует 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

Compose может монтировать файл секрета в /run/secrets/. Этот пример с локальным файлом не равнозначен зашифрованному хранилищу секретов Swarm:

services:
  web:
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt

⚠️ Ловушка: секрет, переданный через ENV в Dockerfile или --build-arg, остаётся в слоях образа и виден в docker history / docker inspect — кто угодно с доступом к образу его прочитает. Секреты для сборки передавай через BuildKit secret mounts и не копируй их содержимое в слои результата. Секреты приложения передавай в рантайме, а .env добавляй в .gitignore и .dockerignore. Переменные окружения видны процессам и в /proc/<pid>/environ, поэтому для высокочувствительных данных лучше файловые секреты или внешний менеджер.

Источники: Документация 1, Документация 2.

15

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

Короткий ответ: Registry хранит образы. Docker Hub, GitHub/GitLab Container Registry и 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 либо запрещай изменение release-тегов, чтобы сохранить воспроизводимость и откат.

Даже тег версии или коммита можно переместить, если registry не запрещает перезапись. Записывай digest развёрнутого образа для воспроизводимости и отката: одно имя тега не доказывает идентичность содержимого.

Источники: Документация 1.

16

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

Короткий ответ: Обычно контейнер организуют вокруг одного сервиса или обязанности, а долговечное состояние осознанно хранят вне заменяемого writable-слоя контейнера. Это рекомендация по проектированию, не запрет на stateful-сервисы или несколько процессов.

Веб-сервис может держать запросы временными, а PostgreSQL — сохранять данные в volume. Контейнер БД остаётся stateful: ему нужны бэкапы, проверки восстановления и продуманный жизненный цикл хранилища. Сохранение volume после замены контейнера не делает его бэкапом и не обещает доступность на любом хосте.

«Один процесс» означает одну связанную ответственность: менеджер с воркерами или сервис с необходимыми вспомогательными процессами могут быть уместны. Важны понятная обработка сигналов, завершение дочерних процессов, логи, healthcheck и независимые границы развёртывания/масштабирования.

Пример: Выделяйте веб, фонового воркера и БД в разные сервисы, если у них разные жизненные циклы. Дополнительные веб-реплики сами по себе не устранят ограничение общей БД или очереди.

Важно: Стандартные потоки вывода и ошибок удобны для сбора логов. Запись в файл тоже возможна при правильно настроенном хранении/сборе. В обоих случаях задайте ротацию, сроки хранения и сбор, а не предполагайте, что это происходит автоматически.

17

Что такое CI и CD?

Короткий ответ: Continuous integration означает частую интеграцию небольших изменений с автоматическими проверками для раннего обнаружения проблем. Continuous delivery поддерживает изменения в готовом к развёртыванию состоянии, чтобы выпускать по решению команды. Continuous deployment автоматически выпускает изменения, прошедшие необходимые проверки.

  • CI: Сборка и тестирование предлагаемых и уже объединённых изменений. CI сама не сливает каждое изменение и не гарантирует корректность main; триггеры и обязательные проверки нужно настроить.
  • Delivery: Получение проверенного артефакта, готового к деплою. Момент production-релиза выбирается; ручное подтверждение — частая реализация.
  • Deployment: Автоматизация production-шага для подходящих изменений. Тесты, проверка выкатки, наблюдаемость и восстановление всё равно нужны.
CI:                    изменение → сборка/тесты → обратная связь
Continuous delivery:   проверенный артефакт → релиз по решению
Continuous deployment: проверенный артефакт → автоматический релиз

Важно: Уточняйте, что команда называет CD. Continuous delivery — возможность надёжно выпускать по требованию, а не обязательная кнопка подтверждения. Автоматизация не доказывает безопасность релиза.

18

Этапы пайплайна: lint → test → build → deploy

Короткий ответ: Типичный пайплайн: lint (статический анализ/стиль) → test (юнит/интеграционные тесты) → build (сборка артефакта/Docker-образа) → deploy (выкатка). Настройте зависимости так, чтобы деплой требовал прохождения нужных проверок; независимые проверки можно запускать параллельно. Некоторым тестам сначала нужна сборка.

Подробно:

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

Стремитесь к быстрой обратной связи, но выбирайте порядок по реальным зависимостям и длительности. gofmt форматирует Go-код; CI должна явно падать при неожиданных изменениях форматирования. По возможности собирайте один раз и продвигайте тот же проверенный артефакт.

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

19

GitHub Actions / GitLab CI кратко

Короткий ответ: Обе системы описывают автоматизацию в YAML репозитория. У GitHub Actions есть workflow, jobs, steps и runners; у GitLab CI/CD — pipeline с jobs, часто сгруппированными в стадии.

Вопрос GitHub Actions GitLab CI/CD
Конфигурация .github/workflows/*.yml .gitlab-ci.yml
Зависимости needs связывает jobs Порядок стадий или граф через needs
Шаги Последовательные steps внутри job script job и связанные хуки
Результаты сборки Явная выгрузка/загрузка артефактов или registry Артефакты объявляются; следующие стадии по умолчанию скачивают артефакты предыдущих, поведение меняют dependencies или needs:artifacts

Пример процесса: Проверить Go-модуль через go test ./..., после успешных тестов собрать бинарник или образ, опубликовать идентифицированный артефакт и развёрнуть через настроенный шлюз окружения. Сборке контейнера нужен доступ к подходящему builder; публикации образа — авторизация в registry.

Важно: Зависимости jobs не создают общую файловую систему. Jobs на GitHub-hosted runner обычно получают свежие окружения; self-hosted runner может сохранять состояние, а в GitLab это зависит от executor. GitHub jobs независимы без needs; GitLab jobs одной стадии могут выполняться конкурентно. Реальный параллелизм определяется доступными runner и лимитами. Храните секреты в защищённых механизмах платформы и ограничивайте доступ недоверенных pull request.

20

Стратегии деплоя: rolling, blue-green, canary

Короткий ответ: Rolling — постепенно заменяем инстансы новой версией по одному. Blue-green — поднимаем полную новую среду (green) рядом со старой (blue) и переключаем трафик целиком. Canary — пускаем новую версию на маленькую долю трафика, наблюдаем, постепенно увеличиваем.

Подробно:

Стратегия Как Плюсы Минусы
Rolling заменяем поды поэтапно может избежать простоя при readiness/draining и достаточном резерве ресурсов две версии работают одновременно, откат медленный
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 без хорошего мониторинга бессмыслен: некому решать, что выкатка «здорова».

Источники: Документация 1.

21

Артефакты и кэш зависимостей в CI

Короткий ответ: Артефакты — именованные результаты запуска, например скомпилированный бинарник или отчёт тестов. Кэши сохраняют переиспользуемые данные для ускорения, например скачанные зависимости или промежуточные результаты компиляции. Корректная сборка должна работать и без кэша.

Для Go-пайплайна кэшируйте модули и результаты компиляции с учётом версии Go, платформы и состояния зависимостей, где это нужно. Всегда выполняйте разрешение и проверку зависимостей: cache hit не доказывает соответствие текущему коду. Публикуйте проверенный бинарник или образ с неизменяемым идентификатором — commit или digest.

Передача зависит от платформы: GitHub Actions использует явные шаги выгрузки/загрузки артефактов. В GitLab jobs объявляют artifacts, а следующие стадии по умолчанию скачивают артефакты предыдущих, если не настроено иначе. Один порядок jobs не задаёт переносимый контракт передачи результатов.

Важно: Хеш lock-файла полезен в ключе, но не обязателен для каждого вида кэша загрузок. Обычный requirements.txt не обязательно полностью фиксирует зависимости. Кэш может быть удалён, а платформы задают квоты и сроки хранения. Не храните единственную копию релиза в кэше и не помещайте секреты в кэш или открытые результаты сборки.

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 означает «можно зайти внутрь / получить доступ к файлам», а не «выполнить». OpenSSH отвергает приватные ключи, доступные другим пользователям; типичные права — 600 или только чтение 400. Важны также доступ к каталогам и ACL.

Источники: Документация 1.

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-архив (gzip-compressed tar, не ZIP)
tar -xzf backup.tar.gz         # распаковать tar-архив, сжатый gzip
tar -tzf backup.tar.gz         # посмотреть содержимое

⚠️ Ловушка: rm -rf не спрашивает подтверждения и не имеет корзины — ошибочный путь может удалить чужие данные. GNU rm обычно отказывается рекурсивно удалять /, но эта защита не защищает остальные каталоги. Мнемоника tar: create / extract, z = gzip, f = file. И kill <pid> шлёт SIGTERM, а не «убивает мгновенно».

Источники: Документация 1, Документация 2.

24

Процессы, сигналы, демоны, systemd

Короткий ответ: Сигналы уведомляют процесс или управляют им. SIGTERM запрашивает завершение и может обрабатываться; SIGINT часто отправляется по Ctrl+C; SIGKILL нельзя перехватить, блокировать или игнорировать. SIGHUP означает перезагрузку конфигурации только у приложений, которые так его обрабатывают.

kill -TERM 1234  # замените 1234 на проверенный PID нужного процесса
kill -HUP 1234   # только если сервис документирует reload по HUP
systemctl status myapp
journalctl -u myapp --since "1 hour ago"

Graceful shutdown реализует приложение: Обработчик SIGTERM может остановить приём работы, ограниченно дождаться запросов, сохранить нужные данные и выйти. Без обработчика действие по умолчанию обычно завершает процесс; один SIGTERM не гарантирует cleanup.

SIGKILL не даёт прикладному коду выполнить cleanup. Доставка/выход не обязаны выглядеть мгновенными, например при непрерываемом ожидании внутри ядра. Ядро освобождает ресурсы процесса при выходе, но незавершённые прикладные действия могут требовать восстановления; не каждый lock или транзакция БД остаются навсегда.

Многие Linux-системы используют systemd, но не все. docker stop отправляет настроенный сигнал остановки (обычно SIGTERM), ждёт настроенный таймаут, затем при необходимости использует SIGKILL. Значения по умолчанию зависят от платформы; для Linux-контейнеров обычно десять секунд.

Важно: Предпочитайте документированный способ остановки сервиса и проверяйте завершение. Принудительно останавливайте только понимая последствия и путь восстановления.

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

# Комбинации
printf '%s\n' banana apple | sort | head -n 1      # связать читающие фильтры
journalctl -u myapp 2>&1 | tail -50

⚠️ Ловушка: порядок в > file 2>&1 важен! Это значит «stdout → file, затем stderr → туда же, куда сейчас указывает stdout (file)». А 2>&1 > file сначала направит stderr на текущий stdout (часто терминал), и только потом stdout в файл — stderr сохраняет прежнее назначение. По умолчанию Bash возвращает код последней команды пайплайна: её успех может скрыть более ранний сбой. С set -o pipefail пайплайн возвращает крайний справа ненулевой код или ноль при успехе всех команд; ${PIPESTATUS[@]} сразу после пайплайна показывает отдельные коды.

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"    # добавить свою папку в начало
command -v python                 # разрешение команды текущим shell

Постоянные переменные — в файлах инициализации shell:

# ~/.bashrc (bash, интерактивный) или ~/.zshrc (zsh)
export PATH="$HOME/.local/bin:$PATH"
export EDITOR=vim

source ~/.bashrc    # для Bash; выбирайте файл своего shell

⚠️ Ловушка: новая неэкспортированная переменная shell не наследуется дочерними процессами. Уже экспортированная переменная сохраняет атрибут export при переприсваивании. 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. В Linux порт ниже настроенного unprivileged-port порога требует соответствующих прав, например CAP_NET_BIND_SERVICE; запускать всё приложение от root не обязательно.

Источники: Документация 1.

28

Что такое nginx?

Короткий ответ: Nginx — HTTP-сервер и обратный прокси. Он может отдавать файлы, пересылать запросы, завершать TLS, распределять нагрузку, кэшировать ответы и применять настроенные лимиты. Это возможный компонент развёртывания, а не обязательное условие работы Go HTTP-сервиса.

# Внутри контекста http; бэкенды уже должны работать.
upstream backend {
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
}
server {
    listen 127.0.0.1:8080;
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Этот локальный HTTP-пример сохраняет префикс /api/. Добавление URI / в proxy_pass http://backend/ заменяет совпавший префикс location. Для публичного HTTPS нужны TLS-listener и корректные сертификаты; соединение с бэкендом может быть HTTP или HTTPS в зависимости от границы доверия.

Важно: Прокси-заголовки содержат утверждения об исходном соединении. Настройте доверие к ним только от известных прокси и обработку клиентских заголовков на входной границе. Прокси не скрывает бэкенд, чей порт доступен отдельно. До reload проверяйте конфигурацию через nginx -t, затем проверяйте реальные маршруты.

29

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

Короткий ответ: nginx — необязательный веб-сервер и reverse proxy; Gunicorn/Uvicorn — серверы Python-приложений. Они решают разные задачи. Go-сервис обычно использует собственный net/http и не требует WSGI или ASGI.

  • Прокси может централизовать TLS, маршрутизацию, буферизацию и лимиты перед несколькими экземплярами приложения.
  • Gunicorn запускает WSGI-приложения Python с выбранной моделью воркеров. WSGI синхронен на уровне интерфейса, но конкурентность зависит от процессов и потоков.
  • Uvicorn обслуживает ASGI-приложения, включая async HTTP и WebSocket. Это production-сервер, а --reload — опция разработки.
клиент → необязательный proxy/LB → Go net/http приложение
клиент → необязательный proxy/LB → Uvicorn → ASGI приложение

Выбор: добавляй прокси или управляемый edge под конкретные требования, а не потому, что всем нужен nginx. Настрой доверенные forwarded headers, лимиты запросов, таймауты и graceful shutdown. Число воркеров определяй по памяти и нагрузке; формула по CPU не универсальна. Не копируй старую команду Gunicorn/Uvicorn без проверки установленных версий и актуальной документации.

Источники: Документация 1, Документация 2.

30

Kubernetes кратко

Короткий ответ: Kubernetes оркестрирует контейнерные нагрузки, приводя их к объявленному желаемому состоянию. Контроллеры, сеть, хранилище и политики нужно настроить; автоматическое восстановление и масштабирование работают в рамках этих настроек и доступной инфраструктуры.

  • Pod: Минимальная разворачиваемая единица: один или несколько тесно связанных контейнеров с общей сетью и явно настроенными общими томами.
  • Deployment: Поддерживает нужное число заменяемых реплик Pod и управляет изменением их шаблона, часто через rolling-обновления.
  • Service: Даёт стабильный способ обнаружения и доступа к меняющемуся набору endpoint. Обычный ClusterIP Service имеет виртуальный IP и распределяет соединения по готовым endpoint; headless Service работает иначе.
  • Ingress: Объявляет HTTP(S)-маршруты; для реализации нужен установленный контроллер. Другой вариант маршрутизации — Gateway API.
# Минимальный пример Deployment; имя образа — заглушка.
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 }]

Здесь задано желаемое число реплик — три. Пример не добавляет реплики при росте нагрузки и не создаёт публичную точку доступа. Автоскейлинг требует настройки, например HorizontalPodAutoscaler, и подходящих метрик. Для доступности в production нужны также readiness-проверки, ресурсы, корректное завершение и проверенное поведение при обновлении.

Когда использовать: Сопоставьте нужные возможности оркестрации, способность команды поддерживать систему и существующую платформу. Небольшой сервис может работать в Kubernetes, если платформа уже есть; отдельному маленькому проекту может быть проще с Compose или managed-сервисом приложений. Одно число контейнеров не определяет выбор.

31

Облака кратко (compute, storage, managed db)

Короткий ответ: Облака дают вычисления, хранение, сеть и managed-сервисы по запросу. «Managed» описывает обязанности провайдера, а не означает, что все решения по доступности, бэкапам, масштабированию и безопасности автоматические.

Потребность Пример AWS Что всё ещё выбираете/проверяете вы
Вычисления VM EC2, функции Lambda, контейнеры ECS/EKS Ресурсы, права, развёртывание, поддерживаемый рантайм и лимиты
Хранение Объекты S3, блочные EBS, файлы EFS Доступ, сроки хранения, шифрование, долговечность/доступность и стоимость
База данных Реляционные RDS, DynamoDB Схема, нагрузка, доступ, политика бэкапов/восстановления и настройки сервиса

Managed и self-hosted: База на своей VM даёт больше контроля и больше сопровождения. Managed-БД автоматизирует определённые операции. Полная стоимость зависит от нагрузки, команды, требований доступности и выбора сервиса; ни один вариант не всегда дешевле.

Например, обычные RDS Single-AZ и Multi-AZ восстанавливаются по-разному. Конфигурация Multi-AZ даёт standby и автоматический failover для предусмотренных сбоев; это не свойство каждой managed-БД. Проверки восстановления и переподключение приложения остаются важны.

Важно: Проверяйте конкретный продукт, режим развёртывания и модель общей ответственности. Учитывайте передачу данных, хранение, I/O и бэкапы, имейте план переноса/восстановления. Безопасность инфраструктуры провайдера не заменяет ваши обязанности по идентификации, доступу, настройкам и данным.

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/Grafana Alloy) отправляет в хранилище.
  • Трейсы — записывают операции и связи между ними в запросе. OpenTelemetry даёт инструменты и API инструментации/сбора, а backend вроде Jaeger хранит и показывает трейсы. Видимость зависит от инструментирования и семплирования.
  • Алертинг (Alertmanager) — уведомления (Slack/PagerDuty) при срабатывании правил.

Полезные понятия: SLI/SLO/SLA (показатели/цели/обязательства по надёжности), «четыре золотых сигнала» — latency, traffic, errors, saturation.

⚠️ Ловушка: неструктурированные и локальные логи тоже могут помочь, но единые поля, временные метки и request/trace ID упрощают диагностику между сервисами. Удаляйте секреты, минимизируйте чувствительные данные, настраивайте доступ и сроки хранения. Наблюдаемость — возможность отвечать на полезные вопросы, не фиксированный набор инструментов. Promtail достиг конца поддержки в марте 2026 года; для новых установок выбирайте поддерживаемый сборщик.

33

Зачем Docker, если есть venv?

Короткий ответ: Python venv выбирает интерпретатор и изолирует Python-пакеты. Контейнер упаковывает файлы и зависимости пользовательского пространства и запускает процесс с изоляцией средствами ОС. Это разные границы.

Для Go-разработчика аналогичное различие — управление зависимостями через go.mod/go.sum и среда запуска собранного сервиса. Go-бинарнику могут требоваться динамические библиотеки через cgo, сертификаты CA, данные часовых поясов и внешние сервисы. Контейнер позволяет согласованно упаковать эти файлы.

Контейнер не включает новое ядро, не выдаёт секреты автоматически и не запускает один образ на любой архитектуре CPU. Linux-образу нужны совместимое ядро Linux или Linux-VM и сборка для нужной платформы. Compose запускает соседние БД/кэш, но конфигурацию и готовность приложения нужно учитывать отдельно.

Пример: зависимости на ноутбуке разрешаются правильно, но HTTPS-запросы в минимальном образе не работают из-за отсутствующего CA bundle. Изоляция зависимостей языка не решает эту задачу рантайма.

Источники: Документация 1, Документация 2.

34

Зачем вообще CI/CD?

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

Подробно:

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

С CI/CD:

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

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

35

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

Короткий ответ: Nginx может централизовать обратное проксирование, TLS, буферизацию, лимиты и распределение трафика перед приложением. Нужен ли он, зависит от развёртывания: Go-сервер или managed-балансировщик уже могут давать необходимые функции.

# Минимальный локальный пример; приложение слушает 127.0.0.1:8000.
events {}
http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    upstream app_backend { server 127.0.0.1:8000; }
    server {
        listen 127.0.0.1:8080;
        location / {
            limit_req zone=api burst=20;
            limit_req_status 429;
            client_max_body_size 10M;
            proxy_pass http://app_backend;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Объявление limit_req_zone создаёт именованную область учёта; один limit_req zone=api без неё — неполная конфигурация. В примере всплеск может задерживаться, а превышение лимита получает 429. Ключ — IP прямого соседа; за другим прокси нужно осознанно настроить доверенное определение IP клиента.

Ограничения: Буферизация и rate limit помогают только для покрытого конфигурацией трафика. Они не гарантируют защиту от всех злоупотреблений, не ускоряют вычисления Go и не чинят медленную БД. HTTPS, streaming/WebSocket и сбои upstream требуют отдельных настроек и проверок. Не дублируйте уже существующую входную инфраструктуру без конкретной пользы.

Источники

Источники и редакционная политика

Ссылки на источники помогают изучить тему глубже. Если для ответа опубликована техническая проверка, рядом указаны её дата и область. Мы не связаны с упомянутыми работодателями и не продаём места в подборках. Источники, функции сервисов и формат найма могут меняться.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

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

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