← Home

Настройка uwsgi в docker для запуска Python приложения на flask

By Брендель В. М.

Опишу свои страдания на тему.

В итоге я победил этот маразм, который есть результат многих совпадений, приведших к потере 2 дней в поисках решений.

Дисклеймер. Избитая задача деплоя Flask проекта на боевой сервер. Так как я ленивый и сижу на винде, uswgi и gunicorn были отметены, ибо протестировать их в винде нельзя. Я нашел waitress, протестировал его закатал проект в docker.

Но вот незадача в проекте есть return redurect(utl_for(somefunc)). И эта фигня не работала. Сервер возвращал что-то вроде hostname%##hostname/url. Как оказалось это известная проблема ReverseProxy во Flask. Ее решение вроде как представлено в документации Flask.

http://flask.pocoo.org/snippets/35/. Разбираться в этой хурме незахотелось, да и вообще на мой взгляд неправильно кодом проекта чинить баг на стыке веб-сервера (nginx) и сервера приложений (waitress). При том, что debug сервер flask app.run прекрасно работал с проксей.

Вот конфиг nginx рабочий с app.run (с учетом, что flaskapp, nginx, postgress в контейнерах).

flaskapp.conf

server { listen 80; server_name localhost;

location / { 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 Host $http_host; proxy_redirect off;

   proxy_pass http://flaskapp:5090;

} }

dockerfile

FROM python:3.6.5-alpine MAINTAINER Brendel Vadim brendel.vadim@gmail.com

install dependencies

the lapack package is only in the community repository

RUN echo "http://dl-cdn.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories RUN apk --update add --no-cache
lapack-dev
gcc
freetype-dev

Install dependencies

RUN apk add --no-cache --virtual .build-deps
gfortran
musl-dev
g++ RUN ln -s /usr/include/locale.h /usr/include/xlocale.h

COPY . /app WORKDIR /app RUN file="$(ls -1 /app)" && echo $file RUN apk --no-cache add build-base RUN apk --no-cache add postgresql-dev RUN pip3 install -r requirements.txt

removing dependencies

RUN apk del .build-deps

EXPOSE 5090 ENTRYPOINT ["python"] CMD ["upwbp.py"]

docker-compose.yml

version: '3' services: flaskapp: image: me/flaskapp build: context: flaskapp volumes:

networks:

depends_on:

restart: always nginx: image: nginx:alpine ports:

volumes:

depends_on:

networks:

restart: always db: image: postgres:10.5-alpine ports:

volumes:

env_file:

networks:

restart: always networks: web_nw: driver: bridge db_nw: driver: bridge volumes: dbdata:

Окей выкидываем waitress, накатываем uwsgi, все делаем по феньшую.

Нашел вот такой мануал. https://medium.com/@greut/minimal-python-deployment-on-docker-with-uwsgi-bc5aa89b3d35.

FROM alpine:3.7

EXPOSE 3031 VOLUME /usr/src/app/public WORKDIR /usr/src/app

RUN apk add --no-cache
uwsgi-python3
python3

COPY . . RUN rm -rf public/*

RUN pip3 install --no-cache-dir -r requirements.txt

CMD [ "uwsgi", "--socket", "0.0.0.0:3031",
"--uid", "uwsgi",
"--plugins", "python3",
"--protocol", "uwsgi",
"--wsgi", "main:application" ]

Вроде все понятно. Правлю dockerfile, docker-compose. conf. Не взлетает хурма. Так как в чем затык непонятно, для разделения стыка uwsgi, nginx принято решение запустить uwsgi в режиме http. Тогда отключив nginx можно поглядеть, работает корректно ли uwsgi.

Для тестирования я собрал простенький проект-каркас, который грузит немного модулей, создает app=Flask и возвращает Helloworld. И началась боль.

FROM python:3.6.6-alpine MAINTAINER Brendel Vadim brendel.vadim@gmail.com

install dependencies

the lapack package is only in the community repository

RUN echo "http://dl-cdn.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories RUN apk --update add --no-cache
lapack-dev
gcc
freetype-dev

Install dependencies

RUN apk add --no-cache --virtual .build-deps
gfortran
musl-dev
g++ RUN ln -s /usr/include/locale.h /usr/include/xlocale.h

VOLUME /usr/src/app/public WORKDIR /usr/src/app COPY . . RUN rm -rf public/* RUN file="$(ls -1 /usr/src/app)" && echo $file #RUN apk --no-cache add build-base RUN apk add build-base
#postgresql-dev
curl
linux-headers
pcre-dev
python3-dev RUN curl -O http://projects.unbit.it/downloads/uwsgi-lts.tar.gz
tar -xvzf uwsgi-2.0.12.tar.gz
cd u

#RUN apk add --no-cache \

uwsgi-python3 \

uwsgi-http

RUN pip3 install uWSGI RUN pip3 install -r requirements.txt

removing dependencies

RUN apk del .build-deps

EXPOSE 5090 ENTRYPOINT ["tail", "-f", "/dev/null"]

Контейнер с flaskapp постоянно перегружается, если в конфиг uwsgi добавить http. С горем пополам выяснилось, что http-socket вроде как должен работать из коробки, а вот http надо ставить плагин. В дистрибутива debian вроде как есть uwsgi-plugins-all. А вот в индексе пакетов alpine есть либо uwsgi-python3, либо uwsgi-http. Бинарника с двумя одновременно плагинами нет. Более того если поставить uwsgi-python3 и использовать socket вместо http, контейнер все равно валится. Причина — валится uwsgi, он выдает ошибку (еще логирование настроить надо!) — не может импортировать модули Python (flask, flask-restful). Вот это вообще беда. Быстра пересборка всего:

docker-compose up -d --force-recreate --build.

Выяснилось следующее, что при установке apk add uwsgi-python3. Бинарник ставится в /usr/bin/uwsgi, а если использовать pip install uwsgi (как рекомендует официальный сайт) то в /usr/local/bin/uwsgi. Где-то здесь порылась собака. uwsgi не видит основной python системы и поэтому не грузит модули, которые 100% были установлены из requirements.txt во время создания докера. Если зайти в docker

docker exec -ti flask-example_flaskapp_1 sh

И вручную сделать python, import flask все работает. Скорее всего проблема в том что пример dockerfile с uwsgi-python3 начинается от чистого alpine и ставит python3 после установки uwsgi (скорее всего модули тянут одинаковую версию python из alpine index). А у меня dockerfile начинается от python:3.6.6-alpine. Но даже если согласовать версии python, запустить uwsgi с htpp не получится, т.к. нет в alpine index.

Окей, будем добавлять модули в uwsgi вручную.

Я нашел ЕДИНСТВЕННЫЙ внятный материал как ставить uswgi из исходников вот тут. Спасибо огромное человеку. Из документации с сайта uswgi нифига не понятно (хотя это скорее я тупой).

How to build uwsgi from source and run as a service As you might have already noticed, I always deploy python applications using uwsgi and nginx. But I didn't write yet how to install uwsgi. P...

CODEINPYTHON.BLOGSPOT.COM

Делаем как написано в инструкции. копируем скомплированные модули. запускаем ./uwsgi /usr/src/app/uwsgi_init.ini

uwsgi_init.ini

[uwsgi] plugins = python http = 0.0.0.0:5090 plugins-dir = /usr/lib/uwsgi/ ;logto = file:/uwsgi.log module = application:app daemonize = /tmp/uwsgi_daemonize.log

Делаем curl localhost:5090. И вуаля! Все рабоет. Было трудно... Но теперь все-равно надо вернуть socket и прикрутить nginx. И еще написать ручную установку python plugina в докерфайле. Либо же согласовать python:alpine и apk add uwsgi-python3.

Итак docker побежден, все что нужно запущено и работает.

Теперь на будущее.

Надо взять се свои сайты и распихать так:

nginx — 1 контейнер, читает конфигурацию и volume, логи пишет в другой volume.

Каждое приложение — в своем докере со своим uwsgi. PHP/python подключен как модуль (uwsgi лучше собирать из исходников в каждом контейнере). uwsgi в каждым контейнере пишет в unix socket в volume. Эти же volume монтируются в nginx container, откуда он их читает.

Use Unix sockets with Docker WWW.JUJENS.EU

Также каждое приложение монтирует свою статику в volume, эти же volume монтируются в nginx. Nginx настраивается на отдачу статики.

Mysql в отдельном докере. База в volume. Все контейнеры приложений пишут в нее в свою базу.

В основной системе скрипт, который запускается по Crony, делает дамп всего пакует и кладет резервную копию в YandexDisk с учетом ротации. Как-то так.