Цели модуля
После модуля студент сможет:
- Понимать зачем нужна контейнеризация и как она работает
- Писать multi-stage Dockerfile для Go-сервисов
- Оптимизировать размер Docker-образа
- Настраивать Docker Compose для локальной разработки
- Реализовывать health checks в контейнерах
- Сканировать образы на уязвимости через Trivy
- Применять best practices контейнеризации
10.1 Зачем нужна контейнеризация
Проблемы без контейнеризации
- "Работает на моей машине" — разные окружения
- Зависимости конфликтуют между проектами
- Сложно воспроизвести production окружение
- Долгая настройка нового разработчика
- Несовместимость версий библиотек
Что даёт контейнеризация
- Воспроизводимость: одинаковое окружение везде
- Изоляция: каждый сервис в своём контейнере
- Переносимость: один образ работает на Linux, macOS, в облаке
- Масштабируемость: легко запускать несколько инстансов
- CI/CD: артефакт сборки — Docker образ
Docker vs Виртуальные машины
| Критерий | Docker | VM |
|---|---|---|
| Изоляция | Процесс (namespace) | Полная (гипервизор) |
| Размер | Мегабайты | Гигабайты |
| Запуск | Секунды | Минуты |
| Ресурсы | Минимальные | Значительные |
| ОС | Общая с хостом | Отдельная |
Docker контейнеры легче и быстрее VM, но менее изолированы. Для большинства backend-сервисов Docker достаточно.
Основные концепции Docker
| Концепция | Назначение |
|---|---|
| Image | Шаблон для создания контейнеров |
| Container | Запущенный экземпляр image |
| Dockerfile | Инструкция для сборки image |
| Registry | Хранилище images (Docker Hub, ECR, GCR) |
| Volume | Персистентное хранилище |
| Network | Сеть между контейнерами |
10.2 Docker basics для Go
Установка Docker
Linux:
bashcurl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER newgrp docker
Проверка:
bashdocker --version docker run hello-world
Основные команды
bash# Сборка образа docker build -t myapp:latest . # Запуск контейнера docker run -p 8080:8080 myapp:latest # Список контейнеров docker ps # Остановка контейнера docker stop <container_id> # Логи контейнера docker logs <container_id> # Вход в контейнер docker exec -it <container_id> /bin/sh # Удаление образа docker rmi myapp:latest
10.3 Multi-stage Dockerfile
Зачем нужен multi-stage build
Go-проект требует:
- Go toolchain для компиляции (сотни MB)
- Только бинарник для запуска (десятки MB)
Multi-stage build разделяет эти этапы:
- Stage 1 (builder): компиляция с Go toolchain
- Stage 2 (runtime): только бинарник в slim image
Результат: образ 10-20 MB вместо 800+ MB.
Пример multi-stage Dockerfile
dockerfile# Stage 1: Builder FROM golang:1.23-alpine AS builder # Установка зависимостей сборки RUN apk add --no-cache git ca-certificates WORKDIR /app # Кэширование зависимостей COPY go.mod go.sum ./ RUN go mod download # Копирование исходников COPY . . # Сборка бинарника RUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-w -s" \ -o /app/server \ ./cmd/app # Stage 2: Runtime FROM alpine:3.19 # Установка ca-certificates для HTTPS RUN apk add --no-cache ca-certificates tzdata # Создание non-root пользователя RUN adduser -D -u 1000 appuser # Копирование бинарника из builder COPY /app/server /usr/local/bin/server # Переключение на non-root пользователя USER appuser # Порт EXPOSE 8080 # Health check HEALTHCHECK \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/healthz || exit 1 # Запуск ENTRYPOINT ["server"]
Разбор Dockerfile
FROM golang:1.23-alpine AS builder Базовый образ с Go toolchain. alpine — минимальный дистрибутив.
COPY go.mod go.sum ./ RUN go mod download Копируем только файлы зависимостей и скачиваем их. Этот слой кэшируется — при изменении только .go файлов зависимости не будут скачиваться заново.
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/server ./cmd/app
- CGO_ENABLED=0: статическая линковка (не нужны shared libraries)
- GOOS=linux: целевая ОС Linux
- -ldflags="-w -s": убирает debug info, уменьшает бинарник
FROM alpine:3.19 Минимальный runtime образ (~5 MB).
RUN adduser -D -u 1000 appuser Создание non-root пользователя для безопасности.
COPY --from=builder /app/server /usr/local/bin/server Копируем только бинарник из builder stage.
USER appuser Запуск от non-root пользователя. Если процесс скомпрометирован, атакующий не получит root.
Альтернатива: distroless
Для ещё меньшего образа можно использовать distroless:
dockerfileFROM gcr.io/distroless/static-debian12 COPY /app/server /server USER nonroot:nonroot ENTRYPOINT ["/server"]
Distroless образы не содержат shell, package manager, и других утилит. Минимальная поверхность атаки.
Сравнение размеров:
| Base image | Размер |
|---|---|
| golang:1.23 | ~800 MB |
| alpine:3.19 | ~5 MB |
| distroless/static | ~2 MB |
| scratch | 0 MB |
10.4 Оптимизация образа
Кэширование слоев
Docker кэширует каждый слой. Порядок инструкций влияет на кэш.
dockerfile# ПЛОХО: любой change в коде инвалидирует кэш зависимостей COPY . . RUN go mod download
dockerfile# ПРАВИЛЬНО: зависимости кэшируются отдельно COPY go.mod go.sum ./ RUN go mod download COPY . .
Уменьшение размера бинарника
dockerfile# Убираем debug info и symbol table RUN go build -ldflags="-w -s" -o server ./cmd/app # Убираем CGO (статическая линковка) RUN CGO_ENABLED=0 go build -o server ./cmd/app # Указываем целевую ОС и архитектуру RUN GOOS=linux GOARCH=amd64 go build -o server ./cmd/app
.dockerignore
Файл .dockerignore исключает файлы из контекста сборки:
# Git
.git
.gitignore
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# IDE
.idea
.vscode
*.swp
# Тесты и документация
*_test.go
docs/
*.md
# Бинарники
bin/
dist/
# Локальные конфиги с секретами
.env
configs/local/
# Временные файлы
tmp/
*.log
Преимущества:
- Быстрее сборка (меньше контекст)
- Секреты не попадают в образ
- Тесты и доки не включаются
Проверка размера образа
bashdocker images myapp docker history myapp # размер каждого слоя
Целевые размеры:
- Go сервис: 10-30 MB
- С зависимостями: до 50 MB
- Больше 100 MB — повод оптимизировать
10.5 Docker Compose для локальной разработки
Зачем нужен Docker Compose
Локальная разработка требует:
- Приложение
- База данных (PostgreSQL)
- Кэш (Redis)
- Очередь (RabbitMQ/Kafka)
- Мониторинг (Prometheus, Grafana)
Docker Compose запускает всё одной командой.
Пример docker-compose.yml
yamlversion: "3.9" services: app: build: context: . dockerfile: docker/Dockerfile ports: - "8080:8080" environment: - APP_DATABASE_DSN=postgres://app:secret@postgres:5432/mydb?sslmode=disable - APP_LOG_LEVEL=debug - APP_LOG_FORMAT=text depends_on: postgres: condition: service_healthy volumes: - ./configs:/app/configs:ro postgres: image: postgres:16-alpine environment: - POSTGRES_USER=app - POSTGRES_PASSWORD=secret - POSTGRES_DB=mydb ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data - ./migrations:/docker-entrypoint-initdb.d healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d mydb"] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine ports: - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 5 prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ./docker/prometheus.yml:/etc/prometheus/prometheus.yml grafana: image: grafana/grafana:latest ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin depends_on: - prometheus volumes: postgres_data:
Использование
bash# Запуск всех сервисов docker compose up -d # Запуск с пересборкой docker compose up -d --build # Логи docker compose logs -f app # Остановка docker compose down # Остановка с удалением volumes docker compose down -v # Статус docker compose ps
depends_on с healthcheck
yamldepends_on: postgres: condition: service_healthy
Приложение не запустится пока PostgreSQL не пройдёт healthcheck. Это предотвращает ошибки подключения при старте.
Override для продакшена
yaml# docker-compose.prod.yml version: "3.9" services: app: image: registry.example.com/myapp:${VERSION} restart: always environment: - APP_LOG_LEVEL=info - APP_LOG_FORMAT=json
Запуск с override:
bashdocker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
10.6 Health checks в контейнерах
Зачем нужны health checks
Docker и Kubernetes используют health checks для:
- Определения что контейнер жив (liveness)
- Определения что контейнер готов к трафику (readiness)
- Автоматического перезапуска при сбое
HEALTHCHECK в Dockerfile
dockerfileHEALTHCHECK \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/healthz || exit 1
Параметры:
- interval: как часто проверять
- timeout: максимальное время проверки
- start-period: время на инициализацию (проверки не считаются)
- retries: сколько провалов до статуса unhealthy
Варианты health check команд
dockerfile# wget (alpine) CMD wget --no-verbose --tries=1 --spider http://localhost:8080/healthz || exit 1 # curl (debian-based) CMD curl -f http://localhost:8080/healthz || exit 1 # Встроенный бинарник (если нет wget/curl) CMD ["/server", "healthcheck"]
Health check через бинарник
Для distroless образов нет wget/curl. Добавьте команду в бинарник:
go// cmd/app/main.go func main() { if len(os.Args) > 1 && os.Args[1] == "healthcheck" { resp, err := http.Get("http://localhost:8080/healthz") if err != nil || resp.StatusCode != 200 { os.Exit(1) } os.Exit(0) } // Обычный запуск сервера runServer() }
dockerfileHEALTHCHECK CMD ["/server", "healthcheck"]
Проверка статуса
bashdocker inspect --format='{{.State.Health.Status}}' <container_id>
Статусы:
- starting: инициализация
- healthy: проверки проходят
- unhealthy: проверки проваливаются
10.7 Security scanning (Trivy)
Зачем сканировать образы
Образы могут содержать уязвимости:
- В базовом образе (alpine, debian)
- В зависимостях Go (go.sum)
- В системных пакетах
Сканирование находит известные CVE до деплоя.
Установка Trivy
bash# macOS brew install trivy # Linux curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
Сканирование образа
bashtrivy image myapp:latest
Пример вывода:
textmyapp:latest (alpine 3.19.0) ============================= Total: 2 (HIGH: 1, CRITICAL: 1) ┌──────────────┬────────────────┬──────────┬────────┬───────────────────┐ │ Library │ Vulnerability │ Severity │ Status │ Fixed Version │ ├──────────────┼────────────────┼──────────┼────────┼───────────────────┤ │ openssl │ CVE-2024-0001 │ CRITICAL │ fixed │ 3.1.4-r1 │ │ zlib │ CVE-2024-0002 │ HIGH │ fixed │ 1.3-r0 │ └──────────────┴────────────────┴──────────┴────────┴───────────────────┘
Сканирование с фильтрацией
bash# Только HIGH и CRITICAL trivy image --severity HIGH,CRITICAL myapp:latest # Фейл при наличии CRITICAL trivy image --exit-code 1 --severity CRITICAL myapp:latest # JSON формат для CI trivy image --format json --output results.json myapp:latest
Сканирование файловой системы
bash# Сканирование Go зависимостей trivy fs --security-checks vuln ./ # Сканирование конфигураций trivy config ./
Trivy в CI
yaml# GitHub Actions - name: Trivy scan uses: aquasecurity/trivy-action@master with: image-ref: myapp:latest format: table exit-code: 1 severity: CRITICAL,HIGH
Правило
Если Trivy находит CRITICAL уязвимость — деплой блокируется. HIGH — фиксируется в issue, исправляется в следующем спринте.
10.8 Best practices
1. Non-root пользователь
dockerfile# ПЛОХО: запуск от root FROM alpine:3.19 COPY server /server CMD ["/server"]
dockerfile# ПРАВИЛЬНО: non-root пользователь FROM alpine:3.19 RUN adduser -D -u 1000 appuser COPY server /server USER appuser CMD ["/server"]
Если процесс скомпрометирован, атакующий не получит root доступ.
2. Минимальный базовый образ
dockerfile# ПЛОХО: полный Debian FROM debian:bookworm
dockerfile# ПРАВИЛЬНО: alpine или distroless FROM alpine:3.19 FROM gcr.io/distroless/static-debian12
Меньше пакетов = меньше уязвимостей = меньше размер.
3. Фиксированные версии
dockerfile# ПЛОХО: latest FROM golang:latest
dockerfile# ПРАВИЛЬНО: конкретная версия FROM golang:1.23.4-alpine3.19
latest может измениться и сломать сборку.
4. Не хранить секреты в образе
dockerfile# ПЛОХО: секрет в Dockerfile ENV API_KEY=sk-123456
dockerfile# ПРАВИЛЬНО: секрет передаётся в runtime docker run -e API_KEY=$API_KEY myapp
Или через secret manager в production.
5. Multi-stage build
Всегда используйте multi-stage build для Go:
dockerfileFROM golang:1.23-alpine AS builder # ... компиляция
dockerfileFROM alpine:3.19 COPY /app/server /server
6. Кэширование зависимостей
dockerfileCOPY go.mod go.sum ./ RUN go mod download COPY . .
Зависимости кэшируются, пересборка только при изменении go.mod.
7. .dockerignore
Всегда создавайте .dockerignore:
- Исключает .git, .env, тесты, документацию
- Ускоряет сборку
- Предотвращает утечку секретов
8. HEALTHCHECK
Всегда добавляйте HEALTHCHECK:
dockerfileHEALTHCHECK \ CMD wget --spider http://localhost:8080/healthz || exit 1
9. LABELS для метаданных
dockerfileLABEL maintainer="team@example.com" LABEL version="1.0.0" LABEL description="Repo management service"
10. Сканирование в CI
Добавьте Trivy в CI pipeline:
yaml- name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Scan with Trivy run: trivy image --exit-code 1 --severity CRITICAL myapp:${{ github.sha }} - name: Push to registry run: docker push registry.example.com/myapp:${{ github.sha }}
Привязка к шаблону проекта
Где Docker в шаблоне
bashdocker/ ├── Dockerfile # Multi-stage build ├── docker-compose.yml # Локальная разработка ├── docker-compose.prod.yml # Production override └── prometheus.yml # Конфиг Prometheus .dockerignore # Исключения для сборки
Что изучаем на примере шаблона
- Multi-stage Dockerfile с кэшированием
- Non-root пользователь
- HEALTHCHECK для liveness
- Docker Compose с PostgreSQL
- .dockerignore для безопасности
Задание: изучите docker/
- Какие stages в Dockerfile?
- Есть ли non-root пользователь?
- Есть ли HEALTHCHECK?
- Какие сервисы в docker-compose?
- Что исключено в .dockerignore?
💱 Компромиссы
- Docker vs виртуальные машины: контейнер весит мегабайты и стартует за секунды, но изоляция ограничена namespace процесса; VM даёт полную изоляцию гипервизора, расплачиваясь гигабайтами образа и минутами запуска.
- alpine vs distroless: в
alpine:3.19есть shell иwgetдляHEALTHCHECKи отладки черезdocker exec; distroless (~2 MB) даёт минимальную поверхность атаки, но health check возможен только встроенной командой/server healthcheck. - Фиксированные версии vs
latest:golang:1.23.4-alpine3.19воспроизводим, но security-обновления базового образа придётся подтягивать руками;latestобновляется сам — и так же сам ломает сборку. -ldflags="-w -s"vs диагностика: минус debug info и symbol table заметно сжимает бинарник и образ, но дамп крашнувшегося процесса уже не поможет разобраться в инциденте.- Частый HEALTHCHECK vs экономия:
interval=30sс тремяretriesбыстрее объявляет контейнерunhealthyи раньше триггерит перезапуск, но каждый прогон тратит ресурсы; редкие проверки дешевле, однако мёртвый контейнер дольше принимает трафик.
Практическое задание
Задание 1: Multi-stage Dockerfile
Напишите Dockerfile для вашего сервиса:
dockerfile# Stage 1: Builder FROM golang:1.23-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server ./cmd/app # Stage 2: Runtime FROM alpine:3.19 RUN apk add --no-cache ca-certificates RUN adduser -D -u 1000 appuser COPY /app/server /usr/local/bin/server USER appuser EXPOSE 8080 HEALTHCHECK \ CMD wget --spider http://localhost:8080/healthz || exit 1 ENTRYPOINT ["server"]
Проверьте:
- docker build -t myapp:latest .
- docker run -p 8080:8080 myapp:latest
- curl http://localhost:8080/healthz
Задание 2: Оптимизация размера
Соберите образ и проверьте размер:
bashdocker images myapp
Цель: меньше 30 MB.
Оптимизируйте:
- Используйте alpine или distroless
- Добавьте -ldflags="-w -s"
- Создайте .dockerignore
Задание 3: Docker Compose
Создайте docker-compose.yml:
yamlversion: "3.9" services: app: build: . ports: - "8080:8080" environment: - APP_DATABASE_DSN=postgres://app:secret@postgres:5432/mydb depends_on: postgres: condition: service_healthy postgres: image: postgres:16-alpine environment: - POSTGRES_USER=app - POSTGRES_PASSWORD=secret - POSTGRES_DB=mydb healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d mydb"] interval: 5s timeout: 5s retries: 5
Проверьте:
- docker compose up -d
- docker compose ps
- docker compose logs app
Задание 4: Health check
Добавьте healthcheck в ваше приложение:
gofunc (h *Handler) Healthz(w http.ResponseWriter, r *http.Request) { h.writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (h *Handler) Readyz(w http.ResponseWriter, r *http.Request) { if err := h.db.Ping(r.Context()); err != nil { h.writeJSON(w, http.StatusServiceUnavailable, map[string]string{"status": "not ready"}) return } h.writeJSON(w, http.StatusOK, map[string]string{"status": "ready"}) }
Добавьте HEALTHCHECK в Dockerfile.
Задание 5: Trivy scanning
Установите Trivy и просканируйте образ:
bashtrivy image myapp:latest trivy image --severity HIGH,CRITICAL myapp:latest
Исправьте найденные уязвимости:
- Обновите базовый образ
- Обновите зависимости: go get -u ./...
Задание 6: CI pipeline
Добавьте в GitHub Actions:
yamlname: Docker on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Scan with Trivy uses: aquasecurity/trivy-action@master with: image-ref: myapp:${{ github.sha }} exit-code: 1 severity: CRITICAL
Задание 7: .dockerignore
Создайте .dockerignore:
.git
.gitignore
Dockerfile
docker-compose.yml
.dockerignore
.idea
.vscode
*_test.go
docs/
*.md
bin/
.env
tmp/
Пересоберите образ и убедитесь что размер уменьшился.
Quiz "Проверь себя"
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/appFROM golang:1.22-alpine AS builder
RUN CGO_ENABLED=0 go build -o /server ./cmd/app
FROM alpine:3.19
COPY /server /usr/local/bin/serverdepends_on без условия недостаточно в docker-compose?latest для базового образа в production?.env, .git, docs/?✅ Итоги модуля
Студент должен:
- Понимать зачем нужна контейнеризация
- Писать multi-stage Dockerfile для Go
- Оптимизировать размер Docker-образа
- Настраивать Docker Compose для локальной разработки
- Реализовывать health checks в контейнерах
- Сканировать образы через Trivy
- Применять best practices (non-root, slim images, .dockerignore)
- Ответить на все вопросы Quiz
Итог Курса 2
Поздравляем! Вы завершили Курс 2: Go Backend Developer.
Что вы освоили:
- API Design Fundamentals
- Виды организации проектов
- Clean Architecture Deep Dive
- Configuration Management
- Database Access: PostgreSQL
- Business Logic Layer
- Observability
- Background Processing
- API Security
- Docker и контейнеризация
Вы можете:
- Проектировать REST API по best practices
- Применять Clean Architecture
- Работать с PostgreSQL на продвинутом уровне
- Проектировать бизнес-логику
- Настраивать observability
- Реализовывать фоновую обработку
- Обеспечивать безопасность API
- Контейнеризировать сервисы
🔗 Мостик к Модулю 11
Следующий шаг: Bridge Course 2-3: Production Readiness (2 недели)
Фокус:
- Деплой сервиса в staging
- Настройка CI/CD (GitHub Actions)
- Monitoring (Prometheus + Grafana)
- Alerting
- Симуляция инцидента
- Написание runbook
- Ретроспектива
После Bridge: Курс 3: Go Service Architect (Senior)
- Senior Go Architecture
- Advanced API Design
- Distributed Systems Patterns
- Platform and Deployment Thinking
- Observability at Scale
- Security and Secrets
- Storage and Data Strategy
- Reliability and Operations
- Performance and Load Testing
- CI/CD and Delivery
- FinOps and Cost Optimization
- Architecture Documentation