Это восемнадцатая часть серии мега-учебника Flask, в которой я собираюсь развернуть Microblog на облачной платформе Heroku.
Оглавление
Глава 18: Развертывание на Heroku (Эта статья)
В предыдущей статье я показал вам "традиционный" способ размещения приложения написанного на Python и привел реальный пример развертывания на сервере под управлением Linux. Если вы не привыкли управлять системой Linux, вы, вероятно, подумали, что количество усилий, которые необходимо приложить для выполнения задачи, велико, и что наверняка должен быть более простой способ.
В этой главе я собираюсь показать вам совершенно другой подход, при котором вы полагаетесь на стороннего облачного хостинг-провайдера для выполнения большинства задач администрирования, что освобождает вас от необходимости тратить больше времени на работу с вашим приложением.
Многие поставщики облачного хостинга предлагают управляемую платформу, на которой могут запускаться приложения. Все, что вам нужно предоставить для развертывания вашего приложения на этих платформах, - это само приложение, поскольку аппаратное обеспечение, операционная система, интерпретаторы языка сценариев, база данных и т.д. Управляются службой. Этот тип сервиса называется платформа как сервис, или PaaS.
Звучит слишком хорошо, чтобы быть правдой, не так ли?
Я рассмотрю развертывание Microblog в Heroku, популярном облачном хостинге, который очень удобен для приложений на Python. К сожалению, Heroku отменила свой популярный бесплатный уровень, поэтому выполнение этого упражнения по развертыванию обойдется вам в небольшую сумму денег. Если вы решите попробовать это, обязательно удалите свой проект, когда закончите экспериментировать.
Ссылки на GitHub для этой главы: Browse, Zip, Diff.
Хостинг на Heroku
Heroku был одним из первых поставщиков "платформы как услуги". Начинался как вариант хостинга приложений на Ruby, но затем расширился до поддержки многих других языков, таких как Java, Node.js и, конечно, Python.
Развертывание веб-приложения на Heroku выполняется с помощью инструмента контроля версий git
, поэтому ваше приложение должно быть размещено в репозитории git. Heroku ищет файл с именем Procfile в корневом каталоге приложения, чтобы получить инструкции по запуску приложения. Для проектов на Python Heroku также ожидает файл requirements.txt со списком всех зависимостей модуля, которые необходимо установить. После того, как приложение с помощью push-операции git загружено на серверы Heroku, вы, по сути, закончили, и вам просто нужно подождать несколько секунд, пока приложение подключится к сети. Это действительно так просто.
Различные уровни обслуживания, предлагаемые Heroku, позволяют вам выбирать, сколько вычислительной мощности и времени вы получаете для своего приложения, поэтому по мере роста вашей пользовательской базы вам может потребоваться покупать больше вычислительных единиц, которые Heroku называет "dynos".
Готовы попробовать Heroku? Давайте начнем!
Создание учетной записи Heroku
Прежде чем вы сможете выполнить развертывание на Heroku, вам необходимо создать у них учетную запись. Поэтому посетите heroku.com и создайте учетную запись. Как только у вас появится учетная запись и вы войдете в Heroku, у вас будет доступ к панели мониторинга, где перечислены все ваши приложения.
Установка командной строки Heroku
Heroku предоставляет инструмент командной строки для взаимодействия со своей службой под названием Heroku CLI, доступный для Windows, Mac OS X и Linux. Документация включает инструкции по установке для всех поддерживаемых платформ. Продолжайте и установите его в своей системе, если вы планируете развернуть приложение для тестирования службы.
Первое, что вы должны сделать после установки CLI, это войти в свою учетную запись Heroku:
$ heroku login
Интерфейс командной строки Heroku попросит вас ввести адрес электронной почты и пароль вашей учетной записи. Ваш статус аутентификации будет сохранен в последующих командах.
Настройка Git
Инструмент git
является основным для развертывания приложений на Heroku, поэтому вы должны установить его в своей системе, если у вас его еще нет. Если у вас нет доступного пакета для вашей операционной системы, вы можете посетить веб-сайт git, чтобы загрузить установщик.
Есть много причин, по которым использование git
для ваших проектов имеет смысл. Если вы планируете развертывание на Heroku, у вас есть еще одно, потому что для развертывания на Heroku ваше приложение должно находиться в репозитории git
. Если вы собираетесь выполнить тестовое развертывание для Microblog, вы можете клонировать мою версию этого приложения с GitHub:
$ git clone https://github.com/miguelgrinberg/microblog
$ cd microblog
$ git checkout v0.18
Команда git checkout
выбирает в истории изменения приложения конкретную фиксацию, соответствующую этой главе.
Если вы предпочитаете работать со своим собственным кодом, а не с моим, вы можете преобразовать свой собственный проект в репозиторий git
, запустив git init .
в каталоге верхнего уровня (обратите внимание на точку после init
, которая сообщает git, что вы хотите создать репозиторий в текущем каталоге). После создания репозитория git используйте команду git add
, чтобы добавить все ваши исходные файлы и git commit
зафиксировать их в репозитории.
Создание приложения Heroku
Чтобы зарегистрировать новое приложение в Heroku, используете команду apps:create
из корневого каталога приложения, передавая имя приложения в качестве единственного аргумента:
$ heroku apps:create flask-microblog
Creating flask-microblog... done
http://flask-microblog.herokuapp.com/ | https://git.heroku.com/flask-microblog.git
Heroku требует, чтобы приложения имели уникальные име. Имяflask-microblog
, которое я использовал выше, будет недоступно для вас, потому что я использую его, поэтому вам нужно будет выбрать другое имя для вашего развертывания.
Выходные данные этой команды будут включать URL-адрес, который Heroku присвоил приложению, а также его репозиторий git на серверах Heroku. В вашем локальном репозитории git будет настроен дополнительный удаленный доступ, который для этого репозитория называется heroku
. Вы можете проверить, существует ли он с помощью команды git remote
:
$ git remote -v
heroku https://git.heroku.com/flask-microblog.git (fetch)
heroku https://git.heroku.com/flask-microblog.git (push)
В зависимости от того, как вы создали свой репозиторий git, результат приведенной выше команды может также включать другой удаленный вызов origin
, который не используется Heroku.
Эфемерная файловая система
Платформа Heroku отличается от других платформ развертывания тем, что она имеет эфемерную файловую систему, которая работает на виртуализированной платформе. Что это значит? Это означает, что в любой момент Heroku может вернуть виртуальный сервер, на котором работает ваш сервер, в чистое состояние. Вы не можете предполагать, что какие-либо данные, которые вы сохраняете в файловой системе, сохранятся, и фактически Heroku очень часто перерабатывает серверы.
Работа в этих условиях создает некоторые проблемы для моего приложения, которое использует несколько файлов:
Ядро базы данных SQLite по умолчанию записывает данные в файл на диске
Журналы для приложения также записываются в файловую систему
Скомпилированные репозитории языковых переводов также записываются в локальные файлы
В следующих разделах будут рассмотрены эти три области.
Работа с базой данных Heroku Postgres
Чтобы решить первую проблему, я собираюсь переключиться на другой движок базы данных. В главе 17 вы видели, как я использовал базу данных MySQL для повышения надежности развертывания Ubuntu. У Heroku есть собственная база данных, основанная на базе базы данных Postgres, поэтому я собираюсь переключиться на нее, чтобы избежать использования SQLite на основе файлов.
Базы данных для приложений Heroku готовятся с помощью того же интерфейса командной строки Heroku. В этом случае я собираюсь создать базу данных на бесплатном уровне:
$ heroku addons:create heroku-postgresql:mini
Creating heroku-postgresql:mini on flask-microblog... $5/month
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-parallel-56076 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
URL-адрес для вновь созданной базы данных хранится в переменной окружения DATABASE_URL
, которая будет доступна при запуске приложения на платформе Heroku. Это очень удобно, потому что приложение уже ищет URL базы данных в этой переменной.
Чтобы убедиться, что переменная DATABASE_URL
настроена в вашем приложении Heroku, вы можете использовать следующую команду:
$ heroku config
DATABASE_URL: postgres://...
Досадная проблема последних версий SQLAlchemy заключается в том, что они ожидают, что URL-адреса базы данных Postgres будут начинаться с postgresql://
, а не с postgres://
, который использует Heroku. Чтобы гарантировать, что приложение может подключаться к базе данных, необходимо обновить URL-адрес до формата, требуемого SQLAlchemy. Это можно сделать с помощью операции замены строки в классе Config
:
config.py: Исправляем URL-адреса базы данных Postgres, чтобы они были совместимы с SQLAlchemy.
class Config:
# ...
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '').replace(
'postgres://', 'postgresql://') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
# ...
Эта операция замены строки безопасна в использовании, даже если для переменной DATABASE_URL
установлена другая база данных, и в этом случае она на нее не повлияет.
Логирование в стандартный вывод
Heroku ожидает, что приложения будут вести логи напрямую в stdout
. Все, что приложение выводит в стандартный вывод, сохраняется и возвращается при использовании команды heroku logs
. Итак, я собираюсь добавить переменную конфигурации, которая указывает, нужно ли мне направлять логи в stdout
или в файл, как я делал. Вот изменение в конфигурации:
config.py: Возможность логирования в стандартный вывод.
class Config:
# ...
LOG_TO_STDOUT = os.environ.get('LOG_TO_STDOUT')
Затем в функции фабрики приложений я могу проверить эту конфигурацию, чтобы узнать, как настроить логгер приложения:
app/__init__.py: Логирование в стандартный вывод или файл.
def create_app(config_class=Config):
# ...
if not app.debug and not app.testing:
# ...
if app.config['LOG_TO_STDOUT']:
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
app.logger.addHandler(stream_handler)
else:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/microblog.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('Microblog startup')
return app
Итак, теперь мне нужно установить переменную окружения LOG_TO_STDOUT
, когда приложение запускается в Heroku, но не в других конфигурациях. Интерфейс командной строки Heroku упрощает это, поскольку предоставляет возможность устанавливать переменные среды для использования во время выполнения:
$ heroku config:set LOG_TO_STDOUT=1
Setting LOG_TO_STDOUT and restarting flask-microblog... done, v4
LOG_TO_STDOUT: 1
Скомпилированные переводы
Третий аспект Microblog, который зависит от локальных файлов, - это скомпилированные файлы перевода на язык. Более прямой вариант гарантировать, что эти файлы никогда не исчезнут из эфемерной файловой системы, - это добавить скомпилированные языковые файлы в репозиторий git, чтобы они стали частью начального состояния приложения после его развертывания на Heroku.
Более элегантный вариант, на мой взгляд, заключается в включении команды flask translate compile
в команду запуска, предоставляемую Heroku, чтобы при каждом перезапуске сервера эти файлы компилировались снова. Я собираюсь выбрать этот вариант, поскольку знаю, что для моей процедуры запуска в любом случае потребуется более одной команды, поскольку мне также нужно выполнить миграцию базы данных. Итак, пока я отложу эту проблему в сторону и вернусь к ней позже, когда напишу Procfile.
Хостинг Elasticsearch
Elasticsearch - одна из многих служб, которые можно добавить в проект Heroku, но, в отличие от Postgres, это услуга, предоставляемая не Heroku, а третьими сторонами, которые сотрудничают с Heroku для предоставления дополнений. На момент написания этой статьи интегрированную службу Elasticsearch предоставляли три разных поставщика. Если вы предпочитаете не развертывать функцию поиска, пропустите этот раздел. Вы по-прежнему сможете развернуть приложение, но функция поиска не будет включена.
Из опций Elasticsearch, доступных в качестве дополнений, я решил попробовать SearchBox, который поставляется с бесплатным начальным планом. Чтобы добавить SearchBox в свою учетную запись, вам необходимо выполнить следующую команду при входе в систему на Heroku:
$ heroku addons:create searchbox:starter
Эта команда развернет службу Elasticsearch и оставит URL-адрес подключения для службы в переменной окружения SEARCHBOX_URL
, связанной с вашим приложением. Еще раз имейте в виду, что эта команда завершится ошибкой, если вы не добавите свою кредитную карту к своей учетной записи Heroku.
Если вы вспомните главу 16, мое приложение ищет URL-адрес подключения Elasticsearch в переменной ELASTICSEARCH_URL
, поэтому мне нужно добавить эту переменную и установить для нее URL-адрес подключения, назначенный SearchBox:
$ heroku config:get SEARCHBOX_URL
<your-elasticsearch-url>
$ heroku config:set ELASTICSEARCH_URL=<your-elasticsearch-url>
Здесь я сначала попросил Heroku напечатать значение SEARCHBOX_URL
, а затем добавил новую переменную окружения с именем, для которого ELASTICSEARCH_URL
установлено то же значение.
Многие другие функции приложения также настраиваются с помощью переменных окружения, например, SECRET_KEY
, MS_TRANSLATOR_KEY
, MAIL_SERVER
и еще несколько. Эти переменные также необходимо скопировать в развертывание Heroku, чтобы они были доступны приложению. Можно использовать heroku config:set
для переноса этих переменных из вашего файла .env в Heroku.
В приведенном ниже примере настраивается секретный ключ:
heroku config:set SECRET_KEY=7853fbd853a249c586f7d810a7938b43
Обновление зависимостей
Heroku ожидает, что зависимости будут находиться в файле requirements.txt, точно таком, как я определил это в главе 15. Но для запуска приложения на Heroku мне нужно добавить в этот файл две новые зависимости.
Heroku не предоставляет собственного веб-сервера. Вместо этого он ожидает, что приложение запустит свой собственный веб-сервер с номером порта, указанным в переменной окружения $PORT
. Поскольку веб-сервер разработки Flask недостаточно надежен для использования в производстве, я собираюсь снова использовать Gunicorn, сервер, рекомендованный Heroku для приложений на Python.
Приложение также будет подключаться к базе данных Postgres, и для этого SQLAlchemy требует установки пакетов psycopg2
или psycopg2-binary
. Двоичная версия, как правило, предпочтительнее, поскольку она устанавливает уже созданную версию этого пакета, в отличие от psycopg2
которая требует установки компилятора C для сборки пакета во время установки.
Пакеты gunicorn
и psycopg2-binary
необходимо добавить в файл requirements.txt.
Файл Procfile
Heroku должен знать, как запускать приложение, и для этого он использует файл с именем Procfile в корневом каталоге приложения. Формат этого файла прост, каждая строка содержит имя процесса, двоеточие, а затем команду, которая запускает процесс. Наиболее распространенным типом приложения, которое работает на Heroku, является веб-приложение, и для этого типа приложения имя процесса должно быть web
. Ниже вы можете увидеть файл обработки для микроблога:
Procfile: Файл обработки Heroku.
web: flask db upgrade; flask translate compile; gunicorn microblog:app
Здесь я определил команду для запуска веб-приложения в виде трех последовательных команд. Сначала я запускаю обновление миграции базы данных, затем я компилирую языковые переводы и, наконец, запускаю сервер.
Поскольку первые две подкоманды основаны на команде flask
, мне нужно добавить переменную среды FLASK_APP
:
$ heroku config:set FLASK_APP=microblog.py
Setting FLASK_APP and restarting flask-microblog... done, v6
FLASK_APP: microblog.py
Приложение также полагается на другие переменные среды, которые настраивают сервер электронной почты или токен для прямых переводов. Их необходимо дополнить дополнительными командами heroku config:set
.
Команда gunicorn
проще, чем та, которую я использовал для развертывания Ubuntu, потому что этот сервер имеет очень хорошую интеграцию со средой Heroku. Например, по умолчанию используется переменная среды $PORT
, и вместо использования опции -w
для установки количества рабочих процессов, Heroku рекомендует добавить переменную с именем WEB_CONCURRENCY
, которая используется gunicorn
, когда -w
не указана, что дает вам гибкость для управления количеством рабочих процессов без необходимости изменять Procfile.
Развертывание приложения
Все подготовительные шаги завершены, теперь пришло время запускать развертывание. Чтобы загрузить приложение на серверы Heroku для развертывания, используется команда git push
. Это похоже на то, как вы отправляете изменения в своем локальном репозитории git на GitHub или другой удаленный сервер git.
Существует несколько вариантов того, как это сделать, в зависимости от того, как вы создали свой репозиторий git. Если вы используете мой код v0.18
, то вам нужно создать ветку на основе этого тега и отправить ее в качестве удаленной ветки main
следующим образом:
$ git checkout -b deploy
$ git push heroku deploy:main
Если вместо этого вы работаете со своим собственным репозиторием, то ваш код уже находится в ветке main
или master
, поэтому сначала вам нужно убедиться, что ваши изменения зафиксированы:
$ git commit -a -m "heroku deployment changes"
А затем вы можете запустить следующее, чтобы начать развертывание:
$ git push heroku main # you may need to use "master" instead of "main"
Независимо от того, какую вы отправляете ветку, вы должны увидеть следующий вывод Heroku:
$ git push heoroku deploy:main
Enumerating objects: 264, done.
Counting objects: 100% (264/264), done.
Delta compression using up to 12 threads
Compressing objects: 100% (183/183), done.
Writing objects: 100% (264/264), 59.44 KiB | 5.94 MiB/s, done.
Total 264 (delta 132), reused 143 (delta 62)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-20 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Python app detected
remote: -----> No Python version was specified. Using the buildpack default: python-3.9.6
remote: To use a different version, see: https://devcenter.heroku.com/articles/...
remote: -----> Installing python-3.9.6
remote: -----> Installing pip 20.2.4, setuptools 47.1.1 and wheel 0.36.2
remote: -----> Installing SQLite3
remote: -----> Installing requirements with pip
...
remote:
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 69.2M
remote: -----> Launching...
remote: Released v7
remote: https://flask-microblog.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/flask-microblog.git
* [new branch] deploy -> main
Метка heroku
, которую мы использовали в команде git push
, - это пульт дистанционного управления, который был автоматически добавлен командой Heroku при создании приложения. Аргумент deploy:main
означает, что я переношу код из локального репозитория, на который ссылается ветка deploy
, в ветку main
репозитория Heroku. Когда вы работаете со своими собственными проектами, вы, скорее всего, используете команду git push heroku main
, которая запускает вашу локальную ветку main
. Из-за структуры этого проекта я запускаю ветку, которая не является main
, но целевой веткой на стороне Heroku всегда должно быть main
или master
, поскольку это единственные названия ветвей, которые Heroku принимает для развертывания.
И все, теперь приложение должно быть развернуто по URL-адресу, который был указан в выходных данных команды, создавшей приложение. В моем случае URL был https://flask-microblog.herokuapp.com, так что это то, что мне нужно ввести, чтобы получить доступ к приложению.
Если вы хотите просмотреть записи журнала для запущенного приложения, используйте команду heroku logs
. Это может быть полезно, если по какой-либо причине приложение не запускается. Если были какие-либо ошибки, они будут в логах.
Развертывание обновлений приложения
Чтобы развернуть новую версию приложения, вам просто нужно запустить новую команду git push
с новым кодом. При этом процесс развертывания будет повторен, старое развертывание переведено в автономный режим, а затем заменено новым кодом. Команды в Procfile будут выполняться снова как часть нового развертывания, поэтому любые новые миграции или переводы базы данных будут обновляться в процессе.