Подготовка сервера для публикации web-app на Python

  • Tutorial
Доброго времени суток, хабровчане!

Появилось свободное время, и возникло желание сделать небольшую веб-приложение. Идея есть (получать данные с метеодатчика, хранить в БД и потом делать что-то интересное), свободный сервер на centos тоже. Туториалов по настройке вроде бы тоже… Но на момент написания ни одного полностью рабочего не нашлось. Если вы тоже хотите развернуть приложение на сервере CentOS 7.4 используя связку python3.*, uwsgi и nginx, добро пожаловать под кат.

Итак, что уже должно быть и что не будет освещаться в этой статье:

  1. Физический или виртуальный сервер с ОС CentOS 7.4 (для других ОС и версий работоспособность не гарантируется).
  2. Доступ к серверу с правами суперпользователя *если сервер виртуальный, то предполагается, что есть возможность и умение подключиться к нему по SSH).

Первым делом после подключения root-пользователем следует создать нового пользователя с правами администратора (это не обязательно, но как минимум хороший тон):

adduser developer
passwd developer

Здесь потребуется ввести и подтвердить пароль пользователя

usermod -aG wheel developer

Добавляем пользователя в группу локальных администраторов

su - developer

Переключаемся на свежесозданного пользователя.

Теперь готовим окружение:

sudo yum install -y epel-release
sudo yum install -y python3 python3-pip python3-devel nginx gcc
sudo yum update -y python36 python2
sudo yum groupinstall -y "Development Tools" "Development Libraries"
sudo yum install -y python3-devel python36-virtualenv

Хотелось бы обратить внимание на предпоследнюю строчку — «Development Tools» и «Development Libraries» необходимы для корректного запуска виртуальной среды. Флаг -у используем чтобы команда отрабатывала без запроса подтверждения.

Теперь можно приступать к созданию рабочей директории:

sudo mkdir /opt/myproject && cd /opt/myproject
sudo chown -R developer /opt/
python3 -m venv myprojectenv
source myprojectenv/bin/activate

pip3 install --upgrade pip
pip3 install uwsgi flask

Теперь подготовим точку входа в веб-приложение:

vi /opt/myproject/wsgi.py (здесь и далее я буду использовать редактор vi, однако это дело вкуса)

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ["<h1 style='color:blue'>Testing Success!</h1>"]

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

uwsgi --socket 0.0.0.0:8080 --protocol=http -w wsgi

При физическом ljcnegt или rdp-подключении результат можно проверить в браузере по адресу 0.0.0.0:8080. При SSH-подключении откроем новую сессию и протестируем с помощью curl:

curl -v 0.0.0.0:8080

Флаг -v позволит увидеть полный вывод.

После завершения тестирования стоит завершить работу сервиса нажав CTRL+C (иначе придётся искать процесс и убивать его с помощью kill. Тоже выход, но определённо менее удобный).

Теперь можно сделать основное веб-приложение:

vi /opt/myproject/myproject.py

from flask import Flask
application = Flask(__name__)

@application.route("/")
def hello():
   return "<h1 style='color:blue'>Hello There!</h1>"

if __name__ == "__main__":
   application.run(host='0.0.0.0')

Процесс тестирования довольно похож на предыдущий — запускаем приложение с помощью команды python3 /opt/myproject/myproject.py и проверяем. результат должен быть одинаков.

Теперь отредактируем точку входа так, чтобы при её вызове запускалось наше приложение:

vi /opt/myproject/wsgi.py

from myproject import application

if __name__ == "__main__":
   application.run()

Теперь самое сложное и интересное. Пора делать из этого приложения сервис, который будет работать автоматически и в фоновом режиме.

Для начала подготовим ini-файл для uwsgi:

vi /opt/myproject/myproject.ini

[uwsgi]
wsgi-file = myproject/wsgi.py

master = true
processes = 2

uid = developer
socket = /tmp/uwsgi/myproject.sock
chown-socket = nginx:nginx
chmod-socket = 666
vacuum = true

die-on-term = true

Здесь хочется обратить внимание на следующие строчки:

  1. socket = /tmp/uwsgi/myproject.sock — для ускорения работы сервиса стоит использовать unix-socket, а для того, чтобы к нему смог подключиться nginx — создать его во временной папке /tmp/uwsgi
  2. chown-socket = nginx:nginx передаст права на использование сокета пользователю nginx
  3. chmod-socket = 666 — служит для тех же целей, что и предыдущая. В разных руководствах и советах фигурируют разные значения параметра — 664, 665, 777 — но опытным путём установлено, что 666 — минимально рабочий.

Теперь можно приступать непосредственно к созданию сервиса:

sudo vi /etc/systemd/system/myproject.service

[Unit]
Description=uWSGI instance to serve myproject app
After=network.target

[Service]
ExecStartPre=-/usr/bin/bash -c 'mkdir -p /tmp/uwsgi chown nginx:nginx /tmp/uwsgi'
ExecStart=/usr/bin/bash -c 'cd /opt/myproject; source myprojectenv/bin/activate; uwsgi --ini myproject.ini'
ExecStartPost=/usr/bin/bash -c 'setenforce 0'
PrivateTmp=false
[Install]
WantedBy=multi-user.target

Все интересные строчки находятся в блоке Service:

  1. ExecStartPre — это команда, которую менеджер системы выполнит перед запуском сервиса.

    -/usr/bin/bash -c объявляет запуск последующей последовательности команд, а 'mkdir -p /tmp/uwsgi chown nginx:nginx /tmp/uwsgi' создаст временную папку для сокета и передаст права пользователю nginx.
  2. ExecStart — команда, непосредственно запускающая сервис, и в ней мы последовательно перйдём в рабочую директорию, активируем виртуальную среду и запустим uwsgi-сервер из ini файла.
  3. ExecStartPost — команда, которая выполняется после запуска сервиса. В нашем случае она требуется для корректной передачи запросов от сервера nginx к uwsgi-серверу.
  4. PrivateTmp=false — это параметр, который делает созданную временную папку видимой для остальных процессов.

Теперь запустим сервис:

systemctl start myproject
systemctl status myproject
systemctl enable myproject

Последняя команда сделает его автозапускающимся после перезагрузки сервера.

И последний рывок — настроим сервер nginx и сделаем наше веб-приложение доступным для внешней сети. Лог его работы всегда можно посмотреть с помощью команды

journalctl -u  myproject

sudo vi /usr/lib/systemd/system/nginx.service

Найдём блок [Service] и в конце добавим PrivateTmp=false.

После этого перезагрузим демонов командой systemctl daemon-reload

Теперь приступим непосредственно к конфигурации сервера:

vi /etc/nginx/nginx.conf

http {
    . . .

    include /etc/nginx/conf.d/*.conf;

    server {
        listen 80 default_server;

        . . .

Находим блок http и добавляем новый сервер:

server {
    listen 80;
    server_name server_domain_or_IP;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/tmp/uwsgi/myproject.sock;
    }
}

Теперь осталось включить сервер и отредактировать правила на фаерволе:

systemctl start nginx
systemctl enable nginx

firewall-cmd --zone=public --permanent --add-service=http
firewall-cmd --zone=public --permanent --add-service=https

firewall-cmd --reload

Теперь при обращении к серверу по его доменному имени или IP-адресу мы будем получать ответ от нашего приложения на Flask.

Надеюсь, данный материал окажется полезным.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 3

    +5

    Всё плохо.


    опытным путём установлено, что 666 — минимально рабочий.

    А нужно не опыты ставить, а понимать, как права в юниксах устроены. У меня всё и с 660 отлично работает. И вообще если разобраться в правах, то запускать uwsgi от имени root становится без надобности


    chown-socket = nginx:nginx

    Раз прописаны права 666, то это делать бессмысленно


    uid = developer

    Запускать сайт под пользователем с правами администратора — хоть и не root, но тоже так себе идея


    ExecStartPost — команда, которая выполняется после запуска сервиса. В нашем случае она требуется для корректной передачи запросов от сервера nginx к uwsgi-серверу.

    Опять же, если разобраться в правах, то в подобных костылях нет необходимости


    socket = /tmp/uwsgi/myproject.sock

    Хранить сокеты в /tmp — плохой тон, для сокетов изобрели /run


    PrivateTmp=false

    Как следствие предыдущего, это тоже плохой тон


    mkdir -p /tmp/uwsgi

    Для этого изобрели systemd-tmpfiles


    cd /opt/myproject

    А для этого изобрели опцию WorkingDirectory


    «Development Tools» и «Development Libraries» необходимы для корректного запуска виртуальной среды.

    Почему?


    перезагрузим демонов командой systemctl daemon-reload

    Чепуха написана

      0

      Автор, ничего личного, но в вашей статье больше вреда чем пользы. Абсолютное непонимание как работает Linux и systemd.

        0
        Попробую отметить почему так много недовольных статьей ;)

        ExecStartPost=/usr/bin/bash -c 'setenforce 0'

        1. Не пишите руководство, в котором одним из шагов — обязательный выстрел в ногу.
        … source myprojectenv/bin/activate; uwsgi --ini myproject.ini'

        2. Вместо WSGI есть две альтернативы которые умеют удобно работать с VirtualEnv — Nginx Unit и Gunicorn. Если очень хочется uwsgi — ключ "-H $envpath" позволяет работать с venv.
        /usr/bin/bash -c 'cd /opt/myproject...

        3. У задач systemd-unit можно указать WorkingDirectory (и ограниченного пользователя отличного от root)

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое