Модуль: DevOps · Уровень: Middle+/Senior
TL;DR#
- CI (Continuous Integration) — на каждый push/PR автоматически прогоняются lint, тесты, сборка. Цель: ловить интеграционные проблемы рано, держать
mainвсегда зелёным. - CD — двусмысленно: Continuous Delivery (автоматически готовим релиз, кнопка деплоя ручная) vs Continuous Deployment (каждый зелёный коммит в
mainавтоматически едет в prod). На senior-собеседовании важно разделять эти два понятия. - Типичные стадии Go-пайплайна:
lint→test(unit + race + coverage) →build(бинарь/Docker-образ) →scan(уязвимости) →publish(push в registry) →deploy. - Артефакт — иммутабельный результат сборки (Docker-образ по digest, бинарь, helm chart). Билдим один раз, продвигаем по окружениям (build once, deploy many). Не пересобираем для каждого env.
- Версионирование: SemVer для библиотек/API, git SHA или
git describeдля сервисов. Образы тегируем и неизменяемым (sha-abc123), и плавающим (latest,v1.2) тегом. - Стратегии деплоя: rolling (по умолчанию в k8s), blue-green (мгновенное переключение + быстрый откат), canary (постепенный rollout на % трафика с метриками).
Теория#
Pipeline as Code#
Пайплайн описывается в репозитории (.github/workflows/*.yml, .gitlab-ci.yml, Jenkinsfile). Преимущества: версионируется вместе с кодом, review через PR, воспроизводимость. Конфиг в UI — антипаттерн (нет истории, нет review).
Стадии классического пайплайна#
| Стадия | Что делает | Инструменты (Go) |
|---|---|---|
| lint / static | формат, vet, статанализ | gofmt, go vet, golangci-lint |
| test | unit, race detector, coverage | go test -race -cover |
| build | компиляция, Docker-образ | go build, docker build, ko, buildx |
| security scan | SAST, зависимости, образ | govulncheck, gosec, trivy, grype |
| publish | push артефакта | registry (ECR/GCR/GHCR) |
| deploy | выкатка в окружение | kubectl/helm/argocd/terraform |
# минимальный CI-набор для Go
gofmt -l . # список неотформатированных файлов (должен быть пуст)
go vet ./...
golangci-lint run ./...
go test -race -coverprofile=cover.out ./...
go build -trimpath -ldflags "-s -w -X main.version=$(git describe --tags --always)" ./cmd/app
govulncheck ./... # CVE в зависимостях и стандартной библиотекеАртефакты и принцип “build once”#
Главное правило надёжного CD: собранный артефакт неизменен и продвигается по окружениям. Один и тот же Docker-образ (адресуемый по digest image@sha256:...) деплоится сначала в staging, потом ровно он же в prod. Пересборка под каждое окружение убивает воспроизводимость: «в staging работало, в prod нет» — потому что это два разных бинаря.
Конфигурация окружений выносится наружу (env vars, ConfigMap, Secrets), а не вшивается в образ. Это 12-factor: один артефакт, разная конфигурация.
# тег по неизменяемому digest, а не по плавающему latest
docker build -t ghcr.io/org/app:sha-$(git rev-parse --short HEAD) .
docker push ghcr.io/org/app:sha-$(git rev-parse --short HEAD)
# в deploy ссылаемся на digest, который вернул push:
# ghcr.io/org/app@sha256:9f2c...Версионирование#
- SemVer (
MAJOR.MINOR.PATCH) — для публичных API и Go-модулей. MAJOR — несовместимые изменения, MINOR — обратносовместимые фичи, PATCH — фиксы. В Go модули сv2+требуют суффикс в module path (module github.com/org/lib/v2). - Сервисы обычно версионируют git-тегами +
git describe(v1.4.2-3-gabc123= 3 коммита после тега v1.4.2). Версия вшивается в бинарь через-ldflags -X. - Образы: каждый build получает неизменяемый тег (git SHA) + опционально плавающие (
latest, мажорный/минорный). Никогда не деплойтеlatestв prod — невозможно понять, что именно крутится, и нет детерминированного отката.
go build -ldflags "-X main.version=$(git describe --tags --always --dirty)" ./...Стратегии деплоя#
Rolling update (дефолт в Kubernetes). Поды заменяются пачками: новый поднимается, проходит readiness, старый гасится. Параметры maxSurge / maxUnavailable.
- Плюсы: без даунтайма, не нужны двойные ресурсы.
- Минусы: во время выката одновременно живут обе версии (требует обратной совместимости API и схемы БД), откат не мгновенный.
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # сколько лишних подов сверх replicas
maxUnavailable: 0 # 0 => без просадки ёмкостиBlue-Green. Две идентичные среды: blue (текущая) и green (новая версия). После прогрева и проверки green трафик переключается целиком (через изменение Service/LB/DNS).
- Плюсы: мгновенный откат (вернуть указатель на blue), нет смешанных версий в момент переключения.
- Минусы: 2x ресурсов, переключение БД/состояния — сложная часть (миграции должны быть совместимы с обеими версиями).
Canary. Новая версия получает малую долю трафика (1% → 5% → 25% → 100%), на каждом шаге смотрят метрики (error rate, latency p99, бизнес-метрики). При деградации — автоматический откат. Реализуется через mesh (Istio/Linkerd), ingress weights, или Argo Rollouts / Flagger.
- Плюсы: минимальный blast radius, раннее обнаружение проблем на реальном трафике.
- Минусы: сложность (нужны хорошие метрики и автоматизация анализа), дольше по времени.
# Argo Rollouts: canary со ступенями и автоанализом
apiVersion: argoproj.io/v1alpha1
kind: Rollout
spec:
strategy:
canary:
steps:
- setWeight: 5
- pause: { duration: 5m }
- analysis:
templates: [{ templateName: success-rate }]
- setWeight: 25
- pause: { duration: 10m }
- setWeight: 100| Стратегия | Откат | Ресурсы | Смешанные версии | Когда |
|---|---|---|---|---|
| Rolling | медленный | 1x | да | дефолт, стабильные релизы |
| Blue-Green | мгновенный | 2x | нет (момент switch) | критичные, нужен быстрый rollback |
| Canary | авто/быстрый | ~1.1x | да | высокий трафик, нужна проверка на реальных юзерах |
Связь с миграциями БД#
Любая стратегия без даунтайма требует, чтобы старая и новая версии кода работали с одной схемой БД одновременно. Отсюда паттерн expand/contract (parallel change): сначала добавляем новое (nullable-колонку), деплоим код, читающий и старое и новое, бэкфиллим данные, переключаем запись, и только потом отдельным релизом удаляем старое. Никогда не совмещайте несовместимую миграцию схемы с деплоем кода в одном шаге.
GitOps#
Декларативный CD: желаемое состояние кластера описано в git, контроллер (ArgoCD/Flux) непрерывно синхронизирует кластер с репозиторием. Деплой = merge в git. Плюсы: git как source of truth, аудит, лёгкий откат через revert, автокоррекция дрейфа.
Подводные камни / gotchas#
latestв prod — нет детерминированного состояния и отката. Всегда иммутабельный тег/digest.- Пересборка артефакта на каждый env — теряете «build once», получаете расхождение staging/prod.
- Флаки-тесты в CI — убивают доверие к пайплайну, люди начинают перезапускать «на всякий случай» или мерджить красное. Изолировать/чинить, а не ретраить вслепую.
- Несовместимая миграция БД при rolling/canary — старые поды ломаются на новой схеме. Нужен expand/contract.
- Секреты в логах CI —
echo $TOKEN, или утечка черезset -x. Маскируйте, используйте secret-стораджи. - Долгий пайплайн убивает скорость команды. Кэширование, параллелизм, разделение fast (lint+unit на PR) и slow (e2e, нагрузка на nightly).
- Canary без хороших метрик бессмысленен: если нечем измерить деградацию, это просто медленный rolling.
- Отсутствие защиты ветки — прямой push в
mainмимо CI. Required status checks + review обязательны. - Деплой = только обновление образа, но забыли про конфиг/секреты/feature flags — частичный rollout, который ведёт себя непредсказуемо.
Вопросы на собеседовании#
В: В чём разница между Continuous Delivery и Continuous Deployment? О: Delivery — пайплайн автоматически готовит релиз, пригодный к деплою, но выкатка в prod — ручное решение (кнопка/approval). Deployment — каждый коммит, прошедший проверки, автоматически едет в prod без участия человека. Deployment требует очень зрелого тестирования, мониторинга и фич-флагов.
В: Почему важно «build once, deploy many»? О: Чтобы в prod уезжал ровно тот артефакт, что проверен в staging. Пересборка под env вносит риск расхождения (разные версии зависимостей, флаги компиляции, время сборки). Конфигурацию выносим наружу, а образ остаётся неизменным и адресуется по digest.
В: Сравните blue-green и canary. Когда что выбрать? О: Blue-green — мгновенное переключение всего трафика и мгновенный откат, но 2x ресурсов и нет постепенной проверки. Canary — постепенный rollout на % трафика с анализом метрик, минимальный blast radius, но сложнее (нужны метрики и автоматизация). Blue-green для критичных релизов, где нужен быстрый rollback; canary для высоконагруженных сервисов, где важно проверить на реальном трафике.
В: Как делать деплой без даунтайма при изменении схемы БД? О: Expand/contract (parallel change): миграции обратносовместимы, схема меняется в несколько шагов так, чтобы старая и новая версии кода работали одновременно. Сначала добавить новое (nullable), задеплоить код, бэкфилл, переключить запись, и отдельным релизом удалить старое. Несовместимую миграцию нельзя совмещать с деплоем кода.
В: Как версионировать Docker-образы для сервиса?
О: Неизменяемый тег по git SHA (sha-abc123) или git describe плюс ссылка по digest в деплое. Опционально плавающие теги (latest, минорный) для удобства, но в prod деплоим только иммутабельный. Версия дополнительно вшивается в бинарь через -ldflags -X main.version.
В: Что такое GitOps и чем он лучше push-деплоя? О: Желаемое состояние кластера хранится в git, контроллер (ArgoCD/Flux) непрерывно подтягивает и синхронизирует кластер. Деплой = merge. Плюсы: git как единый source of truth, полный аудит изменений, откат через revert, автокоррекция дрейфа, не нужен доступ CI к кластеру (pull-модель безопаснее).
В: Как ускорить медленный CI-пайплайн?
О: Кэш зависимостей (go mod, build cache), параллелизация независимых job, разделение быстрых проверок (lint+unit на каждый PR) и медленных (e2e/нагрузка на merge или nightly), сборка Docker с layer-кэшем и buildx, запуск тестов только для изменённых пакетов где возможно, отказ от лишних шагов на PR.
В: Что такое flaky-тесты и почему они опасны в CI?
О: Тесты, которые проходят/падают недетерминированно (гонки, таймауты, зависимость от порядка/времени/сети). Опасны тем, что разрушают доверие: команда привыкает перезапускать красное и в итоге мерджит реально сломанное. Лечение — изоляция, детерминизм (фиксированные часы, моки), -race, карантин с обязательным тикетом, а не слепой retry.
На что копают на senior+#
- Проектирование пайплайна для монорепо/множества сервисов: что собирать инкрементально, как определять затронутые сервисы (path filters, affected graph).
- Безопасность supply chain: подпись образов (cosign/sigstore), SBOM, провенанс (SLSA), сканирование зависимостей, защита от typosquatting в
go.mod. - Стратегия откатов: автоматический rollback по метрикам, совместимость миграций, feature flags для decouple деплоя от релиза (деплой кода ≠ включение фичи).
- Промоушн между окружениями и approval-гейты, environments с защитой, OIDC-федерация вместо долгоживущих ключей.
- Метрики DORA (deployment frequency, lead time, MTTR, change failure rate) и как пайплайн на них влияет.
- Эфемерные/preview-окружения на PR и их стоимость.