はじめに
初投稿になります。プログラミング歴1年半のPHPエンジニアです。
普段の業務ではAWSを使っているのですが、まだ本番環境に関わったことがないので、今回のHerokuへのデプロイを通していろいろと勉強になったことを初心者の視点でまとめていきたいと思います。
今回作ったアプリについて簡単にご紹介します。
-
Twitterで特定のワードのリプライに反応して自動で返信
-
flaskで自動返信用テキストの追加・編集を行う管理画面を作成
-
Twitter Streaming API (Filter realtime Tweets) を利用して、特定のワードのリプライを24時間監視
-
BotはPythonの
twitter
ライブラリを用いて実装
ディレクトリ構成
app/
├── web.py # flaskアプリの実行ファイル
├── web/ # flaskアプリ
│ ├── models/
│ ├── static/
│ ├── templates/
│ ├── views/
│ ├── __init__.py
│ ├── config.py
│ └── database.py
├── bot.py # 自動返信Botの実行ファイル
├── bot/ # 自動返信Bot(一部省略)
│ ├── __init__.py
│ ├── config.py
│ └── database.py
├── Procfile
├── requirements.txt
├── runtime.txt
├── .env
├── .gitignore
├── migrations/ # Flask-Migrateで作成
└── venv/ # ローカルの仮想環境
flaskについては、以下の記事を参考にさせていただきました。
Twitter APIを利用している自動返信Botについては、こちらで詳細の説明をしています。
Herokuにデプロイする上で必要なファイル
Herokuに環境、必要なライブラリや実行ファイルなどを伝えるためのファイルを準備する必要があるみたいです。
requirements.txt
必要なライブラリをバージョンも含めて記載します。
venvで開発を行ったので、仮想環境にインストールしたパッケージを以下のコマンドでファイルに出力しました。
(venv)$ pip freeze > requirements.txt
インストールしたライブラリ一覧
alembic==1.4.2
autopep8==1.5.3
cffi==1.14.0
click==7.1.2
cryptography==2.9.2
Flask==1.1.2
Flask-Login==0.5.0
Flask-Migrate==2.5.3
Flask-SQLAlchemy==2.4.3
gunicorn==20.0.4
itsdangerous==1.1.0
Jinja2==2.11.2
Mako==1.1.3
MarkupSafe==1.1.1
pycodestyle==2.6.0
pycparser==2.20
PyMySQL==0.9.3
python-dateutil==2.8.1
python-dotenv==0.14.0
python-editor==1.0.4
six==1.15.0
SQLAlchemy==1.3.18
toml==0.10.1
twitter==1.18.0
Werkzeug==1.0.1
runtime.txt
これは別になくても良いみたいですが、pythonのバージョンを指定するために準備します。
また、Herokuがサポートしているバージョンを指定するよう注意が必要です。
python-3.7.9
Procfile
アプリの起動方法を指定します。
flaskアプリは本番環境ではgunicorn
などのWSGIサーバーを使う必要があります。
web: gunicorn web:app --log-file=-
worker: python bot.py
web
では、web.py中のappというFlaskインスタンスを起動しています。
worker
では、自動返信Botの実行ファイルbot.pyを実行しています。
環境変数の設定
ローカル環境と本番環境で異なる環境変数(ex. DB情報)をコードを書き換えずにどう設定するかにつまづいたので、まとめます。
Herokuの環境変数
以下のコマンドで環境変数を設定できます。
(コマンドを使用するためには、Heroku CLI のインストールが必要です)
$ heroku config:set DB_HOST=xxxxxxxxxxx
環境変数の一覧を表示して、設定できているか確認できます。
$ heroku config
MySQL DB の環境変数の設定は、こちらを参考にさせていただきました。
・HerokuでMySQLを利用する方法
ローカル環境の環境変数
.envファイルを用意して、そこに環境変数を記載していきます。
機密情報なので、gitignoreするのを忘れないようにしましょう。
ENV = 'LOCAL'
# DB
DB_HOST = 'xxxxx'
DB_NAME = 'xxxxx'
DB_USER = 'xxxxx'
DB_PASSWORD = 'xxxxx'
# Session
SESSION_SECRET_KEY = 'xxxxx'
アプリ側で環境変数を読み込む
app/web/config.py で以下のように環境変数を読み込んで、アプリ側の設定を行います。
"""FlaskのConfigを提供する"""
import os
from os.path import join, dirname
from dotenv import load_dotenv
dotenv_path = join(dirname(__file__), '../.env')
load_dotenv(dotenv_path)
class Config:
# Flask
if (os.environ.get('ENV') == 'LOCAL'):
DEBUG = True
else:
DEBUG = False
# Session
SECRET_KEY = os.environ.get('SESSION_SECRET_KEY')
# SQLAlchemy
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@{host}/{db_name}?charset=utf8'.format(**{
'user': os.environ.get('DB_USER'),
'password': os.environ.get('DB_PASSWORD'),
'host': os.environ.get('DB_HOST'),
'db_name': os.environ.get('DB_NAME')
})
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
Config = Config
このように設定すると、Heroku環境でもローカル環境でも環境変数を読み込むことができます。
Pythonにおける環境変数の扱いについては、こちらを参考にさせていただきました。
・【GitHub】に載せたくない環境変数の書き方 Python
1アカウントにつき1日1リプライをどう実装するか
短時間に何度も自動返信するのは、Twitter API的によろしくないので、1アカウントにつき1日1リプライまでという仕様にしました。
一度リプライしたユーザをどうやって管理するか迷ったんですけど、
また、Dynosは、Herokuで実行されているアプリケーションの正常性を維持するために、少なくとも1日1回再起動(循環)されます。ローカルファイルシステムへの変更はすべて削除されます。サイクリングは24時間ごとに発生します(さらに、アプリケーションのすべてのdynoが同時に再起動しないように、最大216のランダムな分)。
Herokuの自動再起動が24時間周期で行われるみたいなので、今回はworkerに指定したPythonファイル上(常時ループし続けている)のリストでリプライしたユーザを管理することにしました。
こうすることで、自動再起動されるたびにリストは初期化されるので、24時間ごとに再び自動返信を行うことができるようになります。
replied_user_list = [] # 返信したユーザをリストで管理
# 24時間監視し続ける(プログラムが走り続ける)
for tweet in twitter_stream.statuses.filter(language='ja', track=tracking_text):
# 以下、特定のワード(tracking_text)が含まれるツイートを検知したときの処理
# ...
# 自動返信を行うと、リストに追加する
replied_user_list.append(user_id)
# ...
webがsleepするとworkerもsleepする問題
Heroku の無料プラン (Free Dyno) では、30分間Webアプリにアクセスがないと自動的にsleepします。
これに関しては、全く問題がないのですが、どうやらweb
がsleepするとworker
もsleepしてしまうみたいです。[https://devcenter.heroku.com/articles/free-dyno-hours#dyno-sleeping]
Apps that only utilise a free worker dyno do not sleep, because they do not respond to web requests. Be mindful of this as they may run 24/7 and consume from your pool of hours.
worker
がsleepしてしまうと、自動返信Botが反応しなくなってしまうので、これはかなり問題です。
そこで、web
をsleepさせないために以下のようにリクエストを定期的に送るなどの対処法があるみたいですが、
・Herokuの無料dynoをスリープさせないで24時間稼働させる4つの方法
これだと、web
とworker
のdynoがともに24時間稼働し続けることになってしまい、無料のdyno時間である1000時間/月をオーバーしてしまいます。
結果、web
は必要なとき以外は停止しておくことにしました。(Heroku ダッシュボードで簡単に起動できる)
worker
単体ではsleepすることがないので、これで自動返信Botが24時間稼働し続けられるようになりました。
まとめ
pythonでのWebアプリ開発は初めてで、そういうことも書こうと思ったんですが、今回はHerokuへデプロイする上でつまづいたことを中心に書きました。
コーディング以外の知識をもっと身に付ける必要があるなと痛感しました。
今後はAWSの勉強をして、AWSソリューションアーキテクトのアソシエイトを取りたいと思います。
初めての技術ブログですので、読みにくい等の技術以外のアドバイスもどんどんよろしくお願いします。もちろん、技術的な指摘もよろしくお願いします。