Написать код — это половина дела. Вторая половина — доставить его до продакшена надёжно, безопасно и воспроизводимо. В этой главе разберём весь путь Go-приложения от go build до работающего кластера Kubernetes с мониторингом, автоскейлингом и CI/CD пайплайном.
Go идеально подходит для контейнеризации: статический бинарник без зависимостей можно запустить в минимальном образе размером несколько мегабайт. Это даёт преимущества в скорости деплоя, потреблении ресурсов и безопасности.
Go компилирует код в единый бинарный файл. Это главное преимущество для деплоя — не нужно устанавливать runtime, виртуальные машины или интерпретаторы.
По умолчанию Go может линковать некоторые системные библиотеки динамически (через CGO). Для контейнеров лучше собирать полностью статический бинарник:
# Отключаем CGO для полностью статического бинарникаCGO_ENABLED=0 go build -o ./bin/myapp ./cmd/myapp
CGO_ENABLED=0
Если ваш код или зависимости используют CGO (например, SQLite через mattn/go-sqlite3), отключение CGO сломает сборку. В таком случае используйте alpine с musl или собирайте с CGO_ENABLED=1 и --static флагами линковщика.
Флаги линковщика
Флаги -ldflags позволяют уменьшить размер бинарника и встроить информацию о сборке:
// internal/version/version.gopackage version// Переменные, которые будут установлены при сборкеvar ( Version = "dev" // версия приложения BuildTime = "unknown" // время сборки GitCommit = "unknown" // хеш коммита)
Go поддерживает кросс-компиляцию «из коробки» — можно собирать бинарники для любой платформы с любой машины:
# Сборка под Linux (для Docker/серверов)GOOS=linux GOARCH=amd64 go build -o ./bin/myapp-linux ./cmd/myapp# Сборка под macOS (Apple Silicon)GOOS=darwin GOARCH=arm64 go build -o ./bin/myapp-darwin ./cmd/myapp# Сборка под WindowsGOOS=windows GOARCH=amd64 go build -o ./bin/myapp.exe ./cmd/myapp
Таблица популярных комбинаций GOOS/GOARCH
GOOS
GOARCH
Описание
linux
amd64
Серверы, Docker (x86-64)
linux
arm64
AWS Graviton, Raspberry Pi 4
linux
arm
Raspberry Pi 3 и старше
darwin
amd64
macOS (Intel)
darwin
arm64
macOS (Apple Silicon M1/M2/M3)
windows
amd64
Windows x86-64
windows
arm64
Windows on ARM
freebsd
amd64
FreeBSD серверы
js
wasm
WebAssembly (запуск в браузере)
wasip1
wasm
WASI (серверный WebAssembly)
Build tags
Build tags позволяют включать или исключать файлы из сборки по условию:
//go:build linux && amd64package mypackage// Этот файл компилируется только для linux/amd64func platformSpecific() string { return "Linux AMD64"}
//go:build !productionpackage mypackage// Этот файл НЕ включается при сборке с тегом productionfunc debugInfo() { fmt.Println("Debug mode enabled")}
# Сборка с тегом productiongo build -tags production -o ./bin/myapp ./cmd/myapp
Makefile для сборки
На практике команды сборки выносят в Makefile:
# ПеременныеAPP_NAME := myappVERSION := $(shell git describe --tags --always --dirty)BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)GIT_COMMIT := $(shell git rev-parse --short HEAD)LDFLAGS := -s -w \ -X github.com/myorg/$(APP_NAME)/internal/version.Version=$(VERSION) \ -X github.com/myorg/$(APP_NAME)/internal/version.BuildTime=$(BUILD_TIME) \ -X github.com/myorg/$(APP_NAME)/internal/version.GitCommit=$(GIT_COMMIT)# Сборка для текущей платформы.PHONY: buildbuild: CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -o ./bin/$(APP_NAME) ./cmd/$(APP_NAME)# Сборка для Linux (для Docker).PHONY: build-linuxbuild-linux: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o ./bin/$(APP_NAME)-linux-amd64 ./cmd/$(APP_NAME)# Сборка для всех платформ.PHONY: build-allbuild-all: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o ./bin/$(APP_NAME)-linux-amd64 ./cmd/$(APP_NAME) CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o ./bin/$(APP_NAME)-linux-arm64 ./cmd/$(APP_NAME) CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o ./bin/$(APP_NAME)-darwin-amd64 ./cmd/$(APP_NAME) CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o ./bin/$(APP_NAME)-darwin-arm64 ./cmd/$(APP_NAME)# Очистка.PHONY: cleanclean: rm -rf ./bin
🏠 Домашнее задание
Создайте пакет internal/version с переменными Version, BuildTime, GitCommit. Напишите Makefile, который устанавливает их при сборке. Добавьте endpoint /version, который возвращает эту информацию в JSON.
Соберите бинарник с CGO_ENABLED=0 -ldflags="-s -w" и без флагов. Сравните размеры (ls -lh). Запустите file на обоих — в чём разница?
Реализуйте build tag //go:build debug, который включает дополнительное логирование. Соберите с -tags debug и без — проверьте поведение.
2. Dockerfile для Go
Multi-stage build
Multi-stage build — стандартный паттерн для Go. Первый этап (builder) содержит Go toolchain и компилирует код. Второй этап (runtime) содержит только готовый бинарник.
Вариант 1: Alpine (рекомендуется для начала)
# ===== Этап сборки =====FROM golang:1.22-alpine AS builder# Устанавливаем необходимые инструментыRUN apk add --no-cache git ca-certificates# Рабочая директорияWORKDIR /app# Копируем файлы зависимостей отдельно для кеширования слоёв# Docker кеширует слои — если go.mod не изменился, зависимости не скачиваются зановоCOPY go.mod go.sum ./RUN go mod download# Копируем весь исходный кодCOPY . .# Собираем статический бинарникRUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-s -w" \ -o /app/bin/myapp ./cmd/myapp# ===== Этап выполнения =====FROM alpine:3.19# Устанавливаем корневые сертификаты для HTTPS-запросовRUN apk add --no-cache ca-certificates tzdata# Создаём непривилегированного пользователяRUN adduser -D -g '' appuser# Копируем бинарник из этапа сборкиCOPY --from=builder /app/bin/myapp /usr/local/bin/myapp# Копируем конфигурацию (если есть)# COPY --from=builder /app/configs /etc/myapp# Переключаемся на непривилегированного пользователяUSER appuser# Порт приложенияEXPOSE 8080# Точка входаENTRYPOINT ["myapp"]
Кеширование слоёв Docker
Порядок инструкций COPY критически важен. Сначала копируем go.mod и go.sum, затем запускаем go mod download. Если зависимости не изменились, Docker использует кеш. Только потом копируем исходный код. Это экономит минуты при каждой сборке.
Вариант 2: Scratch (минимальный размер)
# ===== Этап сборки =====FROM golang:1.22-alpine AS builderRUN apk add --no-cache git ca-certificatesWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .# Важно: статическая сборка обязательна для scratchRUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-s -w" \ -o /app/bin/myapp ./cmd/myapp# ===== Этап выполнения =====# scratch — пустой образ, ничего не содержитFROM scratch# Копируем сертификаты из builder (нужны для HTTPS)COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/# Копируем информацию о часовых поясахCOPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo# Копируем passwd для USER nobodyCOPY --from=builder /etc/passwd /etc/passwd# Копируем бинарникCOPY --from=builder /app/bin/myapp /myapp# Запускаем от nobodyUSER nobodyEXPOSE 8080ENTRYPOINT ["/myapp"]
Ограничения scratch
В scratch-образе нет шелла, нет пакетного менеджера, нет утилит. Нельзя зайти внутрь контейнера через docker exec -it ... sh. Отладка затруднена. Используйте scratch только когда уверены в стабильности приложения.
Вариант 3: Distroless (компромисс)
# ===== Этап сборки =====FROM golang:1.22-alpine AS builderRUN apk add --no-cache git ca-certificatesWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-s -w" \ -o /app/bin/myapp ./cmd/myapp# ===== Этап выполнения =====# Distroless: минимальный образ от Google с базовыми файламиFROM gcr.io/distroless/static-debian12# Копируем бинарникCOPY --from=builder /app/bin/myapp /myapp# Distroless уже использует nonroot пользователяUSER nonroot:nonrootEXPOSE 8080ENTRYPOINT ["/myapp"]
Сравнение размеров образов
Базовый образ
Размер
Shell
Пакетный менеджер
Отладка
alpine:3.19
~15 MB
Да
apk
Удобная
scratch
~0 MB
Нет
Нет
Нет
distroless/static
~2 MB
Нет
Нет
Ограничена
ubuntu:22.04
~77 MB
Да
apt
Удобная
Файл .dockerignore
Создайте .dockerignore, чтобы не копировать лишние файлы в контекст сборки:
Напишите три варианта Dockerfile (alpine, scratch, distroless) для своего Go-приложения. Соберите все три образа и сравните размеры через docker images.
Попробуйте зайти в каждый контейнер через docker exec -it <id> sh. В каких случаях это работает?
Намеренно уберите COPY go.mod go.sum ./ и RUN go mod download — копируйте всё сразу. Измените один .go файл и пересоберите. Заметьте разницу во времени сборки.
3. Docker Compose для локальной разработки
Docker Compose позволяет запустить всё окружение одной командой: приложение, базу данных, кеш, брокер сообщений.
# Запуск всех сервисов в фонеdocker compose up -d# Запуск с пересборкой образаdocker compose up -d --build# Просмотр логов всех сервисовdocker compose logs -f# Логи конкретного сервисаdocker compose logs -f app# Остановка всех сервисовdocker compose down# Остановка с удалением данных (томов)docker compose down -v# Перезапуск одного сервисаdocker compose restart app# Выполнение команды внутри контейнераdocker compose exec postgres psql -U myapp -d myapp_dev# Статус сервисовdocker compose ps
Hot-reload с Air
Для разработки удобно использовать Air — утилиту, которая автоматически перезапускает приложение при изменении файлов:
# docker-compose.dev.yml — переопределение для разработкиversion: "3.8"services: app: build: context: . dockerfile: Dockerfile.dev # Отдельный Dockerfile для разработки volumes: - .:/app # Монтируем исходный код для hot-reload command: ["air", "-c", ".air.toml"] # Запускаем через Air
# Dockerfile.dev — для разработки с AirFROM golang:1.22-alpineRUN apk add --no-cache git# Устанавливаем Air для hot-reloadRUN go install github.com/air-verse/air@latestWORKDIR /appCOPY go.mod go.sum ./RUN go mod download# Не копируем исходники — они монтируются через volumeEXPOSE 8080CMD ["air", "-c", ".air.toml"]
# Запуск dev-окружения с hot-reloaddocker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
🏠 Домашнее задание
Напишите полный docker-compose.yml для своего приложения с PostgreSQL, Redis и миграциями. Убедитесь, что depends_on с condition: service_healthy работает корректно.
Настройте hot-reload с Air. Измените файл и убедитесь, что приложение перезапустилось автоматически.
Добавьте сервис adminer (веб-интерфейс для БД) в docker-compose. Подключитесь к базе через браузер.
4. Kubernetes
Kubernetes (K8s) — платформа оркестрации контейнеров. Она управляет запуском, масштабированием и восстановлением приложений.
Основные концепции
Pod — минимальная единица деплоя, содержит один или несколько контейнеров
Deployment — управляет подами: количество реплик, стратегия обновления, откат
Service — стабильный сетевой адрес для группы подов
ConfigMap — конфигурация (не секретная)
Secret — секреты (пароли, ключи)
HPA — горизонтальный автоскейлер
Ingress — маршрутизация внешнего трафика
Deployment
# k8s/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: myapp namespace: production labels: app: myapp version: v1spec: # Количество реплик (подов) replicas: 3 # Стратегия обновления strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # Максимум 1 под может быть недоступен при обновлении maxSurge: 1 # Максимум 1 дополнительный под при обновлении # Селектор для выбора подов selector: matchLabels: app: myapp template: metadata: labels: app: myapp version: v1 annotations: prometheus.io/scrape: "true" # Prometheus будет скрейпить метрики prometheus.io/port: "9090" prometheus.io/path: "/metrics" spec: # Время на graceful shutdown terminationGracePeriodSeconds: 30 containers: - name: myapp image: registry.example.com/myapp:1.2.3 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8080 protocol: TCP - name: metrics containerPort: 9090 protocol: TCP # Переменные окружения из ConfigMap и Secret envFrom: - configMapRef: name: myapp-config - secretRef: name: myapp-secrets # Дополнительные переменные env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP # Ресурсы: запросы и лимиты resources: requests: cpu: 100m # 0.1 ядра — гарантированный минимум memory: 128Mi # 128 МБ — гарантированный минимум limits: cpu: 500m # 0.5 ядра — максимум memory: 256Mi # 256 МБ — при превышении OOMKill # Проверка готовности (readiness) readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 # Проверка живости (liveness) livenessProbe: httpGet: path: /health port: http initialDelaySeconds: 10 periodSeconds: 15 timeoutSeconds: 3 failureThreshold: 3 # Проверка запуска (startup) startupProbe: httpGet: path: /health port: http initialDelaySeconds: 0 periodSeconds: 5 failureThreshold: 30 # 30 * 5 = 150 секунд на запуск # Секреты для доступа к Docker registry imagePullSecrets: - name: registry-credentials
Service
# k8s/service.yaml---# ClusterIP — доступен только внутри кластераapiVersion: v1kind: Servicemetadata: name: myapp namespace: production labels: app: myappspec: type: ClusterIP ports: - name: http port: 80 # Порт сервиса targetPort: http # Порт контейнера (ссылка на имя порта) protocol: TCP - name: metrics port: 9090 targetPort: metrics protocol: TCP selector: app: myapp---# NodePort — доступен извне через порт на каждом узлеapiVersion: v1kind: Servicemetadata: name: myapp-nodeport namespace: productionspec: type: NodePort ports: - name: http port: 80 targetPort: http nodePort: 30080 # Порт на каждом узле кластера (30000-32767) selector: app: myapp---# LoadBalancer — создаёт внешний балансировщик (в облаке)apiVersion: v1kind: Servicemetadata: name: myapp-lb namespace: production annotations: # Аннотации зависят от облака service.beta.kubernetes.io/aws-load-balancer-type: "nlb"spec: type: LoadBalancer ports: - name: http port: 80 targetPort: http selector: app: myapp
Secret в K8s хранит данные в base64 — это НЕ шифрование. Любой, кто имеет доступ к кластеру, может прочитать секреты. Для продакшена используйте Sealed Secrets, HashiCorp Vault или AWS Secrets Manager.
HPA (Horizontal Pod Autoscaler)
# k8s/hpa.yamlapiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: myapp namespace: productionspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp # Границы масштабирования minReplicas: 2 maxReplicas: 10 # Метрики для автоскейлинга metrics: # По CPU — если среднее использование > 70% - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # По памяти — если среднее использование > 80% - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 # По пользовательской метрике (requests per second) - type: Pods pods: metric: name: http_requests_per_second target: type: AverageValue averageValue: "1000" # Поведение скейлинга behavior: scaleUp: stabilizationWindowSeconds: 60 # Ждать 60 сек перед увеличением policies: - type: Percent value: 50 # Увеличивать не более чем на 50% periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 300 # Ждать 5 минут перед уменьшением policies: - type: Percent value: 25 # Уменьшать не более чем на 25% periodSeconds: 60
# helm/myapp/Chart.yamlapiVersion: v2name: myappdescription: Go микросервис для управления задачамиtype: applicationversion: 0.1.0 # Версия чартаappVersion: "1.2.3" # Версия приложенияmaintainers: - name: DevTeam email: dev@example.com
Метрики (Prometheus): числовые показатели, агрегации, алерты. Отвечают на вопрос “что происходит?”
Трейсы (Jaeger): путь запроса через микросервисы. Отвечают на вопрос “где тормозит?”
Логи (Loki/ELK): детальная информация о событиях. Отвечают на вопрос “почему так произошло?”
🏠 Домашнее задание
Добавьте Prometheus-метрики в своё приложение: счётчик запросов, гистограмму задержек, gauge активных соединений. Проверьте через curl localhost:9090/metrics.
Настройте Prometheus + Grafana в Docker Compose. Создайте дашборд с графиками RPS, латентности (p50, p95, p99) и процента ошибок.
Напишите alert rule, который срабатывает, если p99 задержка превышает 2 секунды в течение 5 минут.
9. Health checks
Health checks позволяют Kubernetes понимать состояние приложения и автоматически реагировать на проблемы.
Три типа проб
Проба
Путь
Назначение
При неудаче
Liveness
/health
Процесс жив и не завис
Контейнер перезапускается
Readiness
/ready
Готов обрабатывать трафик
Трафик перестаёт поступать
Startup
/health
Приложение успешно запустилось
Ждёт или перезапускает
Разница между liveness и readiness
Liveness отвечает на вопрос: “Процесс вообще работает?” Если нет — Kubernetes убивает под и создаёт новый. Не проверяйте здесь зависимости — иначе при падении БД все поды будут бесконечно перезапускаться.
Readiness отвечает на вопрос: “Готов ли я принимать трафик?” Если нет — под убирается из Service (балансировщика). Когда зависимость восстановится, под снова получит трафик.
Реализация в Go
// internal/health/health.gopackage healthimport ( "context" "encoding/json" "net/http" "sync" "time" "github.com/jackc/pgx/v5/pgxpool" "github.com/redis/go-redis/v9")// Checker проверяет здоровье приложения и его зависимостейtype Checker struct { db *pgxpool.Pool redis *redis.Client}// NewChecker создаёт новый экземпляр Checkerfunc NewChecker(db *pgxpool.Pool, redis *redis.Client) *Checker { return &Checker{ db: db, redis: redis, }}// HealthResponse — ответ проверки здоровьяtype HealthResponse struct { Status string `json:"status"` // "ok" или "error" Timestamp string `json:"timestamp"` Checks map[string]ComponentStatus `json:"checks,omitempty"` // Статусы компонентов}// ComponentStatus — статус отдельного компонентаtype ComponentStatus struct { Status string `json:"status"` // "up" или "down" Message string `json:"message,omitempty"` Duration string `json:"duration,omitempty"` // Время проверки}// LivenessHandler — проверка живости (liveness).// Только проверяет, что процесс жив. НЕ проверяет зависимости!func (c *Checker) LivenessHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { resp := HealthResponse{ Status: "ok", Timestamp: time.Now().UTC().Format(time.RFC3339), } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(resp) }}// ReadinessHandler — проверка готовности (readiness).// Проверяет доступность всех зависимостей.func (c *Checker) ReadinessHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) defer cancel() checks := make(map[string]ComponentStatus) allHealthy := true var mu sync.Mutex var wg sync.WaitGroup // Проверяем PostgreSQL wg.Add(1) go func() { defer wg.Done() status := c.checkPostgres(ctx) mu.Lock() checks["postgres"] = status if status.Status != "up" { allHealthy = false } mu.Unlock() }() // Проверяем Redis wg.Add(1) go func() { defer wg.Done() status := c.checkRedis(ctx) mu.Lock() checks["redis"] = status if status.Status != "up" { allHealthy = false } mu.Unlock() }() wg.Wait() resp := HealthResponse{ Timestamp: time.Now().UTC().Format(time.RFC3339), Checks: checks, } if allHealthy { resp.Status = "ok" w.WriteHeader(http.StatusOK) } else { resp.Status = "error" w.WriteHeader(http.StatusServiceUnavailable) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) }}// checkPostgres проверяет доступность PostgreSQLfunc (c *Checker) checkPostgres(ctx context.Context) ComponentStatus { start := time.Now() if err := c.db.Ping(ctx); err != nil { return ComponentStatus{ Status: "down", Message: err.Error(), Duration: time.Since(start).String(), } } return ComponentStatus{ Status: "up", Duration: time.Since(start).String(), }}// checkRedis проверяет доступность Redisfunc (c *Checker) checkRedis(ctx context.Context) ComponentStatus { start := time.Now() if err := c.redis.Ping(ctx).Err(); err != nil { return ComponentStatus{ Status: "down", Message: err.Error(), Duration: time.Since(start).String(), } } return ComponentStatus{ Status: "up", Duration: time.Since(start).String(), }}
Реализуйте /health и /ready эндпоинты в своём приложении. /ready должен проверять БД и Redis параллельно (goroutines).
Задеплойте в minikube с настроенными пробами. Остановите PostgreSQL (kubectl delete pod postgres-...) и наблюдайте: поды должны стать NotReady, но НЕ перезапускаться.
Добавьте /startup пробу с failureThreshold: 60 и periodSeconds: 2 — это даст 2 минуты на запуск.
10. Graceful shutdown
При остановке пода Kubernetes отправляет сигнал SIGTERM. Приложение должно корректно завершить работу: дообработать текущие запросы, закрыть соединения с БД, остановить консьюмеры сообщений.
Почему это важно
In-flight запросы — если убить процесс мгновенно, клиенты получат обрыв соединения
Kubernetes pod termination — K8s убирает под из Service (перестаёт направлять трафик), затем отправляет SIGTERM. Если приложение не завершится за terminationGracePeriodSeconds, K8s отправит SIGKILL
Консистентность данных — незавершённые транзакции БД, недоотправленные сообщения
1. Pod помечается как Terminating
2. Pod убирается из Service endpoints (новый трафик не приходит)
3. Выполняется preStop hook (если настроен)
4. Отправляется SIGTERM
5. Приложение дообрабатывает текущие запросы
6. Приложение закрывает соединения
7. Приложение завершается с кодом 0
8. Если не завершилось за terminationGracePeriodSeconds → SIGKILL
preStop hook
Между шагами 2 и 4 есть гонка: Kubernetes может отправить SIGTERM раньше, чем endpoints обновятся. Добавьте preStop hook с небольшой задержкой:
Никогда не подставляйте пользовательский ввод напрямую в SQL-запрос через fmt.Sprintf. Всегда используйте параметризованные запросы ($1, $2) или query builder. Это защищает от SQL-инъекций на уровне драйвера.
Управление секретами
// НЕПРАВИЛЬНО — секреты в кодеconst dbPassword = "supersecret123"// ПРАВИЛЬНО — из переменных окруженияdbPassword := os.Getenv("DB_PASSWORD")if dbPassword == "" { log.Fatal("DB_PASSWORD не установлен")}// ЕЩЁ ЛУЧШЕ — из vault// Используйте HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager
Безопасность контейнера
# Запускаем от непривилегированного пользователяUSER nobody# Read-only файловая система# (в Kubernetes)
# Kubernetes: SecurityContextspec: containers: - name: myapp securityContext: runAsNonRoot: true # Запрет запуска от root runAsUser: 65534 # nobody readOnlyRootFilesystem: true # Read-only файловая система allowPrivilegeEscalation: false # Запрет повышения привилегий capabilities: drop: - ALL # Убираем все Linux capabilities volumeMounts: - name: tmp mountPath: /tmp # Записываемая директория для временных файлов volumes: - name: tmp emptyDir: {}
🏠 Домашнее задание
Добавьте SecurityHeaders middleware в своё приложение. Проверьте заголовки через curl -I http://localhost:8080.
Настройте CORS для конкретного домена (не *). Проверьте через fetch из консоли браузера с другого домена.
Проведите аудит безопасности контейнера: добавьте securityContext с runAsNonRoot, readOnlyRootFilesystem и drop ALL capabilities. Убедитесь, что приложение работает.
12. Сквозной проект: деплой Todo-микросервисов
Применим все знания на практике. Задеплоим два микросервиса из 06-microservices — todo-service и user-service — в Kubernetes с Helm и CI/CD.
-- scripts/init-databases.sql-- Создание баз данных и пользователей для каждого сервиса-- Todo ServiceCREATE USER todo WITH PASSWORD 'todo_secret';CREATE DATABASE todo_db OWNER todo;-- User ServiceCREATE USER users WITH PASSWORD 'users_secret';CREATE DATABASE users_db OWNER users;
Helm chart для todo-service
# helm/todo-service/Chart.yamlapiVersion: v2name: todo-servicedescription: Микросервис управления задачамиtype: applicationversion: 0.1.0appVersion: "1.0.0"
# Makefile.PHONY: help dev dev-down build test lint docker-build deployhelp: ## Показать справку @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'# ===== Локальная разработка =====dev: ## Запуск dev-окружения docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --builddev-down: ## Остановка dev-окружения docker compose -f docker-compose.yml -f docker-compose.dev.yml downdev-logs: ## Логи dev-окружения docker compose logs -fdev-ps: ## Статус сервисов docker compose ps# ===== Сборка =====build: ## Сборка всех сервисов cd services/todo-service && CGO_ENABLED=0 go build -ldflags="-s -w" -o ../../bin/todo-service ./cmd/todo-service cd services/user-service && CGO_ENABLED=0 go build -ldflags="-s -w" -o ../../bin/user-service ./cmd/user-service# ===== Тесты =====test: ## Запуск всех тестов cd services/todo-service && go test -v -race ./... cd services/user-service && go test -v -race ./...lint: ## Линтинг всех сервисов cd services/todo-service && golangci-lint run ./... cd services/user-service && golangci-lint run ./...# ===== Docker =====docker-build: ## Сборка Docker-образов docker build -t todo-service:latest ./services/todo-service docker build -t user-service:latest ./services/user-service# ===== Деплой =====deploy-staging: ## Деплой в staging helm upgrade --install todo-service ./helm/todo-service \ --namespace staging --create-namespace \ -f helm/todo-service/values-staging.yaml helm upgrade --install user-service ./helm/user-service \ --namespace staging --create-namespace \ -f helm/user-service/values-staging.yamldeploy-production: ## Деплой в production @echo "ВНИМАНИЕ: деплой в production! Продолжить? [y/N]" && read ans && [ $${ans:-N} = y ] helm upgrade --install todo-service ./helm/todo-service \ --namespace production --create-namespace \ -f helm/todo-service/values-production.yaml helm upgrade --install user-service ./helm/user-service \ --namespace production --create-namespace \ -f helm/user-service/values-production.yaml
🏠 Домашнее задание
Реализуйте полный сквозной проект: два микросервиса с Dockerfile, docker-compose для локальной разработки, Helm charts и GitHub Actions CI/CD.
Задеплойте в minikube. Проверьте, что оба сервиса работают и общаются друг с другом.
Выполните полный цикл: измените код → push → CI проходит → Docker образ собирается → Helm обновляет деплой. Проследите весь путь от коммита до работающего пода.
Добавьте мониторинг: Prometheus + Grafana в docker-compose. Создайте дашборд с метриками обоих сервисов.
Итоги главы
В этой главе мы прошли весь путь Go-приложения от сборки до продакшена:
Ключевые навыки
Сборка — статические бинарники с CGO_ENABLED=0, оптимизация размера через -ldflags, кросс-компиляция
Docker — multi-stage builds, выбор базового образа (alpine/scratch/distroless), кеширование слоёв
Docker Compose — полное dev-окружение одной командой, healthcheck, hot-reload с Air
Kubernetes — Deployment, Service, ConfigMap, Secret, HPA, Ingress — полный набор ресурсов
Helm — шаблонизация K8s-манифестов, параметризация, управление релизами
CI/CD — автоматические пайплайны в GitHub Actions и GitLab CI