Блог Синявского
  • Разделы
  • Метки
  • Все статьи

Flask веб-приложение с socketio

1

Как я выкладывал Flask веб-приложение с socketio

Я написал программу на Flask, которая сочетала в себе 2 веб-приложения: веб-сервер и SocketIO сервер. Лучше было бы писать их, подключать к брокеру сообщений и деплоить раздельно. Главная идея классическая и заключалась в том, что один пользователь через веб-интерфейс генерировал event, а другие пользователи получали сообщение через SocketIO. Проблема с которой я стокнулся лежала в области деплоя, то есть возникла на завершающей стадии. Я захотел использовать uWSGI сервер для деплоя своего Flask приложения. uWSGI - отличный сервер приложений, который по сути заменяет собой две сущности supervisor и gunicorn.

Как деплоить Flask с uwsgi и nginx

Это работает следующим образом, когда приходит клиентский запрос:

  1. Nginx - frontend отвечает на http запрос

  2. Nginx передает запрос uWSGI

  3. uWSGI управляет Flask приложением

INTERNET <---> nginx <---> uWSGI <---> Flask

Настроим nginx через unix socket

Создадим конфигурация для проксирования запросов на сервер uwsgi

$nano /etc/nginx/sites-available/<your_project>
server {
    listen 80;
    server_name <your_project>;

    location / { 
        try_files $uri @<your_project>; 
    }

    location @<your_project> {
        include uwsgi_params;
        uwsgi_pass unix:/tmp/<your_project>.sock;
    }

    ...

    location /static/ {
        root /<your_project>/;
        gzip on;
        gzip_comp_level 6;
        gzip_proxied any;
        gzip_types text/css application/x-javascript text/javascript image/png image/svg+xml;
    }
}

Создадим сиволическую ссылку

$ ln -s /etc/nginx/sites-available/<your_project> /etc/nginx/sites-enabled/

Протестируем конфигурацию

$ nginx -t

Перезагрузим nginx

service nginx restart

Так как nginx работает под пользователем www-data необходимо чтобы у сокетного файла были права на чтение запись для этого пользователя.

$ chown www-data:www-data /tmp/<your_project>.sock
$ chmod 644 /tmp/<your_project>.sock

Настроим uwsgi для работы Flask приложения с виртуальным окружением

Установим uwsgi

$ apt-get install uwsgi

Тема настройки uwsgi очень попсовая, по этому вопросу есть множество статей. Открывая офф сайт мозг может взорваться от того, что может uwsgi. Он по праву заслуживает имя "трактора с крыльями". В принципе ничего сложного нет, все работает довольно прозрачно, логи пишутся. Есдинственное мне показалось странным, на разных сайтах фигурируют различные аттрибуты запуска uwsgi, которые зачастую противоречат uwsgi --help. Поэтому не стоит заниматься тупой копипастой, лучше посмотреть дефолтный конфиг (или сгенерировать его).

Ссылки на статьи:

  1. http://uwsgi.readthedocs.io/en/latest/WSGIquickstart.html официальня документация

  2. http://killtheyak.com/serve-flask-with-uwsgi-nginx/ хорошая статья для простых случаев

  3. https://the-bosha.ru/2017/01/04/zapusk-flask-prilozheniia-c-uwsgi-virtualenv-i-nginx/ запуск Flask приложения c uWSGI, virtualenv и nginx

Конфигурационный файл uwsgi

$ nano /etc/uwsgi/apps-enabled/captiva.ini

Пояснений по конфигурации будет немного и они все тривиальны.

[uwsgi]
autoload = true
# компилирование python модуля для python из виртуального окружения чуть ниже
plugins = python36
master = true
chdir = <project_path>
# uwsgi не умеет искать пакеты поэтому необходимо указать эти пути
pythonpath = <project_env>/lib/python3.6/site-packages
pythonpath = <project_path>
# это количество процессов по количеству процессоров
processes = 8
# количество воркеров, должно быть 1, при использовании во Flask приложении gevent или eventlet
workers = 1
socket = /tmp/<your_project>.sock
chmod-socket = 666
# модуль файл manage.py c приложением application 
module = manage:app

Для тестирования конфигруционного файла лучше использовать команду

uwsgi -i <your_config>.ini

Теперь поговорим о самом интересном моменте, а именно о граблях на которые наступил я несколько раз. Возможные ошибки если выбран неправильный модуль plugin в настройках uwsgi. Далее опишу свои ошибочные рассужения. Я решил, что если моё приложение использует Python, то мне необходимо установить модуль python для uwsgi.

$ apt-get install uwsgi-plugin-python

Затем я указал модуль и получил ошибку.

plugins = python

Python version: 2.7.12 (default, Nov 19 2016, 06:48:10)  [GCC 5.4.0 20160609]
Set PythonHome to /web/captiva/env
ImportError: No module named site

Я же использую Python3, то мне необходимо установить модуль python3.

$ apt-get install uwsgi-plugin-python3

Получил такую ошибку

plugins = python3

Python version: 3.5.2 (default, Sep 14 2017, 22:51:06)  [GCC 5.4.0 20160609]
Set PythonHome to /web/captiva/env
Fatal Python error: Py_Initialize: Unable to get the locale encoding
ImportError: No module named 'encodings'

Но у меня же python 3.6.3??? В uWSGI необходимо компилировать модуль в соответствии со своим Python в проекте

Компилируем свой модуль для uWSGI

Установим необходимые библиотеки

$ apt-get install python3.6 python3.6-dev gcc
$ apt-get install uwsgi-src

Переходим в папку с виртуальным окружением

$ cd <project_env>/bin
$ uwsgi --build-plugin "/usr/src/uwsgi/plugins/python python36"

Возможные ошибки и их решение

fatal error: uuid/uuid.h
$ apt-get install uuid-dev

fatal error: sys/capability.h
$ apt-get install libcap-dev

fatal error: openssl/conf.h
$ apt-get install libssl-dev

fatal error: pcre.h
$ apt-get install libpcre3-dev

Если все скомпилировалось, то мы увидим в консоли следующий вывод

 uwsgi --build-plugin "/usr/src/uwsgi/plugins/python python36"
*** uWSGI building and linking plugin from /usr/src/uwsgi/plugins/python ***
[x86_64-linux-gnu-gcc -pthread] python36_plugin.so
build time: 4 seconds
*** python36 plugin built and available in python36_plugin.so ***

Скомпилированный файл в той же папке. Его надо переместить в директорию с плагинами /usr/lib/uwsgi/plugins/

$ mv python36_plugin.so /usr/lib/uwsgi/plugins/

Задать права 644

sudo chmod 644 /usr/lib/uwsgi/plugins/python36_plugin.so

Тестовый запуск интерпретатора

$ uwsgi --plugin python36 -s :0

Если все работает мы увидим:

*** Starting uWSGI 2.0.12-debian (64bit) on [Mon Nov 13 15:17:54 2017] ***
compiled with version: 5.4.0 20160609 on 31 August 2017 21:02:04
os: Linux-4.4.0-93-generic #116-Ubuntu SMP Fri Aug 11 21:17:51 UTC 2017
nodename: RADIUS
machine: x86_64
clock source: unix
pcre jit disabled
detected number of CPU cores: 8
current working directory: <yor_project>
detected binary path: /usr/bin/uwsgi-core
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) *** 
*** WARNING: you are running uWSGI without its master process manager ***
your processes number limit is 15645
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uwsgi socket 0 bound to TCP address :44816 (port auto-assigned) fd 3
Python version: 3.6.3 (default, Oct  4 2017, 02:55:45)  [GCC 5.4.0 20160609]
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0x201e230
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 72768 bytes (71 KB) for 1 cores
*** Operational MODE: single process ***
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 10618, cores: 1)

Как подружить uwsgi Flask socketio

Будь у меня веб-приложение все мои страдания на этом бы закончились. Но я же использую Flask-Socketio.

Так как я использую eventlet

socketio.init_app(app, async_mode='eventlet')

Необходимо в manage.py применить патч, чтобы сделать приложение асинхронным

import eventlet
eventlet.monkey_patch()

Если используется gevent

socketio.init_app(app, async_mode='gevent')

Необходимо в manage.py применить патч, чтобы сделать приложение асинхронным

from gevent import monkey
monkey.patch_all()

Об этом можно узнать из официальной документации https://python-socketio.readthedocs.io/en/latest/#gevent-with-uwsgi

Ошибка возникала следующая

RuntimeError: You need to use the eventlet server. See the Deployment section of the documentation for more information.
RuntimeError: You need to use the gevent server. See the Deployment section of the documentation for more information.

При этом у меня работал или веб-сервер или socketio, закономерности я не выявил. При этом при запуске python manage.py runserver приложение на Flask работало без замечаний.

Полезная подсказка на stackowerflow https://stackoverflow.com/questions/45035647/flask-socketio-uwsgi-causes-typeerror-socketio-object-is-not-callable

В итоге я понял следующее, чем, в принципе, и хотел поделиться.

  1. SocketIO полноценный сервер, который запускается на отдельном порту и не может работать через uwsgi. Поскольку WSGI не поддерживает WebSocket. Через uwsgi только longpooling а не websockets.
  2. Единственный вариант работы Flask - SocketIO - uWSGI это использовать gevent. И проксирование через TCP, а не через unix socket. При этом моё веб приложение не работает.
  3. В статьях рекомендуют для целей SocketIO использовать eventlet. Eventlet - это отдельный веб сервер, так что uwsgi не подходит. Что то одно - или eventlet или gevent/uwsgi.

Таким образом если используется uwsgi: единственный вариант, настроить nginx как прокси для отдельного сервера с соответствующими своими заголовками, запускать вебсокет приложение отдельно, а вебсайт отдельно. Общение между ними осуществлять через брокер сообщений RabbitMQ например.

Полезная страница в которой советуют использовать gunicorn для моей ситуации. https://github.com/miguelgrinberg/Flask-SocketIO/issues/429 - Мигель Гринберг отвечает there is only one way - to use long-polling Так же есть статья в которой сравниваются eventlet и gevent, к сожалению не могу найти.

Nginx + Supervisor + Gunicorn + Flask + WebSockets

В итоге я сменил uWSGI на связку supervisor + gunicorn, где websocket работают из коробки. Конфигурация nginx

server {
    listen 80;
    server_name <your project>;

    location / {
            proxy_pass http://127.0.0.1:8887;
            include proxy_params;
            proxy_redirect off;
    }

    location /socket.io {
            proxy_pass http://127.0.0.1:8887/socket.io;
            proxy_redirect off;
            proxy_buffering off;
            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_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
    }

    location /static/ {
        root /web/captiva/project/app/;
        gzip on;
        gzip_comp_level 6;
        gzip_proxied any;
        gzip_types text/css application/x-javascript text/javascript image/png image/svg+xml;
    }
}

Конфигурация supervisor

[program:captiva]
user=www-data
directory=<project_path>
command=<env_project_path>/bin/python <env_project_path>/bin/gunicorn -k eventlet -w 1 manage:app --bind 127.0.0.1:8887 -t 90
redirect_stderr=true
#autorestart=unexpected
stdout_logfile=/var/log/supervisor/<project>.log
stderr_logfile=/var/log/supervisor/<project>_err.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=50
stdout_capture_maxbytes=1MB
stdout_events_enabled=false
loglevel=warn
autostart=true
stopsignal=KILL
environment=LANG="en_US.UTF-8",LC_ALL="en_US.UTF-8",LC_LANG="en_US.UTF-8"
stopasgroup=true
killasgroup=true

А gunicorn как видно из конфига supervisor я устанавливаю в виртуальное окружение.

P.S. Моя статья копирует некоторые из статей в интернете, но я писал о своих личных приключениях на которые напоролся, и о которых и хотел рассказать.



  • ← сюда
  • туда →

comments powered by Disqus

Опубликовано

16.11.2017

Обновление

05.05.2022

Категории

python

Тэги

  • flask 3
  • gunicorn 2
  • nginx 11
  • python 30
  • socketio 1
  • supervisor 2
  • uwsgi 1

Всегда на связи

  • Блог Синявского - Ничего не переносить на завтра, это тоже проблема с прокастинацией?
  • © Алексей Синявский, по лицензии CC BY-SA если не указано иное.
  • С использованием Pelican. Тема: Elegant от Talha Mansoor