Gunicorn + uvloop, nginx reverse proxy, Docker, CI/CD, логирование, health checks, Prometheus.
Научитесь развёртывать aiohttp-приложения в продакшене с использованием Gunicorn, nginx, Docker и CI/CD
web.run_app() предназначен для разработки, а не для продакшена:
Для продакшена используйте Gunicorn или uvicorn.
pip install gunicorn# Базовый запуск
gunicorn -w 4 -k aiohttp.GunicornWebWorker app.main:create_app
# С настройками
gunicorn app.main:create_app \
-w 4 \
-k aiohttp.GunicornWebWorker \
--bind 0.0.0.0:8000 \
--timeout 120 \
--keep-alive 5 \
--access-logfile logs/access.log \
--error-logfile logs/error.log \
--log-level info \
--pid pidfile.pid \
--daemon| Параметр | Описание | Рекомендация |
|---|---|---|
-w | Количество воркеров | 2-4 × CPU ядра |
-k | Класс воркера | aiohttp.GunicornWebWorker |
--bind | Хост:порт | 0.0.0.0:8000 |
--timeout | Таймаут воркера (сек) | 120 |
--keep-alive | Keep-alive (сек) | 5 |
--max-requests | Перезапуск после N запросов | 1000 |
--max-requests-jitter | Рандомизация перезапуска | 50-100 |
# gunicorn.conf.py
import multiprocessing
bind = '0.0.0.0:8000'
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'aiohttp.GunicornWebWorker'
timeout = 120
keepalive = 5
# Логирование
accesslog = 'logs/access.log'
errorlog = 'logs/error.log'
loglevel = 'info'
# Перезапуск воркеров
max_requests = 1000
max_requests_jitter = 100
# Graceful shutdown
timeout_graceful = 30
# Preload (экономия памяти)
preload_app = TrueЗапуск:
gunicorn --config gunicorn.conf.py app.main:create_app# /etc/nginx/sites-available/task_manager
upstream task_manager {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
listen 80;
server_name api.example.com;
# Редирект на HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL сертификаты (Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# SSL настройки
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Логирование
access_log /var/log/nginx/task_manager_access.log;
error_log /var/log/nginx/task_manager_error.log;
# Размеры
client_max_body_size 10M;
# Таймауты
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
# API запросы
location / {
proxy_pass http://task_manager;
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;
# WebSocket поддержка
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Буферы
proxy_buffering off;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# Статические файлы
location /static/ {
alias /var/www/task_manager/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Health check
location /health {
proxy_pass http://task_manager;
access_log off;
}
# Rate limiting
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://task_manager;
}
}
# Rate limiting зона
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
}# Dockerfile
FROM python:3.11-slim
# Переменные окружения
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Рабочая директория
WORKDIR /app
# Системные зависимости
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Установка зависимостей Python
COPY pyproject.toml poetry.lock* ./
RUN pip install --no-cache-dir poetry && \
poetry config virtualenvs.create false && \
poetry install --only main --no-interaction
# Копирование кода
COPY . .
# Пользователь (не root!)
RUN useradd --create-home --shell /bin/bash app && \
chown -R app:app /app
USER app
# Порт
EXPOSE 8000
# Health check
HEALTHCHECK \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
# Запуск
CMD ["gunicorn", "--config", "gunicorn.conf.py", "app.main:create_app"]# docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/task_manager
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- DEBUG=false
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
networks:
- app_network
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=task_manager
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- app_network
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- app_network
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/ssl:ro
depends_on:
- api
restart: unless-stopped
networks:
- app_network
volumes:
postgres_data:
redis_data:
networks:
app_network:
driver: bridge# Сборка и запуск
docker-compose up -d --build
# Просмотр логов
docker-compose logs -f api
# Остановка
docker-compose down
# Остановка с удалением volumes
docker-compose down -v# .env.example
HOST=0.0.0.0
PORT=8000
DATABASE_URL=postgresql://user:password@localhost:5432/database
DB_MIN_SIZE=5
DB_MAX_SIZE=20
REDIS_URL=redis://localhost:6379
JWT_SECRET=change-me-in-production
JWT_ALGORITHM=HS256
DEBUG=false
LOG_LEVEL=info
SENTRY_DSN=https://...@sentry.io/...# app/config.py
import os
from dotenv import load_dotenv
load_dotenv() # Загрузка из .env
class Settings:
debug: bool = os.getenv('DEBUG', 'false').lower() == 'true'
database_url: str = os.getenv('DATABASE_URL')
jwt_secret: str = os.getenv('JWT_SECRET')
# ...
settings = Settings()# app/logging_config.py
import logging
import sys
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
},
'json': {
'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(asctime)s %(name)s %(levelname)s %(message)s',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'default',
'stream': sys.stdout,
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'default',
'filename': 'logs/app.log',
'maxBytes': 10485760, # 10MB
'backupCount': 5,
},
},
'root': {
'level': 'INFO',
'handlers': ['console', 'file'],
},
'loggers': {
'aiohttp': {
'level': 'INFO',
'propagate': False,
},
'asyncpg': {
'level': 'WARNING',
'propagate': False,
},
},
}pip install python-json-loggerimport logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setFormatter(jsonlogger.JsonFormatter())
logger.addHandler(handler)
logger.info('User logged in', extra={
'user_id': 123,
'ip': '192.168.1.1',
'action': 'login'
})
# {"asctime": "...", "user_id": 123, "ip": "192.168.1.1", "action": "login"}# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
POSTGRES_DB: task_manager_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
test:
stage: test
image: python:3.11
services:
- postgres:15
before_script:
- pip install poetry
- poetry install
script:
- poetry run pytest --cov=app --cov-fail-under=80
coverage: '/TOTAL.*\s+(\d+%)/'
build:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
script:
- ssh -o StrictHostKeyChecking=no user@server "
cd /app/task_manager &&
docker-compose pull &&
docker-compose up -d --build
"
only:
- main# app/handlers/health.py
from aiohttp import web
import asyncio
async def health_check(request):
"""Проверка здоровья приложения"""
checks = {
'status': 'healthy',
'database': 'unknown',
'redis': 'unknown'
}
# Проверка БД
try:
async with request.app['db'].acquire() as conn:
await conn.fetchval('SELECT 1')
checks['database'] = 'ok'
except Exception:
checks['database'] = 'error'
checks['status'] = 'unhealthy'
# Проверка Redis
try:
redis = request.app.get('redis')
if redis:
await redis.ping()
checks['redis'] = 'ok'
except Exception:
checks['redis'] = 'error'
status = 200 if checks['status'] == 'healthy' else 503
return web.json_response(checks, status=status)pip install aiohttp-prometheusfrom aiohttp_prometheus import PrometheusMiddleware, MetricsHandler
app = web.Application()
app.middlewares.append(PrometheusMiddleware())
app.router.add_get('/metrics', MetricsHandler)Убедитесь, что вы понимаете:
Переходите к вопросам для закрепления.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.