За годы работы с Flask я натыкался на одни и те же грабли достаточно часто, чтобы наконец научится их обходить. И сегодня хочу поделится опытом, который сбережет вам немало нервных клеток. Начнем с правильной подготовки проекта к деплою.
От разработки до продакшена - как деплоить Flask приложения без боли и страданий
Структура проекта и зависимости
Перед тем, как задумываться о деплое, убедитесь, что структура вашего проекта в порядке. Часто видел, как разработчики держат весь код в одном файле app.py, а потом удивляются, почему код превращается в лапшу. Для серьезных проектов рекомендую разделить код на модули:
| Python | 1
2
3
4
5
6
7
8
9
10
| my_flask_app/
├── app/
│ ├── __init__.py # Инициализация Flask
│ ├── models/ # Модели данных
│ ├── routes/ # Маршруты API
│ ├── services/ # Бизнес-логика
│ └── templates/ # HTML шаблоны
├── config.py # Конфигурация
├── requirements.txt # Зависимости
└── wsgi.py # Точка входа для WSGI-сервера |
|
Теперь о зависимостях. Тут все просто — pip freeze > requirements.txt не подойдет. Знаете почему? Этой командой вы выгрузите ВСЕ установленные пакеты, включая те, которые не используете в проекте. Результат? Раздутые зависимости и потенциальные конфликты. Лучше создавать виртуальное окружение под каждый проект и устанавливать только нужные пакеты:
| Bash | 1
2
3
4
| python -m venv venv
source venv/bin/activate # На Windows: venv\Scripts\activate
pip install flask gunicorn psycopg2-binary
pip freeze > requirements.txt |
|
И не забывайте про версии! Фиксируйте их для критичных библиотек:
| Python | 1
2
| flask==2.2.3
SQLAlchemy==2.0.4 |
|
А вот еще лайфхак: используйте pip-tools для управления зависимостями. Создаете файл requirements.in с основными библиотеками:
| Python | 1
2
3
| flask
sqlalchemy
psycopg2-binary |
|
И компилируете его в requirements.txt с фиксацией всех зависимостей:
| Bash | 1
| pip-compile requirements.in |
|
Конфигурация для разных окружений
Здесь часто происходят забавные истории. Однажды мой коллега случайно запустил тестовые данные на продакшен-базе. Почему? Потому что конфигурация была хардкодом. Правильный подход — использовать переменные окружения и разные конфигурации для разных сред. Вот как можно организовать config.py:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hardcoded-secret-for-dev-only'
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class ProductionConfig(Config):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
# Выбор конфигурации по переменной окружения
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
def get_config():
return config[os.environ.get('FLASK_ENV', 'default')] |
|
В __init__.py вашего приложения:
| Python | 1
2
3
4
5
6
7
| from config import get_config
def create_app():
app = Flask(__name__)
app.config.from_object(get_config())
# Остальная инициализация
return app |
|
Секреты и переменные среды
Никогда, слышите, НИКОГДА не храните пароли, ключи API и другие секреты в коде! Звучит очевидно, но я видел столько репозиториев с закоммиченными секретами...
Вместо этого используйте переменные окружения или файлы .env (только не забудьте добавить их в .gitignore!). Для работы с .env файлами рекомендую библиотеку python-dotenv:
| Python | 1
2
3
4
| from dotenv import load_dotenv
load_dotenv() # Загружает переменные из файла .env
# Теперь можно использовать os.environ.get() |
|
А вот пример .env файла:
| Python | 1
2
3
| DATABASE_URL=postgresql://user:password@localhost/dbname
SECRET_KEY=your-secret-key-here
FLASK_ENV=development |
|
И не поленитесь создать примерный файл .env.example без реальных значений, который можно коммитить в репозиторий:
| Python | 1
2
3
| DATABASE_URL=
SECRET_KEY=
FLASK_ENV=development |
|
Отдельный вопрос — база данных. Многие Flask-приложения используют SQLite для разработки, но в продакшене нужно что-то посерьезнее. PostgreSQL — отличный выбор. Вот как можно настроить приложение для работы с разными базами:
| Python | 1
| app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL') or 'sqlite:///app.db' |
|
При деплое на Heroku или подобные платформы, они автоматически создают переменную окружения DATABASE_URL, указывающую на их PostgreSQL.
Деплой проекта на Flask Есть небольшой py script, есть домен с самоподписанным ssl-сертификатом, есть настроенная бд на... Flask так сказать изучаю "мега туториал flask" строка "from app import app" Объясните что всё это означает?
Почему app подчеркнуто красным?
В чём ошибка? Прием json-объекта | Flask, Flask-Security, Telegram-bot Здравствуйте, помогите , пожалуйста, Flask знаю не очень, но что-то смог, писал бота с бд и... Подключить PostgreSQL к Flask API и передавать данные таблицы в flask Нужна срочная и большая помощь, надеюсь только на вас.
Есть Python+QT5 (PYQT5) приложение.
В...
Выбор платформы развертывания
Когда приложение уже готово к деплою, встает вопрос: куда именно его выкатывать? Тут, как говорится, есть варианты. За десять лет работы с веб-приложениями я перепробовал множество платформ, и каждая имеет свои плюсы и минусы.
Heroku против VPS
Долгое время Heroku был золотым стандартом для деплоя Flask-приложений. Почему? Потому что это просто как дважды два: git push heroku master — и через пару минут ваше приложение уже в воздухе. Никаких танцев с бубном вокруг настройки серверов. Вот базовый сетап для деплоя Flask на Heroku:
1. web: gunicorn wsgi:app
1. pip install gunicornpip freeze > requirements.txt
1. from app import create_appapp = create_app()if name == "main": app.run()
1. git initheroku create my-flask-appgit add .git commit -m "Initial commit"git push heroku master
Но у Heroku есть и недостатки. Во-первых, c ноября 2022 года они закрыли бесплатный тир. А во-вторых, цены на платные планы кусаются. За 7$ в месяц вы получите довольно скромный "хоббийный" dynos, который засыпает при отсутствии активности.
VPS (виртуальный частный сервер) — альтернатива для тех, кто готов немного погрузиться в настройку. Лично я часто использую DigitalOcean или Linode для небольших проектов. За те же 5-7$ в месяц вы получаете полноценный сервер, где можете размещать не только Flask-приложение, но и базу данных, и еще пару сервисов впридачу. Однако, с VPS придется заниматься настройкой сервера самостоятельно. Устанавливать nginx, настраивать фаервол, обновлять ОС. Как-то раз я забыл обновить сервер на одном из своих проектов, и через полгода обнаружил, что там накопилось столько уязвимостей, что проще было поднять новый.
Облачные решения и их подводные камни
AWS, Google Cloud Platform, Microsoft Azure — киты облачной индустрии. Они предлагают невероятную гибкость, но и сложность у них соответствующая.
AWS Elastic Beanstalk, например, позволяет развернуть Flask-приложение буквально в несколько кликов. Но попробуйте понять их систему биллинга! Однажды я запустил небольшой тестовый проект на AWS и забыл про него, а через месяц получил счет на 50$. Оказалось, я случайно включил какой-то дополнительный сервис, который тихо съедал деньги.
Google App Engine предлагает более прозрачное ценообразование и хорошо подходит для Flask. Вот минимальная конфигурация для GAE (файл app.yaml):
| YAML | 1
2
3
4
5
6
7
8
| runtime: python39
handlers:
url: /.*
script: auto
env_variables:
FLASK_ENV: "production" |
|
Но что меня всегда удивляло в облачных решениях — это несоответствие между тем, как просто они выглядят на маркетинговых страницах, и сколько времени в итоге уходит на то, чтобы разобраться с их документацией и спецификами.
Docker как универсальное решение
Docker стал моим спасением после нескольких болезненных миграций между хостинг-провайдерами. Принцип "собрал один раз — запустил где угодно" работает как часы.
Базовый Dockerfile для Flask-приложения выглядит примерно так:
| Windows Batch file | 1
2
3
4
5
6
7
8
9
10
11
12
| FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "wsgi:app"] |
|
А еще я всегда добавляю .dockerignore файл, чтобы не копировать лишнее:
| Windows Batch file | 1
2
3
4
5
6
7
8
9
10
| venv/
__pycache__/
*.pyc
*.pyo
*.pyd
.pytest_cache/
.coverage
htmlcov/
.git/
.env |
|
Docker контейнеры можно запускать практически где угодно: на VPS, в облачных сервисах, специализированных платформах вроде Heroku или DigitalOcean App Platform.
Один раз я столкнулся с интересной проблемой: приложение прекрасно работало на моем ноутбуке с MacOS, но падало в Docker-контейнере на Linux. Причина оказалось в регистрозависимости путей — на MacOS import Utils и import utils работают одинаково, а на Linux это разные модули. Такие вот "веселые" особености разработки.
Kubernetes для масштабируемых решений
Если ваш проект вырос из категории "домашний питомец" в полноценный "скот" (как шутят девопсы), возможно, пришло время взглянуть на Kubernetes. Когда-то я считал, что это перебор для Flask-приложений, но однажды мне пришлось масштабировать сервис с 100 до 10000 запросов в минуту, и K8s спас положение. Для Flask приложения в Kubernetes понадобится дополнительная конфигурация. Вот упрощенный пример deployment.yaml:
| YAML | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
spec:
replicas: 3 # Количество инстансов
selector:
matchLabels:
app: flask-app
template:
metadata:
labels:
app: flask-app
spec:
containers:
- name: flask-app
image: your-registry/flask-app:latest
ports:
- containerPort: 5000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: flask-secrets
key: database-url |
|
Честно скажу, порог входа в Kubernetes высоковат. Я потратил недели на изучение этой технологии. Зато теперь могу одним коммандом раскатать новую версию приложения, откатиться на предыдущую версию или масштабировать сервис в зависимости от нагрузки.
Serverless развертывание - AWS Lambda и Google Cloud Functions
Серверлесс подход полностью меняет модель деплоя. Вместо постоянно работающего сервера, ваш код выполняется по требованию. Удобно для API с неравномерной нагрузкой.
Для Flask это может выглядеть немного странно, так как серверлесс-функции обычно не являются полноценными веб-серверами. Например, для AWS Lambda можно использовать адаптер вроде zappa:
| Bash | 1
2
3
| pip install zappa
zappa init
zappa deploy dev |
|
И настройка в zappa_settings.json:
| JSON | 1
2
3
4
5
6
7
8
9
10
| {
"dev": {
"app_function": "wsgi.app",
"aws_region": "us-west-2",
"profile_name": "default",
"project_name": "flask-app",
"runtime": "python3.9",
"s3_bucket": "your-deployment-bucket"
}
} |
|
У серверлесс есть свои особенности. Помню случай, когда я получил странный баг — приложение теряло данные между запросами. Оказалось, что я наивно хранил состояние в глобальной переменной, но в мире серверлесс каждый запрос может обрабатываться отдельным инстансом функции.
PaaS-решения - Railway, Render и другие альтернативы Heroku
После заката бесплатного тира Heroku многие перешли на другие PaaS. Railway — один из моих фаворитов для небольших проектов. Деплой через GitHub, простой интерфейс, и цены начинаются буквально с нескольких долларов. Render тоже заслуживает внимания — их подход "zero-config" для стандартных стеков радует. Для Flask приложения достаточно репозитория с requirements.txt и переменными окружения. Fly.io выделяется тем, что размещает ваши приложения в ЦОДах по всему миру, ближе к пользователям. А их подход к настройке напоминает Heroku, что упрощает миграцию.
При выборе платформы я в первую очередь обращаю внимание на:
1. Простоту деплоя и обновления.
1. Ценовую политику (особенно при масштабировании).
1. Наличие дополнительных сервисов (база данных, кэш).
1. Возможность настройки CI/CD.
Нет универсального решения — для прототипа подойдет простой PaaS, для production-системы с высокой нагрузкой скорее понадобится Kubernetes, а для API с неравномерным трафиком можно рассмотреть серверлесс.
Настройка веб-сервера
Многие разработчики падают в обморок, когда речь заходит о настройке веб-сервера. Помню, как сам впервые столкнулся с этим — сплошные непонятные термины, конфигурационные файлы и какие-то сокеты. Но давайте разберемся по порядку.
WSGI серверы — Gunicorn и альтернативы
Первое, что нужно понять: встроенный в Flask веб-сервер (app.run()) — это игрушка для разработки. Использовать его в продакшене — все равно что ездить на картонной машине по автобану. Нужен WSGI-сервер (Web Server Gateway Interface) — промежуточный слой между вашим приложением и внешним миром.
Gunicorn (Green Unicorn) — золотой стандарт для Flask-приложений. Настраивается элементарно:
| Bash | 1
| gunicorn wsgi:app -w 4 -b 0.0.0.0:8000 |
|
Где -w 4 указывает на четыре рабочих процесса, а -b — на адрес и порт для привязки.
Более полная конфигурация через файл gunicorn.conf.py:
| Python | 1
2
3
4
5
6
7
| bind = "0.0.0.0:8000"
workers = 4 # Обычно 2-4 воркера на каждое ядро процессора
worker_class = "gevent" # Асинхронные воркеры для лучшей производительности
max_requests = 1000 # Перезапуск воркера после 1000 запросов
max_requests_jitter = 50 # Случайное число до 50, чтобы избежать одновременного перезапуска
timeout = 30 # Таймаут в секундах
keepalive = 2 # Время в секундах для удержания соединения |
|
Альтернативы Gunicorn? uWSGI и Waitress. uWSGI мощнее, но и сложнее в настройке. Waitress привлекает кросс-платформенностью — в отличие от Gunicorn, работает и на Windows.
Моя личная рекомендация — начинайте с Gunicorn, его конфигурация интуитивно понятна и хорошо документирована. Только если у вас есть особые требования (например, супер-производительность или Windows-сервер), смотрите в сторону альтернатив.
Nginx как обратный прокси
Мой дядя как-то сказал: "Любой нормальный сервер без Nginx — как дорогая тачка с лысой резиной". И я с ним согласен. Nginx работает как обратный прокси — принимает запросы от пользователей и перенаправляет их вашему WSGI-серверу. Зачем этот лишний слой? Во-первых, Nginx отлично обрабатывает статические файлы, во-вторых, может терминировать SSL, в-третьих, выступает буфером против DDoS-атак.
Минимальная конфигурация /etc/nginx/sites-available/flask-app.conf:
| JSON | 1
2
3
4
5
6
7
8
9
10
| server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
} |
|
После создания файла не забудьте:
| Bash | 1
2
3
| sudo ln -s /etc/nginx/sites-available/flask-app.conf /etc/nginx/sites-enabled/
sudo nginx -t # Проверка конфигурации
sudo systemctl restart nginx |
|
Однажды у меня был случай, когда все работало локально, но падало на сервере. Оказалось, Nginx по умолчанию имеет ограничение на размер тела запроса в 1 MB. Решение? Добавьте client_max_body_size 10M; в блок server или http.
Обработка статических файлов
Помните, я говорил, что Nginx классно работает со статикой? Вот как это настроить:
| JSON | 1
2
3
4
5
6
7
8
9
10
11
12
13
| server {
# ... другие настройки ...
location /static/ {
alias /path/to/your/flask/app/static/;
expires 30d; # Кэширование в браузере на 30 дней
}
location / {
proxy_pass http://localhost:8000;
# ... другие заголовки ...
}
} |
|
Для Flask приложения убедитесь, что статические файлы находятся в папке static и доступны через url_for('static', filename='...'). Если у вас большое количество статического контента, стоит задуматься о выносе его на CDN (Content Delivery Network). Но это уже совсем другая история.
SSL-сертификаты и HTTPS настройка
В 2023 году сайт без HTTPS — моветон и потенциальная дыра в безопасности. Не один раз я наблюдал, как незашифрованные данные перехватываются в публичных сетях. Раньше получение SSL-сертификата было проблемой: дорого и сложно. Сейчас, благодаря Let's Encrypt, это бесплатно и относительно просто. Установите certbot:
| Bash | 1
| sudo apt install certbot python3-certbot-nginx |
|
И запустите для автоматической настройки:
| Bash | 1
| sudo certbot --nginx -d example.com -d www.example.com |
|
Certbot модифицирует вашу конфигурацию Nginx, добавляя новый блок server для HTTPS и настраивая редирект с HTTP.
После этого в вашей конфигурации появится нечто вроде:
| JSON | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# ... другие настройки SSL ...
location / {
proxy_pass http://localhost:8000;
# ... прочие настройки прокси ...
}
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
} |
|
Первый блок обрабатывает HTTPS-трафик, второй просто перенаправляет HTTP на HTTPS.
Автоматическое обновление SSL-сертификатов с Let's Encrypt
Важный момент с сертификатами Let's Encrypt: они действительны всего 90 дней. Поэтому надо настроить автоматическое обновление, чтобы не проснуться однажды с ошибкой SSL в браузере и паникующими пользователями. Certbot устанавливает задачу в cron, которая проверяет срок действия сертификатов и обновляет их:
| Bash | 1
| sudo systemctl status certbot.timer |
|
Но лучше проверить, что обновление работает:
| Bash | 1
| sudo certbot renew --dry-run |
|
Если все хорошо, система покажет, что тестовое обновление прошло успешно. Я когда-то забыл про это и получил проблемы на боевом сайте – пользователи видели страшное предупреждение, а трафик упал на 70%. Не повторяйте моих ошибок!
Тонкая настройка безопасности
После настройки HTTPS стоит добавить дополнительные заголовки безопасности. Вот пример для Nginx:
| JSON | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| server {
# ... другие настройки ...
# Защита от кликджекинга
add_header X-Frame-Options "SAMEORIGIN";
# Защита от XSS
add_header X-XSS-Protection "1; mode=block";
# Запрет угадывания MIME типов
add_header X-Content-Type-Options "nosniff";
# Политика контента
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:;";
# ... остальная конфигурация ...
} |
|
Безопасность – это процесс, а не состояние. Я регулярно прогоняю свои сайты через инструменты вроде Mozilla Observatory или SSL Labs для проверки конфигурации.
Управление процессами
Для стабильной работы Flask в продакшене нужен менеджер процессов, который автоматически перезапустит ваш сервер в случае сбоя или после перезагрузки системы. Мой выбор – systemd:
Создайте файл /etc/systemd/system/flask-app.service:
| Code | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| [Unit]
Description=Flask App with Gunicorn
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/your/flask/app
Environment="PATH=/path/to/your/venv/bin"
ExecStart=/path/to/your/venv/bin/gunicorn -c gunicorn.conf.py wsgi:app
Restart=always
[Install]
WantedBy=multi-user.target |
|
Затем активируйте сервис:
| Bash | 1
2
| sudo systemctl enable flask-app
sudo systemctl start flask-app |
|
Проверьте статус:
| Bash | 1
| sudo systemctl status flask-app |
|
Таким образом, даже если ваш сервер перезагрузится, приложение запустится автоматически. А если процесс упадет, systemd его перезапустит.
Кстати, одна интересная особенность: если вы используете socket активацию в systemd, ваше приложение может запускаться только когда приходит первый запрос. Это экономит ресурсы сервера, но добавляет задержку при первом обращении.
С настройкой веб-сервера разобрались. Теперь ваше Flask-приложение работает за Nginx, использует HTTPS, автоматически перезапускается и защищено основными заголовками безопасности. Можно двигаться дальше – к стратегиям кеширования, которые помогут справиться с нагрузкой и улучшить время отклика.
Выбор стратегии кэширования для Flask приложений
Даже самый быстрый код Flask при нагрузке может превратиться в черепаху. Не раз сталкивался с ситуацией, когда приложение летало на тестовом стенде, но стоило запустить его под реальной нагрузкой — и начинались проблемы. Таймауты, тормоза, а в худшем случае падение сервера.
Кэширование на уровне приложения
Самый простой способ начать с кэширования во Flask — использовать расширение Flask-Caching. Устанавливается просто:
| Bash | 1
| pip install flask-caching |
|
Базовая настройка:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| from flask import Flask
from flask_caching import Cache
app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'})
@app.route('/expensive-operation')
@cache.cached(timeout=60) # Кэшируем результат на 60 секунд
def expensive_operation():
# Здесь какая-то тяжелая операция
return result |
|
SimpleCache хранит данные в памяти и подходит только для разработки или маленьких приложений с одним процессом. В продакшене лучше использовать распределенное хранилище типа Redis. Кроме декоратора @cache.cached() для маршрутов, можно кэшировать отдельные функции:
| Python | 1
2
3
| @cache.memoize(timeout=50)
def get_user(user_id):
return User.query.get(user_id) |
|
Разница между cached и memoize в том, что memoize учитывает аргументы функции при создании ключа кэша, а cached — нет.
Что кэшировать в первую очередь? Мой опыт подсказывает:
1. Тяжелые запросы к базе данных,
1. Расчеты и агрегации,
1. Данные, которые редко меняются, но часто запрашиваются,
Но будьте осторожны с инвалидацией кэша. Особено при обновлении данных:
| Python | 1
2
3
4
5
6
7
| @app.route('/update-user/<int:user_id>', methods=['POST'])
def update_user(user_id):
# Обновляем пользователя
# ...
# Инвалидируем кэш для этого пользователя
cache.delete_memoized(get_user, user_id)
return "Updated" |
|
Кэширование на уровне базы данных
SQLAlchemy, которую часто используют с Flask, имеет встроенные механизмы кэширования. Например, сессионный кэш:
| Python | 1
2
| user = User.query.get(1) # Запрос к БД
same_user = User.query.get(1) # Берется из кэша сессии, без запроса к БД |
|
Но это работает только в рамках одной сессии. Для более продвинутого кэширования запросов можно использовать SQLAlchemy Query Cache:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| from dogpile.cache import make_region
region = make_region().configure(
'dogpile.cache.redis',
arguments = {
'host': 'localhost',
'port': 6379,
'db': 0,
'redis_expiration_time': 60*60*2, # 2 часа
}
)
@region.cache_on_arguments()
def get_popular_products():
return Product.query.filter(Product.popular == True).all() |
|
Кэширование с Redis и memcached
Для серьезных приложений с несколькими инстансами кэширование в памяти не подходит. Тут на помощь приходят Redis и memcached.
Redis — мой личный фаворит из-за богатого функционала. Он работает не только как кэш, но и как хранилище сессий, очередь задач, и даже как простая БД. Настроить Flask-Caching с Redis элементарно:
| Python | 1
2
3
4
5
6
| cache = Cache(app, config={
'CACHE_TYPE': 'RedisCache',
'CACHE_REDIS_HOST': 'localhost',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 0
}) |
|
Для Memcached настройка похожа:
| Python | 1
2
3
4
| cache = Cache(app, config={
'CACHE_TYPE': 'MemcachedCache',
'CACHE_MEMCACHED_SERVERS': ['127.0.0.1:11211']
}) |
|
Выбор между Redis и Memcached? Redis гибче и функциональнее, Memcached проще и иногда быстрее для базового кэширования. Но честно говоря, для большинства проектов разница не критична.
Управление сессиями через Redis
Еще один важный аспект — хранение сессий. По умолчанию Flask использует подписанные куки, но для масштабируемых приложений лучше хранить данные сессий в централизованном хранилище:
| Python | 1
2
3
4
5
| from flask_session import Session
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = Redis(host='localhost', port=6379)
Session(app) |
|
Теперь данные сессий хранятся в Redis, а в куки остается только идентификатор сессии. Это особенно полезно при горизонтальном масштабировании.
Фрагментарное кэширование шаблонов
Если используете шаблонизатор Jinja2 (а он идет в комплекте с Flask), можно кэшировать части шаблонов:
| Python | 1
2
3
4
5
6
7
8
| @app.route('/')
def index():
homepage_news = cache.get('homepage_news')
if homepage_news is None:
homepage_news = get_latest_news() # Тяжелая операция
cache.set('homepage_news', homepage_news, timeout=300) # 5 минут
return render_template('index.html', news=homepage_news) |
|
CDN для статического контента
Статические файлы (CSS, JavaScript, изображения) лучше раздавать через CDN (Content Delivery Network). CDN — это сеть серверов, распределенных географически, которые кэшируют контент ближе к пользователям. Для небольших проектов я часто использую Cloudflare — у них есть бесплатный план и настройка занимает буквально минуты. Для крупных проектов есть Amazon CloudFront, Google Cloud CDN и другие.
В Flask можно настроить URL для статики через переменную STATIC_URL:
| Python | 1
| app.config['STATIC_URL'] = 'https://cdn.example.com/static/' |
|
А в шаблонах:
| HTML5 | 1
| <img src="{{ url_for('static', filename='logo.png') }}"> |
|
Это автоматически преобразуется в URL с вашим CDN.
HTTP-кэширование
Не забывайте про базовое HTTP-кэширование. Правильные заголовки могут значительно снизить нагрузку на сервер:
| Python | 1
2
3
4
5
6
| @app.route('/api/products')
def products_api():
response = make_response(jsonify(products))
# Кэшировать на стороне клиента на 1 час
response.headers['Cache-Control'] = 'public, max-age=3600'
return response |
|
Для динамического контента, который может меняться, используйте условные заголовки:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| @app.route('/api/user/<int:user_id>')
def user_api(user_id):
user = get_user(user_id)
last_modified = user.updated_at.timestamp()
# Проверка If-Modified-Since
if request.if_modified_since and request.if_modified_since >= last_modified:
return '', 304 # Not Modified
response = make_response(jsonify(user.to_dict()))
response.headers['Last-Modified'] = format_date_time(last_modified)
return response |
|
Оптимизация запросов к базе данных
Часто проблемы производительности связаны не с самим Flask, а с базой данных. Проблема N+1 запросов — классический пример, когда для списка объектов делается отдельный запрос для каждой связи. SQLAlchemy решает это через опцию joinedload:
| Python | 1
2
3
4
5
6
7
8
9
| # Плохо: N+1 запросов
users = User.query.all()
for user in users:
print(user.profile.name) # Для каждого пользователя отдельный запрос к профилю
# Хорошо: 1 запрос с JOIN
users = User.query.options(joinedload(User.profile)).all()
for user in users:
print(user.profile.name) # Данные уже загружены |
|
Асинхронность и задачи в фоне
Некоторые операции (например, отправка email, генерация отчетов) можно вынести в фоновые задачи. Для этого часто используют Celery с брокером сообщений вроде Redis или RabbitMQ:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| from flask import Flask
from celery import Celery
app = Flask(__name__)
celery = Celery(app.name, broker='redis://localhost:6379/0')
@celery.task
def send_email(recipient, subject, body):
# Логика отправки email
pass
@app.route('/register', methods=['POST'])
def register():
# Регистрация пользователя
send_email.delay(user.email, 'Welcome!', 'Thanks for registering')
return 'Registration successful' |
|
В этом примере .delay() ставит задачу в очередь, и она выполняется фоновым процессом Celery, не блокируя основное приложение.
Производительность Flask-приложения — это комплексная проблема, и кэширование — лишь один из аспектов. Но грамотно внедренное кэширование может дать колоссальный прирост скорости и устойчивости к нагрузкам.
Автоматизация развертывания через CI/CD пайплайны
Всегда улыбаюсь, когда вспоминаю, как раньше деплоил приложения: SSH-соединение с сервером, ручное копирование файлов, сбивчивые команды... и неизбежное "упс, забыл обновить зависимости". После пары ночных фиксов на продакшене я понял — пора автоматизировать этот хаос. Так я познакомился с CI/CD. Continuous Integration (непрерывная интеграция) и Continuous Deployment (непрерывное развертывание) — это как автопилот для вашего кода. Написал, запушил, а дальше магия: автоматические тесты, сборка и деплой. Без нервов, ручных операций и человеческих ошибок.
GitHub Actions для Flask
GitHub Actions — мой фаворит из-за простоты настройки. Создайте файл .github/workflows/deploy.yml:
| YAML | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| name: Deploy Flask App
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Run tests
run: pytest
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.12.12
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: "your-app-name"
heroku_email: ${{ secrets.HEROKU_EMAIL }} |
|
Этот конфиг делает две вещи: запускает тесты и, если они прошли, деплоит на Heroku. Обратите внимание на secrets — это переменные, которые нужно настроить в репозитории (Settings -> Secrets).
Однажды я забыл добавить секреты и битый час пытался понять, почему деплой не работает. Классическая ошибка новичка.
GitLab CI для контейнеризированных приложений
Если ваше Flask-приложение в Docker, GitLab CI — отличный выбор. Создайте .gitlab-ci.yml:
| YAML | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| stages:
- test
- build
- deploy
test:
stage: test
image: python:3.9
script:
- pip install -r requirements.txt
- pip install pytest
- pytest
build:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- ssh $SERVER_USER@$SERVER_IP "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG && docker-compose up -d"
only:
- main |
|
Здесь три этапа: тесты, сборка Docker-образа и деплой на сервер через SSH. В реальном проекте я настраивал также прогон статического анализа кода (flake8, black) и сканирование уязвимостей.
Jenkins для корпоративных решений
В корпоративной среде часто встречается Jenkins. Вот пример Jenkinsfile для Flask:
| Groovy | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Setup') {
steps {
sh 'python -m venv venv'
sh '. venv/bin/activate && pip install -r requirements.txt'
}
}
stage('Test') {
steps {
sh '. venv/bin/activate && pytest'
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh 'fab deploy_staging'
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
input message: 'Deploy to production?'
sh 'fab deploy_production'
}
}
}
post {
always {
cleanWs()
}
}
} |
|
Ключевая особенность — этап подтверждения перед деплоем на продакшен. В прошлом проекте это спасло меня от случайного релиза экспериментальной фичи, которая была не готова к боевым условиям.
CircleCI для комплексных пайплайнов
CircleCI отлично подходит для сложных рабочих процессов. Вот пример .circleci/config.yml:
| YAML | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| version: 2.1
orbs:
python: circleci/python@1.4
heroku: circleci/heroku@1.2
workflows:
deploy:
jobs:
- build-and-test
- deploy:
requires:
- build-and-test
filters:
branches:
only: main
jobs:
build-and-test:
docker:
- image: cimg/python:3.9
- image: cimg/postgres:13.3
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: test_db
steps:
- checkout
- python/install-packages:
pkg-manager: pip
packages:
- '.'
- run:
name: Run tests
command: pytest
deploy:
executor: heroku/default
steps:
- checkout
- heroku/install
- heroku/deploy-via-git:
app-name: $HEROKU_APP_NAME |
|
Здесь я настроил не только тесты и деплой, но и поднял PostgreSQL для полноценного тестирования с базой данных.
Стратегии деплоя
Важный вопрос — как деплоить без простоев? Для Flask есть несколько стратегий:
1. Blue-Green Deployment — подготавливаем новую версию на отдельном сервере и переключаем трафик только когда все готово:
| Python | 1
2
3
4
5
| # В Nginx можно настроить переключение upstream
upstream flask_app {
server blue.example.com; # Текущая версия
# server green.example.com; # Новая версия (закомментирована)
} |
|
1. Rolling Updates — обновляем сервера по одному:
| Bash | 1
2
| # Пример скрипта для Kubernetes
kubectl set image deployment/flask-app flask-app=your-registry/flask-app:new-version --record |
|
1. Canary Deployment — выкатываем новую версию для части пользователей:
| JSON | 1
2
3
4
5
| # Пример Nginx конфига для канареечных релизов
upstream flask_app {
server old.example.com weight=9; # 90% трафика
server new.example.com weight=1; # 10% трафика
} |
|
Как-то раз я чуть не положил продакшен, потому что не учел миграции базы данных в CI/CD пайплайне. Урок: автоматизируйте все, включая миграции!
Непрерывная интеграция и тестирование
Помимо базовых тестов, CI/CD позволяет запускать:- Статический анализ кода (flake8, pylint),
- Проверку типов (mypy),
- Интеграционные тесты с реальной базой данных,
- Нагрузочное тестирование для критичных эндпоинтов.
Для Flask особенно полезно проверять работу с моделями SQLAlchemy — часто миграции ломаются при внесении изменений.
Версионирование в CI/CD
Важный аспект CI/CD — правильное версионирование. Я часто использую семантическое версионирование и git-tags:
| Bash | 1
2
3
4
5
6
7
8
9
10
| # Создаем тег для новой версии
git tag -a v1.2.3 -m "Version 1.2.3"
git push origin v1.2.3
[/PYTHON]
В CI/CD можно настроить чтение версии из тега:
[/PYTHON]yaml
# GitHub Actions пример
name: Get version from tag
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} |
|
Автоматизация деплоя — это не просто удобство, а необходимость для современной разработки. Когда правильно настроен CI/CD, вы можете сосредоточиться на написании кода, а рутину оставить машинам. А если что-то пойдет не так, у вас всегда будет возможность быстро откатиться к предыдущей рабочей версии.
Мониторинг и отладка
Как-то раз я получил панический звонок от клиента в три часа ночи: "Все упало!" Залогинившись на сервер, я обнаружил пустоту — ни логов, ни подсказок, где искать проблему. Пришлось по крупицам восстанавливать, что произошло. С тех пор я всегда уделяю особое внимание мониторингу и отладке Flask-приложений в продакшене.
Логирование в продакшене
Логирование — это ваши глаза и уши, когда дело доходит до отслеживания поведения приложения в боевых условиях. Базовая настройка логирования во Flask выглядит так:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import logging
from logging.handlers import RotatingFileHandler
import os
def setup_logging(app):
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Flask application startup') |
|
Вызовите эту функцию при инициализации приложения:
| Python | 1
2
| app = Flask(__name__)
setup_logging(app) |
|
Теперь вы можете логировать события в любой части приложения:
| Python | 1
2
3
4
5
6
7
8
9
10
| @app.route('/api/resource', methods=['POST'])
def create_resource():
app.logger.info('Creating new resource')
try:
# Логика создания ресурса
app.logger.info(f'Resource {resource.id} created successfully')
return jsonify(resource.to_dict())
except Exception as e:
app.logger.error(f'Error creating resource: {str(e)}')
return jsonify({'error': 'Could not create resource'}), 500 |
|
Однако для серьезных приложений я предпочитаю структурированное логирование в формате JSON. Это позволяет легко парсить логи и интегрироваться с системами анализа логов:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import json
from datetime import datetime
class JsonFormatter(logging.Formatter):
def format(self, record):
log_record = {
'timestamp': datetime.utcnow().isoformat(),
'level': record.levelname,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno
}
if hasattr(record, 'request_id'):
log_record['request_id'] = record.request_id
if record.exc_info:
log_record['exception'] = self.formatException(record.exc_info)
return json.dumps(log_record) |
|
Пару лет назад я работал над API, которое обрабатывало около миллиона запросов в день. Без структурированных логов разобраться в проблемах было бы невозможно.
Централизованное логирование
Для приложений, запущенных на нескольких серверах, критично иметь централизованное логирование. Лично я предпочитаю стек ELK (Elasticsearch, Logstash, Kibana) или более современную альтернативу — Grafana Loki.
Для интеграции с ELK можно использовать Filebeat, который будет отправлять логи с сервера в Logstash:
| YAML | 1
2
3
4
5
6
7
| # filebeat.yml
filebeat.inputs:
type: log
paths:
- /path/to/your/flask/logs/*.log
output.logstash:
hosts: ["logstash:5044"] |
|
Отслеживание ошибок
Для Flask существует несколько отличных сервисов для отслеживания ошибок. Мой личный фаворит — Sentry. Интеграция предельно проста:
| Python | 1
2
3
4
5
6
7
8
9
10
| import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(
dsn="ваш-sentry-dsn",
integrations=[FlaskIntegration()],
traces_sample_rate=1.0 # Для APM, можно уменьшить в продакшене
)
app = Flask(__name__) |
|
Теперь все необработанные исключения будут автоматически отправляться в Sentry, вместе с контекстом запроса, стеком вызовов и переменными окружения. Однажды Sentry помог мне выявить сложный баг, который проявлялся только у пользователей с определенным браузером и только при выполнении конкретной последовательности действий. Без инструментов отслеживания ошибок я бы потратил недели на его поиск.
Для более глубокого понимания того, что происходит в приложении, я обычно добавляю собственные контексты:
| Python | 1
2
3
4
5
6
7
8
| from sentry_sdk import configure_scope
@app.before_request
def before_request():
with configure_scope() as scope:
if current_user.is_authenticated:
scope.user = {"id": current_user.id, "email": current_user.email}
scope.set_tag("route", request.endpoint) |
|
Профилирование и метрики производительности
Знать, что ваше приложение упало — это хорошо, но еще лучше знать, что оно начинает тормозить, до того как пользователи начнут жаловаться. Для этого нужны метрики производительности. Самый простой способ добавить базовое профилирование — использовать middleware:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| import time
from flask import request, g
@app.before_request
def start_timer():
g.start = time.time()
@app.after_request
def log_request(response):
if hasattr(g, 'start'):
elapsed = time.time() - g.start
app.logger.info(f"{request.method} {request.path} completed in {elapsed:.2f}s")
return response |
|
Для более детального профилирования я рекомендую Flask-Profiler или интеграцию с New Relic:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
| from flask_profiler import Profiler
app = Flask(__name__)
app.config["flask_profiler"] = {
"enabled": True,
"storage": {
"engine": "sqlite",
"FILE": "profiler.sqlite"
}
}
profiler = Profiler(app) |
|
Эта библиотека создает красивый веб-интерфейс, где можно увидеть самые медленные эндпоинты и выявить узкие места.
Но для серьезного мониторинга нужны более мощные инструменты. Prometheus с Grafana — это как Феррари в мире мониторинга. Для Flask существует удобная библиотека prometheus-flask-exporter:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| from prometheus_flask_exporter import PrometheusMetrics
metrics = PrometheusMetrics(app)
# Стандартные метрики включают:
# - Количество запросов
# - Время ответа по квантилям
[H2]- Размер ответа[/H2]
# Можно добавлять и свои метрики
custom_counter = metrics.counter(
'flask_my_custom_counter', 'Количество определенных действий',
labels={'action': lambda: request.endpoint}
)
@app.route('/api/action')
@custom_counter
def api_action():
# Ваша логика
return jsonify({'status': 'ok'}) |
|
Интеграция с внешними системами мониторинга
Но если вам нужно что-то проще, могу порекомендовать Datadog или New Relic — они предлагают готовые решения для мониторинга Python-приложений с минимальной настройкой.
Еще один мощный инструмент диагностики — трассировка распределенных запросов. Когда ваше приложение разрастается до микросервисной архитектуры, отследить полный путь запроса становится настоящим квестом. Тут на помощь приходят системы вроде Jaeger или Zipkin.
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| from flask import Flask
from opentelemetry import trace
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentation
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
app = Flask(__name__)
# Настраиваем трассировку
resource = Resource(attributes={SERVICE_NAME: "flask-service"})
provider = TracerProvider(resource=resource)
jaeger_exporter = JaegerExporter(
agent_host_name="localhost",
agent_port=6831,
)
processor = BatchSpanProcessor(jaeger_exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
# Добавляем инструментацию Flask
FlaskInstrumentation().instrument_app(app)
# Создаем трейсер для ручного создания спанов
tracer = trace.get_tracer(__name__) |
|
С такой настройкой вы получите полную карту запроса — от первого хита до фронтенда до последнего запроса к базе данных. Визуализация в Jaeger UI поможет легко найти узкие места.
Мониторинг состояния здоровья
Почти в каждом проекте я создаю специальный эндпоинт для проверки "здоровья" приложения:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| @app.route('/health')
def health_check():
status = {
'status': 'ok',
'db_connection': check_db_connection(),
'redis_connection': check_redis_connection(),
'version': app.config.get('VERSION', 'unknown'),
'uptime_seconds': int(time.time() - app_start_time)
}
# Если что-то не в порядке, меняем статус и код ответа
if not status['db_connection'] or not status['redis_connection']:
status['status'] = 'degraded'
return jsonify(status), 503
return jsonify(status) |
|
Этот эндпоинт можно использовать не только для автоматического мониторинга, но и для балансировщиков нагрузки — чтобы они не отправляли запросы на нездоровые инстансы.
Для более продвинутого мониторинга здоровья я использую python-healthcheck:
| Python | 1
2
3
4
5
6
7
8
9
10
| from healthcheck import HealthCheck
health = HealthCheck()
# Добавляем проверки
health.add_check(check_db_connection)
health.add_check(check_redis_connection)
# Регистрируем эндпоинт
app.add_url_rule('/healthcheck', 'healthcheck', view_func=lambda: health.run()) |
|
Автоматические оповещения
Что толку от мониторинга, если никто не видит алерты? Я интегрирую системы мониторинга с Slack, Telegram и электронной почтой:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| def send_alert(message, severity='warning'):
if severity == 'critical':
# Отправка в Slack и SMS
requests.post(SLACK_WEBHOOK, json={'text': f'КРИТИЧЕСКАЯ ОШИБКА: {message}'})
send_sms_alert(message)
else:
# Только Slack для некритичных проблем
requests.post(SLACK_WEBHOOK, json={'text': f'Предупреждение: {message}'})
# Логируем все алерты
app.logger.error(f"ALERT: {severity} - {message}") |
|
Отладка проблем в продакшене
Иногда, несмотря на все инструменты, приходится заниматься отладкой непосредственно в продакшене. Вот несколько приемов, которые я использую:
1. Временное логирование — добавляем расширенное логирование только для конкретного запроса:
| Python | 1
2
3
4
5
6
7
8
9
10
| @app.route('/problematic-endpoint')
def problematic_endpoint():
request_id = str(uuid.uuid4())
app.logger.info(f"[DEBUG:{request_id}] Starting problematic endpoint")
# Ваш код с доп. логированием
result = some_function()
app.logger.info(f"[DEBUG:{request_id}] Result: {result}")
return jsonify({'result': result}) |
|
1. Переключатели функций (feature flags) — позволяют включать/выключать функционал без редеплоя:
| Python | 1
2
3
4
5
6
| @app.route('/api/feature')
def feature_endpoint():
if app.config.get('ENABLE_NEW_FEATURE'):
return new_implementation()
else:
return old_implementation() |
|
1. Снимки состояния — сохраняем полное состояние перед ошибкой:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| try:
# Сложная операция
pass
except Exception as e:
# Сохраняем контекст
snapshot = {
'user_id': current_user.id,
'params': request.args,
'data': request.json,
'time': datetime.utcnow().isoformat()
}
save_error_snapshot(snapshot)
raise |
|
Эффективный мониторинг и отладка — это не просто набор инструментов, а целая философия. С годами я понял: лучше потратить день на настройку хорошего мониторинга, чем неделю на поиск загадочного бага в три часа ночи. Правильно настроеная система наблюдения не только экономит время разработчиков, но и повышает доверие пользователей — ведь часто вы узнаете о проблеме раньше, чем они.
Пример развертывания с конфигурационными файлами
В этом разделе я покажу весь путь от исходного кода до работающего в продакшене сервиса, который я недавно настраивал для одного стартапа. Начнем с структуры проекта:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| my_awesome_app/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ ├── services.py
│ ├── static/
│ └── templates/
├── migrations/
├── tests/
├── .env.example
├── .gitignore
├── config.py
├── docker-compose.yml
├── Dockerfile
├── gunicorn.conf.py
├── nginx.conf
├── requirements.txt
└── wsgi.py |
|
Содержимое ключевых файлов:
wsgi.py — точка входа для сервера:
| Python | 1
2
3
4
5
6
| from app import create_app
app = create_app()
if __name__ == "__main__":
app.run() |
|
app/init.py — фабрика приложения:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import os
from config import config
db = SQLAlchemy()
migrate = Migrate()
def create_app():
app = Flask(__name__)
config_name = os.environ.get('FLASK_ENV', 'development')
app.config.from_object(config[config_name])
# Инициализация расширений
db.init_app(app)
migrate.init_app(app, db)
# Регистрация blueprints
from app.routes import main_bp
app.register_blueprint(main_bp)
# Настройка логирования в продакшене
if not app.debug and not app.testing:
import logging
from logging.handlers import RotatingFileHandler
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Flask application startup')
return app |
|
config.py — конфигурация для разных окружений:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key-please-change-in-production'
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'dev.db')
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///:memory:'
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'prod.db')
# Настройки безопасности для продакшена
@classmethod
def init_app(cls, app):
# Настройка ProxyFix для работы за Nginx
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
} |
|
Dockerfile — для контейнеризации приложения:
| Windows Batch file | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| FROM python:3.9-slim
WORKDIR /app
# Копируем сначала только requirements.txt для кэширования слоя с зависимостями
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Копируем остальной код
COPY . .
# Создаем непривилегированного пользователя
RUN useradd -m appuser
USER appuser
# Порт, который будет прослушивать приложение
EXPOSE 8000
# Запуск через Gunicorn
CMD ["gunicorn", "--config", "gunicorn.conf.py", "wsgi:app"] |
|
gunicorn.conf.py — настройки для Gunicorn:
| Python | 1
2
3
4
5
6
7
8
9
10
| import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "gevent"
keepalive = 5
timeout = 120
accesslog = "-" # Логи в stdout для Docker
errorlog = "-"
loglevel = "info" |
|
docker-compose.yml — для локальной разработки и простого деплоя:
| YAML | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
env_file:
- .env
volumes:
- ./logs:/app/logs
depends_on:
- db
restart: always
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data
env_file:
- .env
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_USER=${DB_USER}
- POSTGRES_DB=${DB_NAME}
restart: always
nginx:
image: nginx:1.21
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
depends_on:
- web
restart: always
certbot:
image: certbot/certbot
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
volumes:
postgres_data: |
|
nginx.conf — конфигурация Nginx:
| JSON | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| server {
listen 80;
server_name example.com www.example.com;
# Редирект с HTTP на HTTPS
location / {
return 301 https://$host$request_uri;
}
# Для Let's Encrypt
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Рекомендуемые настройки безопасности
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Заголовки безопасности
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# Статические файлы
location /static/ {
alias /app/app/static/;
expires 30d;
}
# Проксирование запросов к Flask
location / {
proxy_pass http://web:8000;
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;
}
} |
|
Теперь о процессе деплоя. Для запуска этого стека на VPS выполняю следующие шаги:
1. Клонирую репозиторий на сервер:
| Bash | 1
2
| git clone https://github.com/username/my_awesome_app.git
cd my_awesome_app |
|
2. Создаю .env файл с реальными переменными окружения:
| Bash | 1
2
| cp .env.example .env
nano .env # Редактирую значения |
|
3. Запускаю первичную инициализацию для SSL:
| Bash | 1
2
| mkdir -p data/certbot/{conf,www}
docker-compose up -d nginx |
|
4. Получаю SSL-сертификаты:
| Bash | 1
| docker-compose run --rm certbot certonly --webroot --webroot-path=/var/www/certbot --email admin@example.com --agree-tos --no-eff-email -d example.com -d www.example.com |
|
5. Перезапускаю всё с SSL:
| Bash | 1
2
| docker-compose down
docker-compose up -d |
|
6. Применяю миграции к базе данных:
| Bash | 1
| docker-compose exec web flask db upgrade |
|
Вот так выглядит полный процесс. Я не первый год занимаюсь деплоем Flask-приложений, и этот набор конфигураций оказался одним из самых удобных. Особенно нравится, что с помощью Docker Compose можно легко перенести всю инфраструктуру на любой сервер буквально за 10 минут.
Для непрерывного развертывания можно добавить скрипт обновления:
| Bash | 1
2
3
4
5
6
| #!/bin/bash
cd /path/to/my_awesome_app
git pull
docker-compose build web
docker-compose up -d
docker-compose exec -T web flask db upgrade |
|
Этот скрипт можно вызывать из CI/CD системы или даже по вебхуку при пуше в мастер-ветку.
Деплой Reactjs приложения на Amazon AWS ec2 instance Пытаюсь развернуть приложение React на инстансе Amazon AWS ec2.
Для этого я поднял там node.js,... Деплой приложения Node.js с использованием strapi в Docker Всем здравствуйте!
Я пытаюсь развернуть приложение на Node.js, которое использует Strapi, в... Деплой телеграм бота на Google Kubernetes Engine через GitLab CI Доброго времни суток. Прошу помощи у форумчан тк. сам не могу разобраться.
Как задеплоить бота на... Деплой на Хероку Здравствуйте!
Возникла проблема с деплоем на Хероку.
Может кто то подскажет как исправить:
... Деплой у базу даних Привет, передо мной стоит задача
данные представляют собой список пользователей (файл... Деплой Телеграм бота на Heroku Хэй гайс. Нид хэлп. :cry:
Запилил изи телеграмм бота на питоне - пытаюсь задеплоить на Heroku. ... Деплой Django + React Всем привет. Не уверен, что в том разделе тему создаю, конечно... В общем, есть REST API на DRF и... Деплой телеграмм-бота на сервере Здравствуйте!
Залил бота на сервер. Все запускается, отлично работает.
Пытаюсь прописать... Деплой Django на Heroku Всем привет, мне нужно закинуть django проект на heroku. Я через закинул все нужное на heroku через... Деплой проекта на heroku через docker Добрый вечер!
Использую бесплатную версию Heroku. Хочу yii2 basic полностью свежий проект... Деплой проекта на WindowsServer Попрошу не ржать и не кидаться какахами )))
Короче, вот возникла необходимость развернуть проект... Деплой телеграм бота на aiogram с webhook на ubuntu Пытаюсь запустить командой sudo python3 bot.py, запускается нормально
Но как только боту...
|