Мега-Учебник Flask, Часть 18: Развертывание на Heroku Cloud

в 21:02, , рубрики: deployment, flask, heroku, python, web-разработка, Веб-разработка

Это восемнадцатая статья в серии, где я описываю свой опыт написания веб-приложения на Python с использованием микрофреймворка Flask.

Цель данного руководства — разработать довольно функциональное приложение-микроблог, которое я за полным отсутствием оригинальности решил назвать microblog.


В предыдущей статье мы рассмотрели вариант традиционного хостинга. Мы видели два актуальных примера хостинга на Linux серверах, сначала на обычном сервере под управлением CentOS, а затем на миникомпьютере Raspberry Pi. Те из читателей, кто не занимался администрированием Linux систем ранее, возможно, решили что это требует слишком много усилий и могло бы быть реализовано как-то проще.

Сегодня мы посмотрим, является ли развертывание «в облако» решением проблемы излишней сложности процесса.

Но что это значит «развернуть в облако»?

Провайдеры облачного хостинга предлагают платформу на которой может быть запущено наше приложение. Всё что требуется от разработчика — предоставить приложение, а всё остальное включая серверное железо, операционную систему, интерпретатор языка и базу данных берёт на себя сервис.

Звучит слишком хорошо, чтобы быть правдой, не так ли?

Мы рассмотрим развертывание приложения на платформе Heroku, одной из самых популярных платформ облачного хостинга. Я выбрал Heroku не только по причине её популярности, но еще и потому, что она предоставляет бесплатный уровень сервиса, так что мы развернем наше приложение не потратив при этом ни цента. Если вы хотите больше узнать об этом типе услуг, и что предлагают другие провайдеры, вы можете ознакомиться со страницей Википедии, посвященной PaaS.

Хостинг на Heroku

Heroku была одной из первых платформ, предоставляющих услуги PaaS. В начале, она предлагала услуги размещения только Ruby приложений, но позднее была включена поддержка многих других языков таких как Java, Node.js и нашего фаворита, Python.

Вообще-то для развертывания приложения на Heroku требуется лишь загрузить приложение при помощи git (вы увидите как это работает уже совсем скоро). Heroku ищет файл Procfile в корневой папке приложения для получения инструкций как приложение должно выполняться. Для Python проектов Heroku также ожидает увидеть файл requirements.txt, содержащий список необходимых сторонних пакетов.

После загрузки приложения, можно считать что дело сделано. Heroku применит свою магию и приложение будет доступно онлайн через считанные секунды. Сумма счета в конце периода, напрямую зависит от потребленных вычислительных мощностей вашим приложением, следовательно чем больше будет пользователей у вашего приложения, тем больше вам придется заплатить.

Готовы испытать Heroku? Давайте начнем!

Создание аккаунта Heroku

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

После авторизации вы попадете в панель управления, откуда вы сможете управлять всеми своими приложениями. Мы не будем интенсивно использовать панель управления, но она предоставляет неплохой обзор вашего аккаунта.

Установка клиента Heroku

Несмотря на то, что некоторые задачи можно выполнять прямо из веб интерфейса, существуют задачи, решить которые можно только из терминала, поэтому мы всё будем делать в консоли.

Heroku предлагает утилиту «Heroku клиент», которой мы будем пользоваться для создания и управления нашим приложением. Эта утилита может быть запущена под управлением Windows, Mac OS X и Linux. Если для вашей платформы доступен набор инструментов Heroku, то это самый простой способ установить клиент Heroku.

Первое, что мы сделаем при помощи клиента — войдем в наш аккаунт:

$ heroku login

Heroku запросит у вас email и пароль от вашего аккаунта. При первой авторизации, клиент отправит ваш ssh ключ на сервера Heroku.

Последующие команды можно будет выполнять без авторизации.

Настройка Git

git представляет собой основу разворачивания приложений на Heroku, поэтому он также должен быть установлен. Если вы установили набор инструментов Heroku, то git у вас уже установлен.

Для разворачивания приложения на Heroku, оно должно присутствовать в локальном репозитарии, поэтому выполните следующие команды в консоли:

$ git clone git://github.com/miguelgrinberg/microblog.git
$ cd microblog

Создание приложения Heroku

Для создания нового приложения Heroku, достаточно вызвать команду create из корневой папки приложения:

$ heroku create flask-microblog
Creating flask-microblog... done, stack is cedar
http://flask-microblog.herokuapp.com/ | git@heroku.com:flask-microblog.git

В дополнение к установкам URL, эта команда добавляет нашему репозитарию удаленный репозитарий (git remote), который мы вскоре используем для загрузки кода приложения в облако.

Естественно, имя flask-microblog теперь занято мной, поэтому придумайте какое-нибудь другое имя своему приложению.

Исключаем локальное хранение файлов

Некоторые функции нашего приложения сохраняют информацию в виде файлов на диске.

И тут мы сталкиваемся с непростой задачей. Приложениям, запущенным на Heroku, не доступно постоянное хранение файлов на диске, т. к. Heroku использует платформу виртуализации, которая не запоминает данные в виде файлов, файловая система очищается от всех файлов, кроме непосредственно файлов приложения, при каждом запуске инстанса. Строго говоря, приложение может хранить временные файлы на диске, но должно быть в состоянии восстановить эти файлы в случае их исчезновения. Кроме того, если запущено два инстанса, каждый из них использует свою собственную виртуальную файловую систему и нет возможности разделить файлы между ними.

Это действительно неприятные новости для нас. Для начала, это означает, что мы не сможем использовать sqlite в качестве базы данных.

Наша база полнотекстового поиска Whoosh также перестанет работать, т. к. она хранит свои данные в виде файлов.

Третий проблемный момент — наша система логирования. Мы сохраняли наш лог в папке /tmp и теперь, при работе на Heroku, это тоже перестанет работать.

Итак, мы обозначили 3 основные проблемы для которых нам нужно искать решения.

Первую проблему мы решим миграцией на базу данных, предлагаемую Heroku, которая основана на PostgreSQL.

Для функционирования полнотекстового поиска у нас нет готовой доступной альтернативы. Нам придется реализовать полнотекстовой поиск, используя функциональность PostgreSQL, но это потребует внесения изменений в наше приложение. Безусловно жаль, но решение этой проблемы сейчас увело бы нас далеко в сторону от темы статьи, поэтому для размещения на Heroku, мы просто отключим полнотекстовой поиск.

И наконец, т. к. мы не можем писать наши логи, мы добавим наши логи к системе логирования, используемой Heroku, которая, кстати, весьма проста в использовании, т. к. отправляет в лог всё, что выводится в stdout.

Создание базы данных Heroku

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

$ heroku addons:add heroku-postgresql:dev
Adding heroku-postgresql:dev on flask-microblog... done, v3 (free)
Attached as HEROKU_POSTGRESQL_ORANGE_URL
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pgbackups:restore.
Use `heroku addons:docs heroku-postgresql:dev` to view documentation.

Заметьте, что мы используем базу данных разработки, т. к. это единственная бесплатная опция. Для боевого сервера понадобится выбрать другую опцию базы данных.

И как наше приложение узнает параметры подключения к базе данных? Heroku помещает URI базы данных в переменную окружения $DATABASE_URL. Если вы помните, мы внесли изменения в наш конфигурационный файл в прошлой статье, т.ч. значение этой переменной будет использоваться для подключения к базе данных, как и требуется.

Запрещение полнотекстового поиска

Для отключения полнотекстового поиска, наше приложение должно уметь определять запущено ли оно на Heroku или нет. Для этого мы создадим пользовательскую переменную окружения, опять же при помощи клиента Heroku:

heroku config:set HEROKU=1

Теперь переменная окружения HEROKU, будет установлена в 1, когда наше приложение запущено на виртуальной площадке Heroku.

Теперь отключить полнотекстовой поиск довольно просто. Для начала, добавим переменную в конфигурационный файл (файл config.py):

# Whoosh does not work on Heroku
WHOOSH_ENABLED = os.environ.get('HEROKU') is None

Затем, отменим создание базы данных полнотекстового поиска (файл app/models.py):

from config import WHOOSH_ENABLED

if WHOOSH_ENABLED:
    import flask.ext.whooshalchemy as whooshalchemy
    whooshalchemy.whoosh_index(app, Post)

Также добавим информацию о полнотекстовом поиске в g в нашем обработчике before_request, чтобы наши шаблоны видели её (файл app/views.py):

from config import WHOOSH_ENABLED

@app.before_request
def before_request():
    g.user = current_user
    if g.user.is_authenticated():
        g.user.last_seen = datetime.utcnow()
        db.session.add(g.user)
        db.session.commit()
        g.search_form = SearchForm()
    g.locale = get_locale()
    g.search_enabled = WHOOSH_ENABLED

И, наконец, уберем поле поиска в базовом шаблоне (файл app/templates/base.html):

        {% if g.user.is_authenticated() and g.search_enabled %}
        <form class="navbar-search pull-right" action="{{url_for('search')}}" method="post" name="search">{{g.search_form.hidden_tag()}}{{g.search_form.search(size=20,placeholder=_('Search'),class="search-query")}}</form>
        {% endif %}

Исправляем логирование

Под управлением Heroku, всё что выводится в поток stdout, тут же попадает в лог приложения Heroku. Но логи, которые пишутся в файлы на диске, будут недоступны. Так что на этой платформе мы должны отключить логирование в файлы и использовать вместо этого логгер, пишуший ошибки прямо в stdout (файл app/__init__.py):

if not app.debug and os.environ.get('HEROKU') is None:
    import logging
    from logging.handlers import RotatingFileHandler
    file_handler = RotatingFileHandler('tmp/microblog.log', 'a', 1 * 1024 * 1024, 10)
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
    app.logger.addHandler(file_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.info('microblog startup')

if os.environ.get('HEROKU') is not None:
    import logging
    stream_handler = logging.StreamHandler()
    app.logger.addHandler(stream_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.info('microblog startup')

Веб сервер

Heroku не предоставляет свой веб сервер. Вместо этого, ожидает что приложение запустит свой собственный сервер на порту, номер которого получит из переменной окружения $PORT.

Мы знаем, что сервер разработки Flask не подходит для работы в продакшне, т. к. он однопроцессный и однопоточный, поэтому нам необходимо решение получше. Руководство Heroku для приложений на Python рекомендует gunicorn, его мы и применим.

В наше локальное окружение, gunicorn устанавливается как обычный python модуль:

$ flask/bin/pip install gunicorn

Для его запуска, мы должны передать один аргумент с именем Python модуля, определяющего приложение и сам объект приложения, разделенные двоеточием.

Давайте создадим отдельный Python модуль для Heroku (файл runp-heroku.py):

#!flask/bin/python
from app import app

Теперь, например, если мы хотим запустить сервер gunicorn локально, при помощи этого модуля, мы должны выполнить следующую команду:

$ flask/bin/gunicorn runp-heroku:app
2013-04-24 08:42:34 [31296] [INFO] Starting gunicorn 0.17.2
2013-04-24 08:42:34 [31296] [INFO] Listening at: http://127.0.0.1:8000 (31296)
2013-04-24 08:42:34 [31296] [INFO] Using worker: sync
2013-04-24 08:42:34 [31301] [INFO] Booting worker with pid: 31301

Файл requirements.txt

Уже совсем скоро мы загрузим наше приложение на Heroku, но прежде мы должны сообщить серверу, какие модули необходимы нашему приложению для запуска. На нашем локальном ПК, мы управляли зависимостями при помощи виртуального окружения, устанавливая в него модули при помощи pip.

Heroku поступает похожим образом. Если файл requirements.txt обнаруживается в корневой папке приложения, то Heroku устанавливает все модули перечисленные в нем при помощи pip.

Для создания файла requirements.txt мы должны использовать опцию freeze при вызове pip:

$ flask/bin/pip freeze > requirements.txt

В список необходимо добавить сервер gunicorn, а также драйвер psycopg2, который необходим SQLAlchemy для подключения к базе данных PostgreSQL. Окончательный вид файла requirements.txt будет таким:

Babel==0.9.6
Flask==0.9
Flask-Babel==0.8
Flask-Login==0.1.3
Flask-Mail==0.8.2
Flask-OpenID==1.1.1
Flask-SQLAlchemy==0.16
Flask-WTF==0.8.3
git+git://github.com/miguelgrinberg/Flask-WhooshAlchemy
Jinja2==2.6
MySQL-python==1.2.4
psycopg2==2.5
SQLAlchemy==0.7.9
Tempita==0.5.1
WTForms==1.0.3
Werkzeug==0.8.3
Whoosh==2.4.1
blinker==1.2
coverage==3.6
decorator==3.4.0
flup==1.0.3.dev-20110405
guess-language==0.2
gunicorn==0.17.2
python-openid==2.2.5
pytz==2013b
speaklater==1.3
sqlalchemy-migrate==0.7.2

Некоторые из этих пакетов будут невостребованны версией нашего приложения, предназначенной для Heroku, но нет ничего страшного в наличии неиспользуемых пакетов в системе. И мне кажется, что всё же лучше иметь полный список требуемых пакетов.

Procfile

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

Этот файл весьма прост, он просто определяет имена процессов и команды ассоциированные с ними (файл Procfile):

web: gunicorn runp-heroku:app
init: python db_create.py && pybabel compile -d app/translations
upgrade: python db_upgrade.py && pybabel compile -d app/translations

Метка web ассоциируется с веб сервером. Heroku требуется это задание для запуска нашего приложения.

Другие две задачи, именуемые init и upgrade это пользовательские задачи, которые мы будем использовать для работы с нашим приложением. Задание init инициализирует наше приложение созданием базы данных и компилированием языковых файлов. Задание upgrade подобно init, но вместо создания базы данных оно обновляет базу до последней миграции.

Развертывание приложения

Теперь мы приступаем к самой интересной части, в которой мы разместим приложение в нашем Heroku аккаунте. Это довольно просто, мы просто используем git для отправки приложения:

$ git push heroku master
Counting objects: 307, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (168/168), done.
Writing objects: 100% (307/307), 165.57 KiB, done.
Total 307 (delta 142), reused 272 (delta 122)

-----> Python app detected
-----> No runtime.txt provided; assuming python-2.7.4.
-----> Preparing Python runtime (python-2.7.4)
-----> Installing Distribute (0.6.36)
-----> Installing Pip (1.3.1)
-----> Installing dependencies using Pip (1.3.1)
...
-----> Discovering process types
       Procfile declares types -> init, upgrade, web

-----> Compiled slug size: 29.6MB
-----> Launching... done, v6
       http://flask-microblog.herokuapp.com deployed to Heroku

To git@heroku.com:flask-microblog.git
 * [new branch]      master -> master

Метка heroku, которую мы используем в нашей команде git push, была автоматически зарегистрирована в нашем репозитарии git когда мы создавали наше приложение при помощи heroku create. Чтобы посмотреть, как настроен этот удаленный репозитарий, вы можете запустить git remote -v в папке приложения.

При первоначальной загрузке приложения на Heroku, нам нужно инициализировать базу данных и файлы перевода, а для этого мы должны выполнить задание init, которое мы включили в наш Procfile:

$ heroku run init
Running `init` attached to terminal... up, run.7671
/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/engine/url.py:105: SADeprecationWarning: The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'. The new URL format is postgresql[+driver]://<user>:<pass>@<host>/<dbname>
  module = __import__('sqlalchemy.dialects.%s' % (dialect, )).dialects
compiling catalog 'app/translations/es/LC_MESSAGES/messages.po' to 'app/translations/es/LC_MESSAGES/messages.mo'

Предупреждение принадлежит SQLAlchemy, т. к. ей не нравятся URI начинающиеся с postgres:// вместо postgresql://. Этот URI формирует Heroku через значение переменной окружения $DATABASE_URL, так что изменить это не в наших силах. Остается надеяться, что этот формат URI проработает еще долго.

Верите вы или нет, но наше приложение уже доступно онлайн. В моем случае, приложение доступно по адресу flask-microblog.herokuapp.com. Вы вполне можете стать моим фоловером со страницы моего профиля. Я не знаю точно, как долго приложение будет доступно по этому адресу, но вам ничто не мешает проверить доступно оно или нет!

Обновление приложения

Рано или поздно, придет время обновить наше приложение. Это будет происходить подобно начальному развертыванию. Первым делом приложение будет залито на сервер при помощи git:

$ git push heroku master

Затем, выполнен скрипт обновления:

$ heroku run upgrade

Логирование

Если с приложением приключится что-то нештатное, может полезно изучить логи. Помните, что для Heroku версии приложения, мы пишем все логи в stdout, а Heroku собирает их в собственный лог.

Чтобы просмотреть логи, используется клиент Heroku:

$ heroku logs

Приведенная команда выведет все логи, включая логи Heroku. Чтобы просмотреть логи только вашего приложения, запустите команду:

$ heroku logs --source app

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

Стоит ли?

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

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

Вопрос стоимости является спорным моментом. Стоимость услуг облачного хостинга обычно дороже выделенных серверов, т. к. вы платите не только за сервер, но и за услуги администрирования. Типичный тарифный план Heroku, включающий два инстанса и самую дешевую продакшн базу данных, будет стоить $85 (это на момент написания этих строк. Примерно, год назад — прим. пер.). С другой стороны, если вы хорошо поищете, то вполне сможете подобрать себе вполне приличный VPS за ~$40 в год.

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

Конец?

Обновленное приложение доступно, как всегда, на github. Или же вы можете скачать его в виде zip архива по ссылке:

Скачать microblog 0.18.

С разворачиванием нашего приложения всеми возможными способами, кажется, что наш экскурс подходит к концу.

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

Впрочем, я не ставлю точку, и не отрицаю вероятности появления статей о microblog-е. Если, и когда, интересная тема придет на ум, я буду писать еще, но я ожидаю что частота обновлений теперь несколько спадет. Время от времени, я могу делать какие-то мелкие исправления в приложении, которые не заслуживают отдельной статьи в блоге, так вот эти изменения вы можете отслеживать на GitHub.

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

Спасибо вам, еще раз, за то, что были лояльным читателем.

Miguel

Автор: p-y-t-h-o-n

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js