django.moscow
УслугиЦеныПроцессОбучениеБлог
Связаться

django.moscow

Поддержка и развитие Django-сайтов с гарантиями. 15+ лет опыта в веб-разработке.

Услуги

  • Аудит и стабилизация
  • Поддержка и SLA
  • Оптимизация
  • Развитие и фичи

Информация

  • Цены
  • Процесс работы
  • Блог
  • Контакты
  • Реквизиты

Контакты

  • constantin@potapov.me
  • @potapov_me
Политика конфиденциальности•Согласие на обработку ПД

© 2025 django.moscow. Все права защищены.

Сделано с ❤️ в России • potapov.me

  1. /
  2. Блог
  3. /
  4. Деплой Django: от gunicorn до Kubernetes
DevOps
5 декабря 2025
59 мин

Деплой Django: от gunicorn до Kubernetes

Деплой Django: от gunicorn до Kubernetes

Деплой Django: от gunicorn до Kubernetes

Правильный деплой Django-приложения — это фундамент надежного продакшена. В этом руководстве разберем три проверенных подхода с реальными конфигурациями, которые используются в production.

Вариант 1: Классический VPS (малые проекты)

Подходит для стартапов и MVP с трафиком до 50-100k пользователей в месяц. Простой, понятный и надежный подход.

Архитектура стека

Internet → Nginx (80/443) → Gunicorn (8000) → Django App
                          ↓
                    PostgreSQL (5432)
                    Redis (6379)

Компоненты:

  • VPS: DigitalOcean ($12-40/мес), Hetzner ($5-20/мес), Vultr
  • Web Server: Nginx — обработка статики, SSL, reverse proxy
  • WSGI Server: Gunicorn — запуск Python приложения
  • Process Manager: systemd — автоматический перезапуск при сбоях
  • БД: PostgreSQL 15+ — надежность и производительность
  • Кеш: Redis — кеширование запросов и сессии

Оптимальная конфигурация Gunicorn

# gunicorn_config.py
import multiprocessing
import os

# Биндинг на localhost (за Nginx)
bind = "127.0.0.1:8000"

# Расчет воркеров: (2 × CPU) + 1
# Для 2 CPU = 5 воркеров, для 4 CPU = 9 воркеров
workers = multiprocessing.cpu_count() * 2 + 1

# Тип воркера
# sync - для CPU-bound задач (стандартный Django)
# gevent/eventlet - для I/O-bound (много внешних API)
worker_class = "sync"

# Для async views в Django 4.1+ используйте:
# worker_class = "uvicorn.workers.UvicornWorker"

# Максимум одновременных подключений на воркер
worker_connections = 1000

# Автоматический перезапуск воркера после N запросов
# Предотвращает утечки памяти
max_requests = 1000
max_requests_jitter = 50  # Рандомизация для равномерной нагрузки

# Таймауты
timeout = 30  # Убить воркер если запрос выполняется > 30 сек
graceful_timeout = 30  # Время на завершение текущих запросов при reload
keepalive = 2  # Keep-alive соединения

# Логирование
errorlog = "/var/log/gunicorn/error.log"
accesslog = "/var/log/gunicorn/access.log"
loglevel = "info"  # debug, info, warning, error, critical

# Безопасность
limit_request_line = 4094  # Максимальная длина HTTP request line
limit_request_fields = 100  # Максимум HTTP заголовков
limit_request_field_size = 8190  # Максимальный размер заголовка

# PID file для управления процессом
pidfile = "/var/run/gunicorn/gunicorn.pid"

# Preload app для экономии памяти (но сложнее hot reload)
preload_app = True

# Hooks для логирования
def on_starting(server):
    """Вызывается при старте master процесса"""
    server.log.info("Gunicorn master процесс запущен")

def when_ready(server):
    """Вызывается когда сервер готов принимать запросы"""
    server.log.info("Сервер готов принимать запросы")

def on_reload(server):
    """Вызывается при reload конфигурации"""
    server.log.info("Конфигурация перезагружена")

Создание директорий:

sudo mkdir -p /var/log/gunicorn
sudo mkdir -p /var/run/gunicorn
sudo chown -R www-data:www-data /var/log/gunicorn
sudo chown -R www-data:www-data /var/run/gunicorn

Запуск и управление:

# Запуск
gunicorn myproject.wsgi:application -c gunicorn_config.py

# Graceful restart (без downtime)
kill -HUP $(cat /var/run/gunicorn/gunicorn.pid)

# Graceful shutdown
kill -TERM $(cat /var/run/gunicorn/gunicorn.pid)

# Проверка количества воркеров
ps aux | grep gunicorn

Production-ready Systemd Service

# /etc/systemd/system/django.service
[Unit]
Description=Django Application (Gunicorn WSGI Server)
Documentation=https://docs.djangoproject.com/
After=network.target postgresql.service redis.service
Requires=postgresql.service
Wants=redis.service

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject

# Environment
Environment="PATH=/var/www/myproject/venv/bin"
Environment="DJANGO_SETTINGS_MODULE=myproject.settings.production"
EnvironmentFile=/var/www/myproject/.env

# Основная команда запуска
ExecStart=/var/www/myproject/venv/bin/gunicorn \
    --config /var/www/myproject/gunicorn_config.py \
    myproject.wsgi:application

# Graceful reload при изменении кода
ExecReload=/bin/kill -s HUP $MAINPID

# Политика перезапуска
Restart=always
RestartSec=10
StartLimitInterval=0

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/myproject/media /var/log/gunicorn /var/run/gunicorn

# Resource limits
LimitNOFILE=65535
MemoryLimit=2G
CPUQuota=90%

# Логирование
StandardOutput=journal
StandardError=journal
SyslogIdentifier=django-app

[Install]
WantedBy=multi-user.target

Управление сервисом:

# Включить автозапуск
sudo systemctl enable django

# Запустить
sudo systemctl start django

# Проверить статус
sudo systemctl status django

# Просмотр логов
sudo journalctl -u django -f

# Перезапустить gracefully
sudo systemctl reload django

# Полный перезапуск
sudo systemctl restart django

# Остановить
sudo systemctl stop django

Production Nginx конфигурация

# /etc/nginx/sites-available/django
# Upstream для балансировки нагрузки
upstream django_backend {
    # Для одного Gunicorn процесса
    server 127.0.0.1:8000 fail_timeout=30s max_fails=3;

    # Для нескольких Gunicorn процессов (масштабирование):
    # server 127.0.0.1:8000 weight=1 fail_timeout=30s;
    # server 127.0.0.1:8001 weight=1 fail_timeout=30s;

    keepalive 32;  # Keep-alive соединения к backend
}

# Редирект HTTP → HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;

    # ACME challenge для Let's Encrypt
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # Редирект всех остальных запросов
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS сервер
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    # SSL сертификаты
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # SSL оптимизация (Mozilla Intermediate)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

    # Размеры загрузок
    client_max_body_size 20M;
    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 16k;

    # Таймауты
    client_body_timeout 12;
    client_header_timeout 12;
    keepalive_timeout 65;
    send_timeout 10;

    # Gzip компрессия
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript
               application/json application/javascript application/xml+rss
               application/rss+xml font/truetype font/opentype
               application/vnd.ms-fontobject image/svg+xml;

    # Логирование
    access_log /var/log/nginx/django_access.log combined buffer=32k flush=5s;
    error_log /var/log/nginx/django_error.log warn;

    # Статические файлы с агрессивным кешированием
    location /static/ {
        alias /var/www/myproject/staticfiles/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;

        # Компрессия статики
        gzip_static on;

        # Безопасность
        location ~* \.(py|pyc|pyo|pyd|log|ini|cfg|md|txt)$ {
            deny all;
        }
    }

    # Медиафайлы с умеренным кешированием
    location /media/ {
        alias /var/www/myproject/media/;
        expires 30d;
        add_header Cache-Control "public";

        # Защита от выполнения скриптов
        location ~ \.php$ {
            deny all;
        }
    }

    # Защита служебных файлов
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    # Health check endpoint
    location /health/ {
        proxy_pass http://django_backend;
        access_log off;
    }

    # Django приложение
    location / {
        proxy_pass http://django_backend;
        proxy_redirect off;

        # Заголовки для Django
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $server_name;

        # Таймауты
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # Буферизация
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;

        # WebSocket support (если используется Channels)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Rate limiting для API endpoints
    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        limit_req_status 429;

        proxy_pass http://django_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Rate limiting зоны (добавить в http {} блок в nginx.conf)
# limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

Активация конфигурации:

# Проверка синтаксиса
sudo nginx -t

# Создание симлинка
sudo ln -s /etc/nginx/sites-available/django /etc/nginx/sites-enabled/

# Перезагрузка Nginx
sudo systemctl reload nginx

# Проверка статуса
sudo systemctl status nginx

Автоматизация SSL с Let's Encrypt

# Установка Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx

# Получение сертификата (автоматическая настройка Nginx)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Или вручную без изменения конфигов
sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com

# Тестовый запуск для проверки
sudo certbot --nginx --dry-run -d yourdomain.com

# Автоматическое обновление (добавляется автоматически в cron)
sudo certbot renew --dry-run

# Проверка таймера systemd для обновления
sudo systemctl status certbot.timer

# Принудительное обновление
sudo certbot renew --force-renewal

Результат: Надежная инфраструктура за 2-4 часа с автоматическим управлением процессами.


Вариант 2: Docker (средние проекты)

Идеален для команд 2-10 разработчиков. Полная изоляция окружения, легкое масштабирование.

Multi-stage Production Dockerfile

# Dockerfile
# Stage 1: Builder - сборка зависимостей
FROM python:3.11-slim as builder

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /build

# Установка системных зависимостей для сборки
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    g++ \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Установка Python зависимостей в виртуальное окружение
COPY requirements.txt .
RUN python -m venv /opt/venv && \
    /opt/venv/bin/pip install --upgrade pip setuptools wheel && \
    /opt/venv/bin/pip install -r requirements.txt

# Stage 2: Runtime - минимальный финальный образ
FROM python:3.11-slim

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PATH="/opt/venv/bin:$PATH" \
    DJANGO_SETTINGS_MODULE=myproject.settings.production

WORKDIR /app

# Установка только runtime зависимостей
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    postgresql-client \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Копирование виртуального окружения из builder
COPY --from=builder /opt/venv /opt/venv

# Копирование кода приложения
COPY . .

# Сборка статики
RUN python manage.py collectstatic --noinput --clear

# Создание non-root пользователя для безопасности
RUN groupadd -r django && \
    useradd -r -g django -u 1000 django && \
    chown -R django:django /app && \
    mkdir -p /app/media /app/logs && \
    chown -R django:django /app/media /app/logs

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD curl -f http://localhost:8000/health/ || exit 1

USER django

EXPOSE 8000

# Entrypoint для миграций и запуска
COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

CMD ["gunicorn", "myproject.wsgi:application", \
     "--bind", "0.0.0.0:8000", \
     "--workers", "4", \
     "--worker-class", "sync", \
     "--max-requests", "1000", \
     "--max-requests-jitter", "50", \
     "--access-logfile", "-", \
     "--error-logfile", "-"]

Entrypoint скрипт:

#!/bin/bash
# docker-entrypoint.sh
set -e

echo "Waiting for database..."
while ! nc -z db 5432; do
  sleep 0.1
done
echo "Database is ready!"

echo "Running migrations..."
python manage.py migrate --noinput

echo "Creating cache tables..."
python manage.py createcachetable || true

echo "Starting application..."
exec "$@"

Production Docker Compose

# docker-compose.yml
version: '3.9'

services:
  db:
    image: postgres:15-alpine
    container_name: django_postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./backups:/backups
    environment:
      POSTGRES_DB: ${DB_NAME:-django_db}
      POSTGRES_USER: ${DB_USER:-django_user}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=ru_RU.UTF-8"
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-django_user}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  redis:
    image: redis:7-alpine
    container_name: django_redis
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    networks:
      - backend

  web:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        DJANGO_ENV: production
    container_name: django_web
    command: gunicorn myproject.wsgi:application
             --bind 0.0.0.0:8000
             --workers 4
             --threads 2
             --worker-class gthread
             --worker-tmp-dir /dev/shm
             --max-requests 1000
             --max-requests-jitter 50
             --access-logfile -
             --error-logfile -
    volumes:
      - ./staticfiles:/app/staticfiles:ro
      - ./media:/app/media
      - ./logs:/app/logs
    ports:
      - "8000:8000"
    env_file:
      - .env
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - backend
      - frontend

  celery_worker:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: django_celery_worker
    command: celery -A myproject worker
             --loglevel=info
             --concurrency=4
             --max-tasks-per-child=1000
    volumes:
      - ./media:/app/media
      - ./logs:/app/logs
    env_file:
      - .env
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - backend

  celery_beat:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: django_celery_beat
    command: celery -A myproject beat
             --loglevel=info
             --scheduler django_celery_beat.schedulers:DatabaseScheduler
    volumes:
      - ./logs:/app/logs
    env_file:
      - .env
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - backend

  nginx:
    image: nginx:alpine
    container_name: django_nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./staticfiles:/app/staticfiles:ro
      - ./media:/app/media:ro
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro
    depends_on:
      - web
    restart: unless-stopped
    networks:
      - frontend

  certbot:
    image: certbot/certbot
    container_name: django_certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    networks:
      - frontend

networks:
  backend:
    driver: bridge
  frontend:
    driver: bridge

volumes:
  postgres_data:
  redis_data:

Безопасный .env файл

# .env
# ВАЖНО: Не коммитить в git! Добавить в .gitignore

# Django
DJANGO_SECRET_KEY=ваш-очень-длинный-случайный-секретный-ключ-минимум-50-символов
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
DJANGO_SETTINGS_MODULE=myproject.settings.production

# Database
DB_NAME=django_production
DB_USER=django_user
DB_PASSWORD=сложный-пароль-БД-минимум-20-символов
DB_HOST=db
DB_PORT=5432

# Redis
REDIS_PASSWORD=сложный-пароль-redis-минимум-20-символов
REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0

# Celery
CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/0

# Email (пример для Gmail)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=app-specific-password

# AWS S3 (для медиафайлов)
AWS_ACCESS_KEY_ID=ваш-access-key
AWS_SECRET_ACCESS_KEY=ваш-secret-key
AWS_STORAGE_BUCKET_NAME=your-bucket-name
AWS_S3_REGION_NAME=eu-central-1

# Sentry (мониторинг ошибок)
SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id
SENTRY_ENVIRONMENT=production

# Security
SECURE_SSL_REDIRECT=True
SESSION_COOKIE_SECURE=True
CSRF_COOKIE_SECURE=True

Деплой и управление

# Первоначальный деплой
docker-compose up -d --build

# Проверка статуса
docker-compose ps

# Просмотр логов
docker-compose logs -f web
docker-compose logs -f celery_worker

# Применение миграций
docker-compose exec web python manage.py migrate

# Создание суперпользователя
docker-compose exec web python manage.py createsuperuser

# Сбор статики
docker-compose exec web python manage.py collectstatic --noinput

# Обновление кода (zero-downtime)
git pull origin main
docker-compose build web
docker-compose up -d --no-deps web

# Применение миграций после обновления
docker-compose exec web python manage.py migrate

# Перезапуск всех сервисов
docker-compose restart

# Очистка неиспользуемых образов
docker system prune -a

# Backup базы данных
docker-compose exec db pg_dump -U django_user django_production > backup_$(date +%Y%m%d_%H%M%S).sql

# Restore базы данных
docker-compose exec -T db psql -U django_user django_production < backup.sql

# Масштабирование Celery воркеров
docker-compose up -d --scale celery_worker=5

# Остановка всех сервисов
docker-compose down

# Полная очистка (включая volumes - ОСТОРОЖНО!)
docker-compose down -v

Результат: Изолированная среда с простым переносом между серверами, легкое масштабирование.


Вариант 3: Kubernetes (Enterprise)

Для проектов с требованиями high availability, автоматическим масштабированием и сложной инфраструктурой.

Архитектура

Internet → LoadBalancer → Ingress (Nginx) → Service → Pods (Django)
                                           ↓
                                    StatefulSet (PostgreSQL)
                                    StatefulSet (Redis)
                                    HPA (Auto-scaling)

Namespace и Secrets

# k8s/00-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    name: production
    environment: production
# Создание secrets из файла
kubectl create secret generic django-secrets \
  --from-env-file=.env.production \
  --namespace=production

# Или из литералов
kubectl create secret generic django-secrets \
  --from-literal=secret-key='ваш-django-secret-key' \
  --from-literal=db-password='пароль-бд' \
  --from-literal=redis-password='пароль-redis' \
  --namespace=production

# Просмотр secrets (base64 encoded)
kubectl get secret django-secrets -n production -o yaml

ConfigMap для настроек

# k8s/01-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: django-config
  namespace: production
data:
  DJANGO_SETTINGS_MODULE: "myproject.settings.production"
  ALLOWED_HOSTS: "yourdomain.com,www.yourdomain.com"
  DB_HOST: "postgres-service"
  DB_PORT: "5432"
  DB_NAME: "django_production"
  REDIS_HOST: "redis-service"
  REDIS_PORT: "6379"
  CELERY_BROKER_URL: "redis://redis-service:6379/0"
  GUNICORN_WORKERS: "4"
  GUNICORN_THREADS: "2"

PostgreSQL StatefulSet

# k8s/02-postgres.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
  namespace: production
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
  clusterIP: None  # Headless service для StatefulSet

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: production
spec:
  serviceName: postgres-service
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          valueFrom:
            configMapKeyRef:
              name: django-config
              key: DB_NAME
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: django-secrets
              key: db-password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "2000m"
        livenessProbe:
          exec:
            command:
            - pg_isready
            - -U
            - postgres
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - pg_isready
            - -U
            - postgres
          initialDelaySeconds: 5
          periodSeconds: 5
  volumeClaimTemplates:
  - metadata:
      name: postgres-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "fast-ssd"  # Используйте ваш storage class
      resources:
        requests:
          storage: 20Gi

Django Deployment с продвинутыми features

# k8s/03-django-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: django-app
  namespace: production
  labels:
    app: django
    version: v1
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # Максимум 1 дополнительный под при обновлении
      maxUnavailable: 0  # Всегда минимум 3 пода доступны
  selector:
    matchLabels:
      app: django
  template:
    metadata:
      labels:
        app: django
        version: v1
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      # Anti-affinity: разные ноды для каждого пода
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - django
              topologyKey: kubernetes.io/hostname

      # Init container для миграций
      initContainers:
      - name: run-migrations
        image: registry.yourdomain.com/django-app:latest
        command: ['python', 'manage.py', 'migrate', '--noinput']
        envFrom:
        - configMapRef:
            name: django-config
        - secretRef:
            name: django-secrets

      containers:
      - name: django
        image: registry.yourdomain.com/django-app:latest
        imagePullPolicy: Always
        ports:
        - name: http
          containerPort: 8000
          protocol: TCP

        # Environment переменные
        envFrom:
        - configMapRef:
            name: django-config
        - secretRef:
            name: django-secrets

        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP

        # Resource limits
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

        # Liveness probe - перезапуск если не отвечает
        livenessProbe:
          httpGet:
            path: /health/
            port: 8000
            httpHeaders:
            - name: Host
              value: yourdomain.com
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3

        # Readiness probe - не отправлять трафик если не готов
        readinessProbe:
          httpGet:
            path: /health/
            port: 8000
            httpHeaders:
            - name: Host
              value: yourdomain.com
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3

        # Startup probe - для медленного старта
        startupProbe:
          httpGet:
            path: /health/
            port: 8000
          initialDelaySeconds: 0
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 30

        # Volume mounts
        volumeMounts:
        - name: media
          mountPath: /app/media
        - name: static
          mountPath: /app/staticfiles
          readOnly: true
        - name: logs
          mountPath: /app/logs

        # Security context
        securityContext:
          runAsNonRoot: true
          runAsUser: 1000
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL

      # Volumes
      volumes:
      - name: media
        persistentVolumeClaim:
          claimName: django-media-pvc
      - name: static
        emptyDir: {}
      - name: logs
        emptyDir: {}

      # Graceful shutdown
      terminationGracePeriodSeconds: 60

      # Image pull secrets
      imagePullSecrets:
      - name: registry-credentials

Service и Ingress

# k8s/04-service-ingress.yaml
apiVersion: v1
kind: Service
metadata:
  name: django-service
  namespace: production
  labels:
    app: django
spec:
  selector:
    app: django
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8000
  type: ClusterIP
  sessionAffinity: ClientIP  # Sticky sessions

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: django-ingress
  namespace: production
  annotations:
    # Cert-manager для автоматического SSL
    cert-manager.io/cluster-issuer: letsencrypt-prod

    # Nginx Ingress настройки
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "20m"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"

    # Rate limiting
    nginx.ingress.kubernetes.io/limit-rps: "100"
    nginx.ingress.kubernetes.io/limit-connections: "50"

    # Security headers
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Frame-Options: SAMEORIGIN";
      more_set_headers "X-Content-Type-Options: nosniff";
      more_set_headers "X-XSS-Protection: 1; mode=block";
      more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";

    # CORS (если нужно)
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://yourdomain.com"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - yourdomain.com
    - www.yourdomain.com
    secretName: django-tls-cert
  rules:
  - host: yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: django-service
            port:
              number: 80
  - host: www.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: django-service
            port:
              number: 80

HorizontalPodAutoscaler (HPA)

# k8s/05-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: django-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: django-app
  minReplicas: 3
  maxReplicas: 20
  metrics:
  # CPU-based scaling
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  # Memory-based scaling
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  # Custom metrics (требует Prometheus adapter)
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "1000"
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300  # Ждать 5 минут перед scale down
      policies:
      - type: Percent
        value: 50  # Уменьшать макс на 50% за раз
        periodSeconds: 60
      - type: Pods
        value: 2  # Уменьшать макс на 2 пода за раз
        periodSeconds: 60
      selectPolicy: Min  # Выбрать более консервативную политику
    scaleUp:
      stabilizationWindowSeconds: 0  # Немедленный scale up
      policies:
      - type: Percent
        value: 100  # Удвоить количество подов
        periodSeconds: 15
      - type: Pods
        value: 4  # Добавить макс 4 пода
        periodSeconds: 15
      selectPolicy: Max  # Выбрать более агрессивную политику

PersistentVolumeClaim для медиа

# k8s/06-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: django-media-pvc
  namespace: production
spec:
  accessModes:
  - ReadWriteMany  # Множественный доступ для всех подов
  storageClassName: "nfs-storage"  # Или ваш storage class
  resources:
    requests:
      storage: 50Gi

Celery Worker Deployment

# k8s/07-celery-worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: celery-worker
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: celery-worker
  template:
    metadata:
      labels:
        app: celery-worker
    spec:
      containers:
      - name: celery-worker
        image: registry.yourdomain.com/django-app:latest
        command:
        - celery
        - -A
        - myproject
        - worker
        - --loglevel=info
        - --concurrency=4
        - --max-tasks-per-child=1000
        envFrom:
        - configMapRef:
            name: django-config
        - secretRef:
            name: django-secrets
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        volumeMounts:
        - name: media
          mountPath: /app/media
      volumes:
      - name: media
        persistentVolumeClaim:
          claimName: django-media-pvc

Деплой в Kubernetes

# Создание namespace
kubectl create namespace production

# Применение всех манифестов
kubectl apply -f k8s/ --namespace=production

# Или по порядку
kubectl apply -f k8s/00-namespace.yaml
kubectl apply -f k8s/01-configmap.yaml
kubectl apply -f k8s/02-postgres.yaml
kubectl apply -f k8s/03-django-deployment.yaml
kubectl apply -f k8s/04-service-ingress.yaml
kubectl apply -f k8s/05-hpa.yaml
kubectl apply -f k8s/06-pvc.yaml
kubectl apply -f k8s/07-celery-worker.yaml

# Проверка статуса
kubectl get all -n production
kubectl get pods -n production -o wide
kubectl get hpa -n production

# Просмотр логов
kubectl logs -f deployment/django-app -n production
kubectl logs -f deployment/celery-worker -n production

# Проверка конкретного пода
kubectl describe pod django-app-xxxxxxxxx-xxxxx -n production

# Выполнение команд в поде
kubectl exec -it deployment/django-app -n production -- python manage.py shell

# Создание суперпользователя
kubectl exec -it deployment/django-app -n production -- python manage.py createsuperuser

# Обновление образа (rolling update)
kubectl set image deployment/django-app \
  django=registry.yourdomain.com/django-app:v1.2.3 \
  --namespace=production

# Проверка статуса обновления
kubectl rollout status deployment/django-app -n production

# История обновлений
kubectl rollout history deployment/django-app -n production

# Откат к предыдущей версии
kubectl rollout undo deployment/django-app -n production

# Откат к конкретной ревизии
kubectl rollout undo deployment/django-app --to-revision=2 -n production

# Масштабирование вручную
kubectl scale deployment/django-app --replicas=5 -n production

# Port forwarding для локального доступа
kubectl port-forward service/django-service 8000:80 -n production

# Получение событий
kubectl get events -n production --sort-by='.lastTimestamp'

# Мониторинг ресурсов
kubectl top pods -n production
kubectl top nodes

Результат: Enterprise-grade инфраструктура с автоматическим масштабированием, self-healing, zero-downtime deployments.


Сравнение подходов (детальное)

КритерийVPSDockerKubernetes
Сложность настройкиНизкаяСредняяВысокая
Сложность поддержкиНизкаяСредняяВысокая (но автоматизировано)
Стоимость (малый проект)$5-20/мес$20-50/мес$100-300/мес
Стоимость (средний)$50-100/мес$100-300/мес$300-1000/мес
Стоимость (крупный)Неприменимо$500-2000/мес$1000-10000+/мес
МасштабируемостьРучная (вертикальная)Средняя (горизонтальная с ограничениями)Полностью автоматическая
Время первоначальной настройки2-4 часа4-8 часов2-5 дней
High AvailabilityНет (single point of failure)Возможно (с дополнительной настройкой)Встроено
Auto-scalingНетНет (требует внешних инструментов)Встроено (HPA, VPA)
Zero-downtime deploysВозможно (требует настройки)ВозможноВстроено
Self-healingНет (требует systemd)Ограничено (Docker restart policies)Встроено
Load BalancingРучная настройка (Nginx)Ручная настройкаАвтоматическое
RollbackРучной (Git + systemd restart)Ручной (docker-compose)Автоматический (kubectl rollout)
Monitoring готовностьТребует настройкиТребует настройкиВстроенные механизмы
Secrets ManagementФайлы .envDocker secrets / .envKubernetes Secrets / Vault
Подходит для команды1-3 человека2-10 человек5-100+ человек
Трафик (RPS)До 50-100До 500-1000Неограничено
Learning CurveЛегкоСреднеСложно

Общие production best practices

1. Zero-downtime deployments

VPS (Gunicorn):

# Graceful reload воркеров
kill -HUP $(cat /var/run/gunicorn/gunicorn.pid)

# Или через systemd
sudo systemctl reload django

# Blue-Green deployment на VPS (2 Gunicorn процесса)
# 1. Запустить новую версию на порту 8001
gunicorn myproject.wsgi:application --bind 127.0.0.1:8001 -c gunicorn_config.py --daemon

# 2. Переключить Nginx upstream
# 3. Остановить старый процесс на 8000

Docker:

# Rolling update
docker-compose up -d --no-deps --scale web=2 web
docker-compose up -d --no-deps --scale web=3 web
docker-compose up -d --no-deps --remove-orphans web

# Blue-Green с Docker
docker-compose -f docker-compose.blue.yml up -d
# Переключить nginx на blue
docker-compose -f docker-compose.green.yml down

Kubernetes:

# Автоматический Rolling Update через strategy в Deployment
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

2. Продвинутый Health Check endpoint

# health/views.py
from django.http import JsonResponse
from django.db import connection
from django.core.cache import cache
from django.conf import settings
import redis
import time

def health_check(request):
    """
    Комплексная проверка здоровья приложения
    GET /health/ - быстрая проверка (для load balancer)
    GET /health/detailed/ - детальная проверка (для мониторинга)
    """
    start_time = time.time()
    checks = {}
    overall_status = "healthy"

    # 1. Проверка базы данных
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
            cursor.fetchone()
        checks['database'] = {
            'status': 'ok',
            'latency_ms': round((time.time() - start_time) * 1000, 2)
        }
    except Exception as e:
        checks['database'] = {
            'status': 'error',
            'error': str(e)
        }
        overall_status = "unhealthy"

    # 2. Проверка Redis/Cache
    cache_start = time.time()
    try:
        test_key = '__health_check__'
        cache.set(test_key, 'ok', timeout=10)
        result = cache.get(test_key)
        if result == 'ok':
            checks['cache'] = {
                'status': 'ok',
                'latency_ms': round((time.time() - cache_start) * 1000, 2)
            }
        else:
            raise Exception("Cache read/write failed")
    except Exception as e:
        checks['cache'] = {
            'status': 'error',
            'error': str(e)
        }
        overall_status = "degraded"  # Cache не критичен

    # 3. Проверка Celery (опционально)
    if hasattr(settings, 'CELERY_BROKER_URL'):
        try:
            from myproject.celery import app as celery_app
            inspect = celery_app.control.inspect()
            stats = inspect.stats()
            if stats:
                checks['celery'] = {
                    'status': 'ok',
                    'workers': len(stats)
                }
            else:
                raise Exception("No workers available")
        except Exception as e:
            checks['celery'] = {
                'status': 'error',
                'error': str(e)
            }
            overall_status = "degraded"

    # 4. Проверка дискового пространства
    import shutil
    try:
        usage = shutil.disk_usage("/")
        percent_used = (usage.used / usage.total) * 100
        checks['disk'] = {
            'status': 'warning' if percent_used > 80 else 'ok',
            'percent_used': round(percent_used, 2),
            'free_gb': round(usage.free / (1024**3), 2)
        }
        if percent_used > 90:
            overall_status = "degraded"
    except Exception as e:
        checks['disk'] = {'status': 'unknown', 'error': str(e)}

    # Response
    response_data = {
        'status': overall_status,
        'timestamp': time.time(),
        'checks': checks,
        'response_time_ms': round((time.time() - start_time) * 1000, 2)
    }

    status_code = 200 if overall_status == "healthy" else 503
    return JsonResponse(response_data, status=status_code)


def liveness_check(request):
    """Простая проверка что приложение живо (для K8s liveness probe)"""
    return JsonResponse({'status': 'alive'})


def readiness_check(request):
    """Проверка готовности принимать трафик (для K8s readiness probe)"""
    try:
        # Минимальная проверка БД
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
        return JsonResponse({'status': 'ready'})
    except:
        return JsonResponse({'status': 'not_ready'}, status=503)
# urls.py
from health import views as health_views

urlpatterns = [
    path('health/', health_views.health_check, name='health'),
    path('health/detailed/', health_views.health_check, name='health_detailed'),
    path('liveness/', health_views.liveness_check, name='liveness'),
    path('readiness/', health_views.readiness_check, name='readiness'),
]

3. Безопасное управление миграциями в production

#!/bin/bash
# scripts/deploy.sh - Безопасный деплой с миграциями

set -e  # Остановка при ошибке

echo "=== Starting deployment ==="

# 1. Проверка миграций БЕЗ применения
echo "Checking for pending migrations..."
python manage.py makemigrations --check --dry-run

# 2. Показать план миграций
echo "Migration plan:"
python manage.py showmigrations --plan

# 3. Backup базы данных ПЕРЕД миграциями
echo "Creating database backup..."
timestamp=$(date +%Y%m%d_%H%M%S)
pg_dump -h localhost -U django_user django_db > backups/db_backup_${timestamp}.sql
echo "Backup created: db_backup_${timestamp}.sql"

# 4. Применение миграций
echo "Applying migrations..."
python manage.py migrate --noinput

# 5. Проверка что всё OK
if [ $? -eq 0 ]; then
    echo "Migrations applied successfully"
else
    echo "Migration failed! Rolling back..."
    psql -h localhost -U django_user django_db < backups/db_backup_${timestamp}.sql
    exit 1
fi

# 6. Сбор статики
echo "Collecting static files..."
python manage.py collectstatic --noinput --clear

# 7. Проверка deployment checklist
echo "Running Django deployment checks..."
python manage.py check --deploy --fail-level WARNING

# 8. Restart приложения
echo "Restarting application..."
sudo systemctl reload django

# 9. Health check
echo "Waiting for application to start..."
sleep 5
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health/)
if [ "$response" = "200" ]; then
    echo "=== Deployment successful! ==="
else
    echo "=== Health check failed (HTTP $response). Please investigate. ==="
    exit 1
fi

Откат миграций при проблемах:

# Просмотр истории миграций
python manage.py showmigrations app_name

# Откат к конкретной миграции
python manage.py migrate app_name 0042_previous_migration

# Откат всех миграций приложения
python manage.py migrate app_name zero

# Fake миграция (если схема уже применена вручную)
python manage.py migrate --fake app_name 0043_new_migration

4. Production логирование и мониторинг

# settings/production.py
import os
from pathlib import Path

# Structured logging с JSON
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'json': {
            '()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
            'format': '%(asctime)s %(name)s %(levelname)s %(message)s',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse',
        },
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        # Файловый handler с ротацией
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/var/log/django/app.log',
            'maxBytes': 10485760,  # 10MB
            'backupCount': 10,
            'formatter': 'verbose',
        },
        # JSON логи для парсинга (ELK, Loki)
        'file_json': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/var/log/django/app.json',
            'maxBytes': 10485760,
            'backupCount': 10,
            'formatter': 'json',
        },
        # Ошибки отдельно
        'file_error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/var/log/django/error.log',
            'maxBytes': 10485760,
            'backupCount': 20,
            'formatter': 'verbose',
        },
        # Консоль для Docker/K8s
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        # Sentry для критичных ошибок
        'sentry': {
            'level': 'ERROR',
            'class': 'sentry_sdk.integrations.logging.EventHandler',
        },
        # Email админам при критичных ошибках
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'filters': ['require_debug_false'],
        },
    },
    'loggers': {
        # Django core
        'django': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
            'propagate': False,
        },
        # Django request/response
        'django.request': {
            'handlers': ['file_error', 'mail_admins', 'sentry'],
            'level': 'ERROR',
            'propagate': False,
        },
        # Database queries (только для отладки!)
        'django.db.backends': {
            'handlers': ['file'],
            'level': 'WARNING',  # DEBUG чтобы видеть SQL
            'propagate': False,
        },
        # Security events
        'django.security': {
            'handlers': ['file_error', 'mail_admins'],
            'level': 'WARNING',
            'propagate': False,
        },
        # Ваше приложение
        'myproject': {
            'handlers': ['console', 'file', 'file_json', 'sentry'],
            'level': 'INFO',
            'propagate': False,
        },
        # Celery tasks
        'celery': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
            'propagate': False,
        },
    },
    'root': {
        'handlers': ['console', 'file', 'file_error'],
        'level': 'WARNING',
    },
}

# Создание директорий для логов
log_dir = Path('/var/log/django')
log_dir.mkdir(parents=True, exist_ok=True)

Интеграция с Sentry:

# settings/production.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.redis import RedisIntegration

sentry_sdk.init(
    dsn=os.getenv('SENTRY_DSN'),
    integrations=[
        DjangoIntegration(),
        CeleryIntegration(),
        RedisIntegration(),
    ],
    environment=os.getenv('SENTRY_ENVIRONMENT', 'production'),
    traces_sample_rate=0.1,  # 10% транзакций для performance monitoring
    profiles_sample_rate=0.1,  # 10% профилирование
    send_default_pii=False,  # Не отправлять личные данные
    before_send=lambda event, hint: event if event.get('level') != 'info' else None,
    release=os.getenv('GIT_COMMIT_SHA'),  # Версия из CI/CD
)

5. Secrets Management

Для VPS/Docker - использование django-environ:

# settings/production.py
import environ

env = environ.Env(
    DEBUG=(bool, False),
    ALLOWED_HOSTS=(list, []),
    DATABASE_URL=(str, ''),
)

# Чтение .env файла
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

DEBUG = env('DEBUG')
SECRET_KEY = env('SECRET_KEY')
ALLOWED_HOSTS = env('ALLOWED_HOSTS')

DATABASES = {
    'default': env.db()  # Парсит DATABASE_URL автоматически
}

# Redis
CACHES = {
    'default': env.cache('REDIS_URL')
}

Для Kubernetes - использование External Secrets Operator:

# k8s/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: django-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager  # или vault, gcp, azure
    kind: SecretStore
  target:
    name: django-secrets
    creationPolicy: Owner
  data:
  - secretKey: secret-key
    remoteRef:
      key: django/production/secret-key
  - secretKey: db-password
    remoteRef:
      key: django/production/db-password

6. CI/CD Pipeline пример (GitHub Actions)

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]
  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
    - uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
        cache: 'pip'

    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest pytest-django pytest-cov

    - name: Run tests
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
      run: |
        pytest --cov=. --cov-report=xml

    - name: Upload coverage
      uses: codecov/codecov-action@v3

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
    - uses: actions/checkout@v3

    - name: Log in to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=sha,prefix={{branch}}-
          type=raw,value=latest,enable={{is_default_branch}}

    - name: Build and push
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        build-args: |
          GIT_COMMIT_SHA=${{ github.sha }}

  deploy-kubernetes:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up kubectl
      uses: azure/setup-kubectl@v3

    - name: Configure kubectl
      run: |
        echo "${{ secrets.KUBECONFIG }}" > kubeconfig.yaml
        export KUBECONFIG=kubeconfig.yaml

    - name: Deploy to Kubernetes
      run: |
        kubectl set image deployment/django-app \
          django=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ github.sha }} \
          --namespace=production

        kubectl rollout status deployment/django-app -n production

    - name: Verify deployment
      run: |
        kubectl get pods -n production
        kubectl get deployment django-app -n production

  notify:
    needs: [deploy-kubernetes]
    runs-on: ubuntu-latest
    if: always()
    steps:
    - name: Notify Slack
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        text: 'Deployment to production: ${{ job.status }}'
        webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Чеклист перед production деплоем

Безопасность

  • ✅ DEBUG = False в production
  • ✅ Сложный SECRET_KEY (минимум 50 символов)
  • ✅ ALLOWED_HOSTS настроен правильно
  • ✅ SECURE_SSL_REDIRECT = True
  • ✅ SESSION_COOKIE_SECURE = True
  • ✅ CSRF_COOKIE_SECURE = True
  • ✅ SECURE_HSTS_SECONDS = 31536000 (HSTS)
  • ✅ X_FRAME_OPTIONS = 'DENY' или 'SAMEORIGIN'
  • ✅ SECURE_CONTENT_TYPE_NOSNIFF = True
  • ✅ SECURE_BROWSER_XSS_FILTER = True
  • ✅ Запустить python manage.py check --deploy

База данных

  • ✅ PostgreSQL 13+ (не SQLite!)
  • ✅ Connection pooling настроен (pgbouncer или django-db-geventpool)
  • ✅ Индексы для часто используемых полей
  • ✅ Автоматические бэкапы настроены
  • ✅ Point-in-time recovery возможен
  • ✅ CONN_MAX_AGE настроен (persistent connections)

Производительность

  • ✅ Статика собрана: python manage.py collectstatic
  • ✅ Кеширование настроено (Redis/Memcached)
  • ✅ Database query optimization (select_related, prefetch_related)
  • ✅ CDN для статики (CloudFlare, CloudFront)
  • ✅ Gzip компрессия включена
  • ✅ HTTP/2 включен

Мониторинг

  • ✅ Sentry или аналог для отслеживания ошибок
  • ✅ Application Performance Monitoring (New Relic, DataDog, Prometheus)
  • ✅ Логирование настроено и ротируется
  • ✅ Uptime monitoring (UptimeRobot, Pingdom)
  • ✅ Метрики сервера (CPU, RAM, Disk, Network)
  • ✅ Алерты при критичных событиях

Резервное копирование

  • ✅ Ежедневные бэкапы базы данных
  • ✅ Бэкапы медиафайлов (если не в S3)
  • ✅ Бэкапы хранятся вне основного сервера
  • ✅ Процедура восстановления протестирована
  • ✅ Retention policy определен (30/60/90 дней)

DevOps

  • ✅ CI/CD pipeline настроен
  • ✅ Automated testing работает
  • ✅ Zero-downtime deployment возможен
  • ✅ Rollback план готов
  • ✅ Runbook для инцидентов создан
  • ✅ On-call rotation определен

Документация

  • ✅ README с инструкциями по деплою
  • ✅ Переменные окружения документированы
  • ✅ Архитектурная диаграмма создана
  • ✅ Disaster recovery план готов
  • ✅ Контакты для экстренных случаев

Заключение

Рекомендации по выбору:

  1. Начинающий проект (MVP): VPS с Gunicorn + Nginx + systemd

    • Быстро настроить, дешево, просто поддерживать
    • Масштабировать позже когда появится нагрузка
  2. Растущий стартап (Product-Market Fit): Docker + Docker Compose

    • Легко переносить между окружениями
    • Проще масштабировать горизонтально
    • Командная разработка упрощается
  3. Enterprise / High-load: Kubernetes

    • Автоматическое масштабирование под нагрузку
    • High availability из коробки
    • Сложная инфраструктура оправдана

Путь миграции: VPS → Docker → Kubernetes по мере роста проекта и команды.

Главное — не переусложнять на старте. Надежный VPS лучше, чем плохо настроенный Kubernetes.

Нужна помощь с Django?

Берём проекты на поддержку с чётким SLA. Стабилизируем за 2 недели, даём план развития на 90 дней. 15+ лет опыта с Django.

Обсудить проектНаши услуги
Предыдущая статья
Безопасность

Чеклист безопасности Django перед продакшеном

Следующая статья
Мониторинг

Мониторинг Django с Sentry и Prometheus

•