CD и стратегии деплоя
CD (Continuous Delivery / Continuous Deployment) — практики автоматизации подготовки и доставки изменений в тестовые и производственные окружения.
- Continuous Delivery — каждая проверенная версия готова к production, но выпуск запускается или подтверждается человеком.
- Continuous Deployment — каждая версия, прошедшая проверки, автоматически попадает в production.
Типичный путь изменения:
commit → build → tests → security checks → image/artifact
→ staging → smoke tests → approval, если требуется
→ production → monitoring → rollback при необходимостиСодержание
- Continuous Delivery и Continuous Deployment
- Основные элементы CD-процесса
- Подготовка приложения к деплою
- Стратегии деплоя
- Rolling deployment
- Blue-green deployment
- Canary deployment
- Сравнение стратегий
- Автоматический деплой на сервер
- GitHub Actions
- GitLab CI/CD
- Проверки после деплоя
- Rollback-стратегии
- Миграции базы данных
- Секреты в CI/CD
- Защита production-окружения
- Чек-лист
Continuous Delivery и Continuous Deployment
Continuous Delivery
Pipeline автоматически выполняет сборку, тесты, проверки безопасности, создаёт артефакт и разворачивает его в staging. Production-деплой выполняется после ручного подтверждения.
автоматически: commit → тесты → образ → staging
вручную: approval → productionТакой подход удобен для регулируемых систем, плановых релизов и команд, которым требуется дополнительная ручная проверка.
Continuous Deployment
Production-деплой запускается без ручного подтверждения после успешного прохождения всех этапов:
commit → tests → image → staging → acceptance tests → productionДля него особенно важны стабильные тесты, наблюдаемость, feature flags, быстрый rollback и ограничение области влияния ошибки.
| Подход | Подготовка версии | Production-деплой |
|---|---|---|
| Continuous Delivery | Автоматическая | С ручным подтверждением или запуском |
| Continuous Deployment | Автоматическая | Автоматический |
Оба подхода используют CI: изменения регулярно собираются, тестируются и проверяются автоматически.
Основные элементы CD-процесса
Окружения
| Окружение | Назначение |
|---|---|
| Development | Локальная разработка |
| Test | Автоматические проверки |
| Staging | Проверка версии в условиях, близких к production |
| Production | Рабочее окружение |
Staging желательно делать похожим на production по ОС, runtime, сетевой конфигурации, способу запуска и внешним зависимостям. Секреты и данные при этом должны быть отдельными.
Immutable artifact
Артефакт следует собрать один раз и продвигать между окружениями без повторной сборки:
build app:1.8.4 → test app:1.8.4 → staging app:1.8.4 → production app:1.8.4Для Docker лучше использовать версию, связанную с commit, или digest:
registry.example.com/team/app@sha256:7b4f...latest неудобен для аудита и отката, потому что его содержимое может измениться.
Идемпотентность и блокировка
Повторный запуск деплоя одной версии должен приводить систему к тому же состоянию. Параллельные production-деплои нужно блокировать: старый pipeline не должен завершиться после нового и вернуть предыдущую версию.
GitHub Actions:
concurrency:
group: production
cancel-in-progress: falseGitLab CI/CD:
deploy-production:
resource_group: productionПодготовка приложения к деплою
Health endpoints
Обычно выделяют:
- liveness — процесс работает и не завис;
- readiness — экземпляр готов принимать трафик;
- startup — завершена длительная инициализация.
GET /health/ready
200 OKReadiness должен быть быстрым и проверять только критические зависимости. Необязательный внешний сервис не должен делать весь экземпляр «не готовым» без необходимости.
Graceful shutdown
При остановке приложение должно перестать принимать новые запросы, завершить текущие операции, закрыть соединения и корректно обработать SIGTERM.
Конфигурация отдельно от образа
image: код и runtime
configuration: переменные, config files, feature flags
secrets: пароли, токены, ключиProduction-секреты не должны встраиваться в образ.
Совместимость версий
Во время rolling и canary старая и новая версии работают одновременно. API, сообщения очередей и схема базы данных должны временно поддерживать обе версии.
Стратегии деплоя
Стратегия определяет, как новая версия заменяет старую и как распределяется трафик.
- Rolling — экземпляры заменяются постепенно.
- Blue-green — создаётся параллельное окружение, затем трафик переключается целиком.
- Canary — новая версия сначала получает небольшую долю трафика.
- Recreate — старая версия останавливается до запуска новой; простой обычно неизбежен.
Rolling deployment
Экземпляры старой версии постепенно заменяются экземплярами новой:
Шаг 1: v1 v1 v1 v1
Шаг 2: v2 v1 v1 v1
Шаг 3: v2 v2 v1 v1
Шаг 4: v2 v2 v2 v1
Шаг 5: v2 v2 v2 v2Новый экземпляр должен получать трафик только после успешной readiness-проверки.
Преимущества: не требуется второе полное окружение, возможен деплой без простоя, стратегия поддерживается большинством оркестраторов, нагрузка переключается постепенно.
Недостатки: версии некоторое время работают одновременно, откат занимает время, ошибка может постепенно затронуть весь трафик, нужна совместимость приложения и базы данных.
Важные параметры: количество одновременно обновляемых экземпляров, readiness timeout, допустимое число ошибок, число дополнительных экземпляров и условие автоматического rollback.
Blue-green deployment
Существуют два окружения:
- blue — текущая production-версия;
- green — новая версия.
До переключения: users → load balancer → blue v1
green v2 ← internal checks
После переключения: users → load balancer → green v2
blue v1 ← standbyПосле smoke-тестов балансировщик переключает трафик на green.
Преимущества: быстрое переключение, простой rollback возвратом трафика, проверка новой версии до полного трафика, возможность временно сохранить старое окружение.
Недостатки: нужны ресурсы для двух окружений, стоимость может временно вырасти, миграции базы усложняют возврат, сессии и фоновые задачи требуют отдельного управления.
Состояние следует хранить во внешних сервисах: базе данных, object storage, cache или очереди. Локальная файловая система blue и green не должна быть единственным источником данных.
Canary deployment
Новая версия получает малую долю production-трафика, после чего доля постепенно увеличивается:
Этап 1: v1 — 95%, v2 — 5%
Этап 2: v1 — 75%, v2 — 25%
Этап 3: v1 — 50%, v2 — 50%
Этап 4: v1 — 0%, v2 — 100%На этапах сравниваются error rate, p95/p99 latency, перезапуски, CPU и память, ошибки в логах, бизнес-метрики и smoke-тесты.
Трафик можно выбирать по случайной доле, cookie, user ID, HTTP-заголовку, региону или группе пользователей. Стабильное закрепление пользователя за одной версией упрощает анализ.
Преимущества: небольшая область влияния ошибки, проверка на реальном трафике и возможность автоматической остановки.
Недостатки: ложная маршрутизация, требования к метрикам, совместимость версий и необходимость управлять сессиями и состоянием.
Canary можно сочетать с feature flags: первая технология управляет версией приложения, вторая — включением функции.
Сравнение стратегий
| Критерий | Rolling | Blue-green | Canary |
|---|---|---|---|
| Дополнительные ресурсы | Небольшие или средние | Высокие | Средние |
| Скорость переключения | Постепенная | Быстрая | Постепенная |
| Область влияния ошибки | Растёт постепенно | Большая после switch | Сначала малая |
| Простота rollback | Средняя | Высокая до изменения данных | Высокая на ранних этапах |
| Маршрутизация | Обычный балансировщик | Переключение окружений | Взвешенная или выборочная |
| Сложность | Средняя | Средняя | Высокая |
Автоматический деплой на сервер
Для небольшого проекта CI может подключаться к Linux-серверу по SSH, менять версию образа и запускать Docker Compose.
На сервере нужны Docker Engine, Compose plugin, отдельный пользователь, каталог приложения, registry access, reverse proxy, TLS, backup и мониторинг.
Пример compose.yaml:
services:
app:
image: ${APP_IMAGE}:${APP_VERSION}
restart: unless-stopped
env_file: [.env]
ports:
- "127.0.0.1:8080:8080"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health/ready"]
interval: 10s
timeout: 3s
retries: 6
start_period: 20sПривязка к 127.0.0.1 оставляет прямой доступ только на сервере; внешний трафик можно пропускать через reverse proxy.
Пример deploy.sh:
#!/usr/bin/env bash
set -Eeuo pipefail
VERSION="${1:?version is required}"
APP_DIR=/opt/myapp
cd "$APP_DIR"
PREVIOUS_VERSION="$(grep '^APP_VERSION=' .env | cut -d= -f2-)"
printf '%s\\n' "$PREVIOUS_VERSION" > releases/previous-version
sed -i "s/^APP_VERSION=.*/APP_VERSION=${VERSION}/" .env
docker compose pull app
docker compose up -d --no-deps app
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:8080/health/ready >/dev/null; then
echo "Deployment ${VERSION} succeeded"
exit 0
fi
sleep 2
done
echo "Health check failed; rolling back" >&2
sed -i "s/^APP_VERSION=.*/APP_VERSION=${PREVIOUS_VERSION}/" .env
docker compose pull app
docker compose up -d --no-deps app
exit 1Это учебный пример. Для production нужны блокировка параллельных запусков, таймауты, проверка образа, централизованные логи и работа с несколькими экземплярами.
SSH-доступ должен использовать отдельного пользователя и ключ, проверять known_hosts, отключать парольный вход и ограничивать сетевой доступ. Не следует без необходимости использовать root. Членство в группе docker фактически предоставляет высокие привилегии на сервере.
GitHub Actions
Пример workflow с тестами, сборкой образа и деплоем:
name: Build and deploy
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
packages: write
concurrency:
group: production
cancel-in-progress: false
env:
IMAGE: ghcr.io/example/myapp
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
- run: npm test
build:
needs: test
runs-on: ubuntu-latest
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: meta
run: echo "version=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ env.IMAGE }}:${{ steps.meta.outputs.version }}
deploy-production:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy
env:
HOST: ${{ secrets.DEPLOY_HOST }}
USER: ${{ secrets.DEPLOY_USER }}
VERSION: ${{ needs.build.outputs.version }}
run: ssh "$USER@$HOST" "/opt/myapp/deploy.sh '$VERSION'"Для production Environment можно настроить reviewers, secrets, ограничения веток и историю деплоев. Не следует отключать проверку host key через StrictHostKeyChecking=no. Сторонние actions желательно закреплять по полному commit SHA.
GitLab CI/CD
Пример production-деплоя с ручным подтверждением:
stages: [test, build, deploy]
variables:
IMAGE: "$CI_REGISTRY_IMAGE/app"
VERSION: "$CI_COMMIT_SHA"
test:
stage: test
image: node:22-alpine
script:
- npm ci
- npm run lint
- npm test
build-image:
stage: build
image: docker:27
services: [docker:27-dind]
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
- docker build --pull -t "$IMAGE:$VERSION" .
- docker push "$IMAGE:$VERSION"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
deploy-production:
stage: deploy
resource_group: production
environment:
name: production
url: https://app.example.com
script:
- ./deploy.sh "$VERSION"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manualwhen: manual соответствует Continuous Delivery. Для Continuous Deployment используется when: on_success после успешного build job. Production-переменные должны быть protected и доступны только доверенным веткам, тегам и ролям.
Проверки после деплоя
Успешное выполнение команды запуска не гарантирует работоспособность приложения.
Health check:
curl --fail --show-error --silent \\
--retry 10 --retry-delay 3 \\
https://app.example.com/health/readySmoke-тесты проверяют основные сценарии:
GET /health/ready → 200
GET /api/version → ожидаемая версия
POST /api/session → авторизация работает
GET /api/items → основной API отвечаетПолезно иметь endpoint версии:
{
"version": "a17c9e4",
"buildTime": "2026-09-21T10:30:00Z"
}После выпуска наблюдают HTTP 5xx, latency, CPU и память, перезапуски, ошибки зависимостей, длины очередей и ключевые бизнес-метрики.
Rollback-стратегии
Rollback — возврат к предыдущей работоспособной версии. План отката нужно подготовить и проверить до production-деплоя.
Redeploy предыдущего артефакта
APP_VERSION=a17c9e4 docker compose up -dПредыдущий образ должен оставаться в registry. Политика очистки не должна удалять последние рабочие релизы.
Rolling rollback
Оркестратор повторяет обновление, но к предыдущей версии. Это не мгновенно: часть запросов некоторое время может попадать на новую версию.
Blue-green rollback
Балансировщик переключает трафик с green обратно на blue. Это быстро, если старое окружение сохранилось и новая версия не внесла несовместимых изменений в данные.
Canary rollback
Новая версия исключается из маршрутизации:
v1 — 95%, v2 — 5%
ошибки превышают порог
v1 — 100%, v2 — 0%Roll-forward
Иногда безопаснее быстро выпустить исправление, а не возвращать старую версию. Это особенно актуально после изменения данных, при несовместимой схеме или необратимой миграции.
Автоматический откат может запускаться при превышении порога ошибок, latency или провале readiness. Порог должен учитывать нормальные значения, иначе система будет создавать ложные rollback.
Для отката сохраняют идентификатор прошлого образа, конфигурацию, историю деплоев, схему маршрутизации, состояние feature flags и информацию о миграциях.
Миграции базы данных
При rolling, blue-green и canary старая и новая версии могут обращаться к одной базе. Нельзя удалять поле, которым ещё пользуется старая версия.
Используют схему expand and contract:
- Expand — добавить новые поля, сохранив старые.
- Migrate — перенести данные и перевести приложение.
- Contract — удалить старые поля отдельным выпуском.
Рекомендации:
- делать миграции обратно совместимыми;
- не рассчитывать на откат данных как на обычный rollback;
- проверять миграции на реалистичном объёме;
- избегать длительных блокировок;
- делать backup перед рискованными изменениями;
- учитывать фоновые jobs и очереди;
- иметь план roll-forward.
Секреты в CI/CD
Секреты — пароли, токены, приватные ключи, сертификаты, registry credentials и параметры подключения к базам.
Где хранить
Секреты хранят во встроенном secret store CI/CD или во внешнем менеджере секретов. Нельзя записывать их в Git, Dockerfile, compose.yaml, логи, artifacts и cache.
# GitHub Actions
env:
API_TOKEN: ${{ secrets.API_TOKEN }}Для GitLab Variables полезны свойства Masked, Protected, ограничение environment scope и тип File variable для сертификатов. Production-секреты не должны быть доступны jobs из pull request и обычных feature-веток.
Минимальные полномочия
Токен должен иметь только необходимые права и ограниченный срок действия:
хорошо: push image в один namespace
плохо: административный доступ ко всей организацииПо возможности долгоживущие cloud-ключи заменяют OIDC и временными credentials:
CI job → OIDC token → cloud IAM → временные credentialsЛоги
Не следует печатать переменные окружения, включать set -x рядом с секретами или передавать секреты в командной строке без необходимости. Даже маскирование CI не гарантирует скрытие преобразованного значения.
Секреты при Docker build
Плохо:
ARG NPM_TOKEN
RUN echo "$NPM_TOKEN" > /root/.npmrcБезопаснее использовать BuildKit secret mount:
# syntax=docker/dockerfile:1
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
COPY . .
RUN npm run builddocker build --secret id=npmrc,src="$HOME/.npmrc" -t myapp:build .Для каждого секрета определяют владельца, срок действия, процедуру ротации и отзыва.
Защита production-окружения
Рекомендуется разделять build и deploy: build создаёт и проверяет артефакт, deploy продвигает выбранный digest. Production-секреты не нужны сборке.
Production защищают:
- protected branches и tags;
- approval для критичных окружений;
- отдельные runners;
- минимальные permissions;
- ограниченные SSH-ключи;
- журнал действий;
- сканирование образов и зависимостей;
- SBOM и подпись артефактов;
- проверка подписи перед деплоем.
Pull request из внешнего fork не должен получать production-секреты. Runner, выполняющий недоверенный код, не должен иметь сетевой доступ к production.
Для каждого деплоя сохраняют версию, commit, инициатора, pipeline, время, окружение, проверки и причину rollback.
Общий пример процесса
checkout
lint и unit tests
→ integration tests
→ dependency scan
→ docker build --pull
→ image scan
→ push image:<commit-sha>
→ deploy exact image to staging
→ readiness и smoke tests
→ approval или automatic promotion
→ canary 10%
→ observe metrics
→ canary 50%
→ observe metrics
→ production 100%При превышении порогов новая версия удаляется из маршрутизации или выполняется redeploy предыдущего digest.
Чек-лист
Процесс
- Определено, используется Continuous Delivery или Continuous Deployment.
- Build, test и deploy разделены.
- Между окружениями продвигается один и тот же артефакт.
- Параллельные production-деплои заблокированы.
- Для операций настроены timeout.
- История деплоев сохраняется.
Стратегия
- Выбрана rolling, blue-green или canary стратегия.
- Есть readiness и smoke-тесты.
- Определены пороги остановки rollout.
- Учтены сессии, очереди и фоновые jobs.
- Стоимость дополнительной инфраструктуры оценена.
Rollback
- Известна точная предыдущая версия.
- Предыдущий образ доступен.
- Rollback протестирован.
- Определено, когда нужен rollback, а когда roll-forward.
- Миграции базы обратно совместимы.
- Есть резервные копии.
Секреты и доступ
- Секреты отсутствуют в Git, образах, логах и artifacts.
- Production-секреты доступны только production job.
- Токены имеют минимальные полномочия.
- По возможности используется OIDC.
- Есть процедура ротации и отзыва.
- Проверяется SSH host key.
- Production runner изолирован от недоверенных jobs.