Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Dev/Prod Parity — паритет сред
dev_prod_parity

Dev/Prod Parity — паритет сред

Минимизация различий между development, staging и production

Dev/Prod Parity: Паритет сред

Минимизируйте различия между development и production

#Суть принципа

Dev/Prod Parity — десятый фактор 12-Factor App. Принцип гласит:

Среда разработки (development), тестирования (staging) и production должны быть максимально похожи по времени, персоналу и инструментам.

┌─────────────────────────────────────────────────────────────────┐
│                    Идеальный паритет                            │
│                                                                  │
│  Development ────────▶ Staging ────────▶ Production            │
│  (локально)           (тестирование)     (пользователи)        │
│                                                                  │
│  Одинаковые:                                                    │
│  • База данных (версия, конфигурация)                          │
│  • Внешние сервисы (кэш, очереди)                              │
│  • Переменные окружения (структура)                            │
│  • Процесс развёртывания                                       │
│  • Инструменты мониторинга                                     │
└─────────────────────────────────────────────────────────────────┘

#Три разрыва паритета

#1. Разрыв во времени

┌──────────────────────────────────────────────────────────────┐
│  Разрыв во времени между деплоем в dev и prod               │
│                                                               │
│  ❌ Плохо:                                                    │
│  Dev:  деплой каждый день                                    │
│  Prod: деплой раз в месяц (накопление изменений)            │
│                                                               │
│  ✅ Хорошо:                                                   │
│  Dev:  деплой каждый день                                    │
│  Prod: деплой каждый день (непрерывная доставка)            │
└──────────────────────────────────────────────────────────────┘

#2. Разрыв в персонале

┌──────────────────────────────────────────────────────────────┐
│  Разрыв в персонале между разработкой и эксплуатацией       │
│                                                               │
│  ❌ Плохо:                                                    │
│  Разработчики пишут код                                     │
│  Операторы развёртывают и поддерживают                      │
│  (разработчики не видят проблем production)                 │
│                                                               │
│  ✅ Хорошо:                                                   │
│  Разработчики участвуют в развёртывании                     │
│  On-call rotation для разработчиков                         │
│  (разработчики чувствуют pain production)                   │
└──────────────────────────────────────────────────────────────┘

#3. Разрыв в инструментах

┌──────────────────────────────────────────────────────────────┐
│  Разрыв в инструментах между средами                        │
│                                                               │
│  ❌ Плохо:                                                    │
│  Dev:  SQLite, локальные файлы, ручные скрипты              │
│  Prod: PostgreSQL, S3, автоматизированный CI/CD             │
│                                                               │
│  ✅ Хорошо:                                                   │
│  Dev:  PostgreSQL (Docker), S3 (MinIO), CI/CD пайплайн      │
│  Prod: PostgreSQL, S3, CI/CD пайплайн                       │
└──────────────────────────────────────────────────────────────┘

#Устранение разрывов

#Базы данных

#❌ Неправильно: разные СУБД

# Development # .env.dev DATABASE_URL=sqlite:///dev.db # SQLite # Production # .env.prod DATABASE_URL=postgresql://user:pass@prod-db:5432/app # PostgreSQL

Проблемы:

  • SQLite не поддерживает транзакции как PostgreSQL
  • Разные типы данных и функции
  • Ошибки проявляются только в production

#✅ Правильно: одинаковые СУБД

# Development # .env.dev DATABASE_URL=postgresql://dev:dev@localhost:5432/dev_db # Production # .env.prod DATABASE_URL=postgresql://prod:pass@prod-db:5432/app
# docker-compose.yml для локальной разработки version: '3.8' services: db: image: postgres:15 # Та же версия, что в production environment: POSTGRES_USER: dev POSTGRES_PASSWORD: dev POSTGRES_DB: dev_db ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:

#Внешние сервисы

#Кэш (Redis)

# docker-compose.yml services: redis: image: redis:7-alpine # Та же версия, что в production ports: - "6379:6379" command: redis-server --appendonly yes # AOF persistence
# Одинаковая конфигурация для всех сред import os REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')

#Очереди (RabbitMQ)

# docker-compose.yml services: rabbitmq: image: rabbitmq:3-management # С веб-интерфейсом ports: - "5672:5672" # AMQP - "15672:15672" # Management UI environment: RABBITMQ_DEFAULT_USER: guest RABBITMQ_DEFAULT_PASS: guest

#Переменные окружения

#Структура переменных

# .env.example (шаблон для всех сред) # Обязательные переменные DATABASE_URL=postgresql://user:pass@host:5432/db REDIS_URL=redis://host:6379/0 SECRET_KEY=change-me-in-production # Опциональные с дефолтами DEBUG=false LOG_LEVEL=INFO MAX_UPLOAD_SIZE=10485760 # Внешние сервисы STRIPE_KEY=sk_test_... SENTRY_DSN=https://...@sentry.io/...
# .env.dev (development) DATABASE_URL=postgresql://dev:dev@localhost:5432/dev_db REDIS_URL=redis://localhost:6379/0 SECRET_KEY=dev-secret-key-not-for-production DEBUG=true LOG_LEVEL=DEBUG STRIPE_KEY=sk_test_... SENTRY_DSN= # Пусто, не отправлять ошибки в Sentry # .env.staging DATABASE_URL=postgresql://staging:pass@staging-db:5432/app REDIS_URL=redis://staging-redis:6379/0 SECRET_KEY=<staging-secret> DEBUG=false LOG_LEVEL=INFO STRIPE_KEY=sk_test_... SENTRY_DSN=https://...@sentry.io/... # .env.prod (production) DATABASE_URL=postgresql://prod:pass@prod-db:5432/app REDIS_URL=redis://prod-redis:6379/0 SECRET_KEY=<production-secret> DEBUG=false LOG_LEVEL=WARNING STRIPE_KEY=sk_live_... SENTRY_DSN=https://...@sentry.io/...

#Процесс развёртывания

#❌ Неправильно: разные процессы

# Development # Ручное развёртывание git pull pip install -r requirements.txt python manage.py migrate python manage.py runserver # Production # Автоматизированное развёртывание # CI/CD пайплайн

#✅ Правильно: одинаковый процесс

# .gitlab-ci.yml (единый пайплайн для всех сред) stages: - test - build - deploy test: stage: test script: - pytest - flake8 build: stage: build script: - docker build -t myapp:$CI_COMMIT_SHA . - docker push registry/myapp:$CI_COMMIT_SHA deploy-dev: stage: deploy script: - kubectl set image deployment/myapp myapp=registry/myapp:$CI_COMMIT_SHA environment: name: development when: manual # Ручное подтверждение deploy-staging: stage: deploy script: - kubectl set image deployment/myapp-staging myapp=registry/myapp:$CI_COMMIT_SHA environment: name: staging when: manual deploy-prod: stage: deploy script: - kubectl set image deployment/myapp-prod myapp=registry/myapp:$CI_COMMIT_SHA environment: name: production when: manual needs: - deploy-staging

#Docker для паритета сред

#Единый Dockerfile для всех сред

# Dockerfile FROM python:3.12-slim as base WORKDIR /app # Установка зависимостей COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Копирование кода COPY . . # Сборка (если нужна) RUN python manage.py collectstatic --noinput # Runtime FROM base as runtime USER nobody EXPOSE 5000 CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
# docker-compose.yml для development version: '3.8' services: web: build: . ports: - "5000:5000" environment: - DEBUG=true - DATABASE_URL=postgresql://dev:dev@db:5432/dev_db - REDIS_URL=redis://redis:6379/0 volumes: - .:/app # Hot reload depends_on: - db - redis db: image: postgres:15 environment: POSTGRES_USER: dev POSTGRES_PASSWORD: dev POSTGRES_DB: dev_db volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine volumes: - redis_data:/data volumes: postgres_data: redis_data:

#Production docker-compose

# docker-compose.prod.yml version: '3.8' services: web: image: registry/myapp:latest # Готовый образ ports: - "5000:5000" environment: - DEBUG=false - DATABASE_URL=postgresql://prod:pass@db:5432/app - REDIS_URL=redis://redis:6379/0 restart: unless-stopped depends_on: - db - redis db: image: postgres:15 environment: POSTGRES_USER: prod POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: app volumes: - postgres_data:/var/lib/postgresql/data deploy: resources: limits: memory: 2G redis: image: redis:7-alpine volumes: - redis_data:/data deploy: resources: limits: memory: 512M volumes: postgres_data: redis_data:

#Миграции базы данных

#Единый процесс для всех сред

# manage.py #!/usr/bin/env python import os import sys if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
# Одинаковые команды для всех сред # Development docker-compose exec web python manage.py migrate # Staging kubectl exec -it deployment/myapp-staging -- python manage.py migrate # Production kubectl exec -it deployment/myapp-prod -- python manage.py migrate

#Alembic для SQLAlchemy

# alembic/env.py from alembic import context from sqlalchemy import engine_from_config import os # Конфигурация из переменных окружения config = context.config config.set_main_option( 'sqlalchemy.url', os.environ['DATABASE_URL'] ) def run_migrations_online(): connectable = engine_from_config( config.get_section(config.config_ini_section), prefix='sqlalchemy.' ) with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata ) with context.begin_transaction(): context.run_migrations() run_migrations_online()
# Применение миграций # Development alembic upgrade head # Production (в CI/CD) alembic upgrade head

#Мониторинг и логирование

#Одинаковый стек мониторинга

# docker-compose.yml (development с мониторингом) services: web: build: . environment: - SENTRY_DSN=https://dev@ingest.sentry.io/... - PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus prometheus: image: prom/prometheus ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml grafana: image: grafana/grafana ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin

#Структурированное логирование

# logging_config.py import os import logging import json class JSONFormatter(logging.Formatter): """JSON формат для структурированного логирования""" def format(self, record): log_entry = { 'timestamp': self.formatTime(record), 'level': record.levelname, 'logger': record.name, 'message': record.getMessage(), 'module': record.module, 'function': record.funcName, 'line': record.lineno } if record.exc_info: log_entry['exception'] = self.formatException(record.exc_info) return json.dumps(log_entry) def setup_logging(): log_level = os.environ.get('LOG_LEVEL', 'INFO') log_format = os.environ.get('LOG_FORMAT', 'text') logger = logging.getLogger() logger.setLevel(log_level) handler = logging.StreamHandler() if log_format == 'json': handler.setFormatter(JSONFormatter()) else: handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) logger.addHandler(handler)
# Development LOG_FORMAT=text LOG_LEVEL=DEBUG # Production LOG_FORMAT=json LOG_LEVEL=WARNING

#Антипаттерны

#❌ Mock-сервисы вместо реальных

# ❌ Неправильно: mock Redis в development class MockRedis: def get(self, key): return None def set(self, key, value): pass # Development использует mock # Production использует реальный Redis # Ошибки кэширования проявляются только в production

Решение: Использовать реальный Redis в development через Docker.

#❌ Ручные шаги для production

# ❌ Неправильно: разные процессы # Development git pull && python manage.py migrate && python manage.py runserver # Production # 1. Создать бэкап БД # 2. Остановить приложение # 3. Применить миграции # 4. Запустить приложение # 5. Проверить логи # 6. Отправить email команде

Решение: Автоматизировать production deployment через CI/CD.

#❌ Hardcoded пути и настройки

# ❌ Неправильно: hardcoded пути if os.environ.get('ENV') == 'production': STATIC_ROOT = '/var/www/static' DATABASE = 'postgresql://prod-db/app' else: STATIC_ROOT = './static' DATABASE = 'sqlite:///dev.db'

Решение:

# ✅ Правильно: переменные окружения STATIC_ROOT = os.environ['STATIC_ROOT'] DATABASE_URL = os.environ['DATABASE_URL']

#Проверка соответствия принципу

Задайте себе вопросы:

  • Используется ли одинаковая СУБД в dev и prod?
  • Запускаются ли внешние сервисы (Redis, RabbitMQ) локально через Docker?
  • Одинаков ли процесс развёртывания для всех сред?
  • Используются ли одинаковые переменные окружения (структура)?
  • Настроен ли мониторинг в development?
  • Участвуют ли разработчики в on-call rotation?

#Связь с другими факторами

  • Config — одинаковая структура переменных окружения во всех средах
  • Backing Services — одинаковые внешние сервисы в dev и prod
  • Build, Release, Run — одинаковый артефакт для всех сред
  • Logs — одинаковый формат логов для всех сред

#Пример из практики

#До: большой разрыв паритета

Development:
• SQLite вместо PostgreSQL
• Локальные файлы вместо S3
• Нет Redis (кэш отключён)
• Ручное развёртывание
• DEBUG=True всегда
• Нет мониторинга

Production:
• PostgreSQL кластер
• Amazon S3
• Redis кластер
• Автоматизированное развёртывание
• DEBUG=False
• Полный стек мониторинга

Результат:
• Ошибки БД проявляются в production
• Проблемы кэширования только в production
• Разработчики не понимают production issues

#После: паритет сред

# docker-compose.yml — полная копия production локально services: web: build: . environment: - DATABASE_URL=postgresql://dev:dev@db:5432/dev_db - REDIS_URL=redis://redis:6379/0 - AWS_S3_ENDPOINT_URL=http://minio:9000 # S3-compatible - DEBUG=true - LOG_LEVEL=DEBUG - SENTRY_DSN=https://dev@ingest.sentry.io/... db: image: postgres:15 # Та же версия, что в production redis: image: redis:7-alpine # Та же версия, что в production minio: image: minio/minio # S3-compatible storage локально prometheus: image: prom/prometheus # Мониторинг как в production grafana: image: grafana/grafana # Дашборды как в production
# Одинаковый процесс развёртывания # CI/CD пайплайн для всех сред # 1. Тесты pytest # 2. Сборка образа docker build -t myapp:$COMMIT_SHA . # 3. Деплой kubectl set image deployment/myapp myapp=myapp:$COMMIT_SHA # 4. Миграции kubectl exec deployment/myapp -- python manage.py migrate # 5. Health check curl https://myapp.example.com/health

Результат:

  • ✅ Ошибки БД обнаруживаются в development
  • ✅ Проблемы кэширования видны сразу
  • ✅ Разработчики понимают production issues
  • ✅ Быстрое восстановление после инцидентов

Ключевой вывод: Минимизируйте различия между development, staging и production по времени, персоналу и инструментам. Используйте Docker для локального запуска тех же сервисов, что в production, и автоматизируйте процесс развёртывания для всех сред.

Далее: Logs — логирование