Модуль 06 — Контейнеры (Docker) ⭐⭐

Контейнеры — стандарт упаковки приложений с 2014 года. База для Kubernetes. Без них шага в современный DevOps не сделать.

Что такое контейнер на самом деле

Контейнер — это не виртуальная машина. Это обычный Linux-процесс, изолированный двумя ядерными механизмами:

Образ — это слоёная файловая система (UnionFS / OverlayFS). Контейнер — это процесс, который видит этот образ как корневую ФС + свой writable слой сверху.

Стандарты OCI

Docker — лишь одна из реализаций. Альтернативы: Podman (rootless), containerd, CRI-O.

Docker CLI — основные команды

docker run -d --name web -p 8080:80 nginx:1.27       # запустить
docker ps                                              # активные контейнеры
docker ps -a                                           # все, включая остановленные
docker logs -f web                                     # логи (follow)
docker exec -it web sh                                 # войти внутрь
docker stats                                           # real-time метрики
docker stop web && docker rm web

docker images                                          # список образов
docker pull alpine:3.20
docker rmi unused:tag

docker system df                                       # использование диска
docker system prune -a --volumes                       # очистить всё

Dockerfile — главное мастерство

Простой пример

FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ ./src/
EXPOSE 8000
CMD ["python", "-m", "src.main"]

Production-grade Dockerfile (multi-stage, non-root)

# syntax=docker/dockerfile:1.7

# ============ Stage 1: build ============
FROM python:3.12-slim AS builder

WORKDIR /app
RUN pip install --no-cache-dir uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-cache --no-dev

# ============ Stage 2: runtime ============
FROM python:3.12-slim AS runtime

# non-root для безопасности
RUN useradd -r -u 1000 -m appuser
WORKDIR /app

# только нужное из builder'а
COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv
COPY --chown=appuser:appuser src/ ./src/

USER appuser
ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

EXPOSE 8000
ENTRYPOINT ["python", "-m", "src.main"]

Ключевые принципы

  1. Multi-stage build — отделяй build-зависимости от runtime. Финальный образ компактный.
  2. Slim/distroless базы — python:3.12-slim, gcr.io/distroless/static. Меньше = безопаснее (меньше CVE).
  3. Non-root user — обязательно. USER 1000 или USER nonroot.
  4. Кеш слоёв: ставь часто меняющиеся файлы (исходники) в конце Dockerfile.
  5. .dockerignore — не тащи node_modules, .git, tests/, .env.
  6. Pinning версий: FROM python:3.12.5-slim, не :latest. Лучше: pin по digest sha256:....
  7. Очистка: apt-get clean && rm -rf /var/lib/apt/lists/*.
  8. Один процесс на контейнер. Не пихай postgres + nginx в один.
  9. BuildKit secretsRUN --mount=type=secret,id=npm,target=/.npmrc. Никаких секретов в layers.

Команды CMD vs ENTRYPOINT

КонструкцияЧто делаетКогда
CMD ["a", "b"]Default команда, переопределяется аргументамиПростые образы
ENTRYPOINT ["bin"] + CMD ["arg"]Bin фиксирован, args меняются«Контейнер как команда»
CMD bin arg (shell-form)Запуск через sh -cПочти никогда — теряется PID 1

Docker Compose

Локальный orchestrator — несколько контейнеров одной командой.

# compose.yaml
services:
  api:
    build: .
    ports: ["8000:8000"]
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes: [db_data:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      retries: 5

  cache:
    image: redis:7-alpine

volumes:
  db_data:
docker compose up -d           # запустить в фоне
docker compose logs -f api     # логи сервиса
docker compose ps
docker compose down -v         # остановить + удалить volumes

Volumes и сети

Volumes

Сети

Container Registries

Безопасность образов ⭐

Сканеры уязвимостей

# Trivy — must-have в pipeline
trivy image myapp:1.0
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:1.0
trivy fs .                          # сканировать файловую систему
trivy config Dockerfile             # misconfig в Dockerfile

SBOM — Software Bill of Materials

Список всех компонентов в образе. Стандарт supply chain security.

syft myapp:1.0 -o spdx-json > sbom.json
syft myapp:1.0 -o cyclonedx-json > sbom.cdx.json
grype sbom:./sbom.json               # сканировать SBOM на CVE

Cosign — подпись образов

Подписываешь → пушишь подпись в registry → kubernetes admission controller проверяет подпись перед запуском.

# Keyless signing через GitHub OIDC (рекомендуется)
cosign sign ghcr.io/me/myapp:1.0

# Верификация
cosign verify \
  --certificate-identity=https://github.com/me/myapp/.github/workflows/ci.yml@refs/heads/main \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  ghcr.io/me/myapp:1.0

Альтернативы Docker

ИнструментОсобенностьКогда
PodmanDaemonless, rootless, Docker-CLI compatБезопасность, RHEL экосистема
BuildahТолько сборка, без daemonВ CI
Kaniko (Google)Сборка в контейнере без daemonK8s pipelines
nerdctlКлиент для containerdНизкоуровневая работа
BuildKitDocker build engine v2Уже у тебя по умолчанию

Современные темы

Чек-лист

Источники

Контейнеры освоены. Дальше — облако.

Модуль 07 → AWS