pythonのFlaskベース+PillowのWebアプリをherokuで実行する

heroku python Flask Pillow その他の戦い

#heroku #python #Webアプリ #Flask #Pillow #デプロイ

Pillowを使った画像処理を行なうアプリをFlaskベースのWebアプリに変え、さらにherokuにデプロイして実行してみました。

スポンサーリンク

確認したOS、pythonとバージョン

Windows 10 Pro 1903(OSビルド 18362.752)
python 3.7.6

サンプルプログラム

本記事では、下記のサンプルプログラムを使用しました。
Pillowを用いた画像表示プログラムです。

<rainbow.py>

import io
from PIL import Image, ImageDraw # PILからImage, ImageDrawクラスを取り出す
from flask import Flask, request, redirect, url_for, render_template, send_file

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('img.html')

@app.route('/show_img', methods=['GET', 'POST'])
def show_img():

    im = Image.new('RGB', ( 500,  600), (255, 255, 255)) # 500 ✕ 600ドットで白(=(255,255,255)の
                                                     # キャンバスを準備する
    draw = ImageDraw.Draw(im) # 絵を描き込むバケツ(ImageDraw.Drwオブジェクト)を準備する

    red = 0
    green = 0
    blue = 0

    red_step = 5
    green_step = 5
    blue_step = -5

    red_cnt = 0
    green_cnt = -250
    blue_cnt = 250

    for delta in range(  0, 501,  1) :
        red_cnt = red_cnt + red_step
        if red_cnt > 0 :
            red = red + red_step
            if red > 250 :
                red = 250
            if red < 0 :
                red = 0
        if red_cnt >= 500 or red_cnt <= -250 :
            red_step = -red_step

        green_cnt = green_cnt + green_step
        if green_cnt > 0 :
            green = green + green_step
            if green > 250 :
                green = 250
            if green < 0 :
                green = 0
        if green_cnt >= 500 or green_cnt <= -250 :
            green_step = -green_step

        blue_cnt = blue_cnt + blue_step
        if blue_cnt > 0 :
            blue = blue + blue_step
            if blue > 250 :
                blue = 250
            if blue < 0 :
                blue = 0
        if blue_cnt >= 500 or blue_cnt <= -250 :
            blue_step = -blue_step

        draw.line(((   delta,   0),( 500-delta, 500)), fill=(red, green, blue))
        draw.line(((   0, delta),( 500, 500-delta)), fill=(red, green, blue))


    im.save("static/images/tmp.png")
    return render_template('img.html', img_url="static/images/tmp.png")

if __name__ == '__main__':
    app.debug = True
    app.run()

以下は、画像の表示先のWebページのHTMLファイルです。”img_url”のところに画像が表示されます。

<templates\img.html>

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Image</title>
</head>
<body>
	<form method="post" action="{{ url_for('show_img') }}" enctype="multipart/form-data">
	 イメージを表示
		<input type="submit" value="送信">
	</form>
	<div>
		<p><img src="{{ img_url }}"></p>
	</div>
</body>
</html>

最初から目的のアプリをデプロイしてもいいのですが、herokuに初めてデプロイをする場合は、下記のサンプルプログラムを使用して最初に一通り行ってみるほうが良いです。

<hello.py>

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()

このプログラムはHello world!を表示するだけの、必要最小限のモジュール(Flask)だけをimportしており、他にも導入する必要のあるものは最小限となるためです。
このプログラムで最初にデプロイ~アプリ実行を行った後に、目的のプログラムでデプロイしてみて、どこが違うかを確認すると、スムーズに原因が突き止められます。

herokuへのサインアップ

https://id.heroku.com/login に行き、”Sign Up”で、サインアップ画面に行きます。
必要事項を記入すると確認のメールが届きますので、手続きを進めます。

ログインすると以下の画面になります。
ログイン後の画面の左下の”Documentation”を押します。

heroku CLIのインストール

“Documentation”を押すと下記画面になります。

The Heroku CLIを押します。
その後、画面を少し下にスクロールさせると、下記の画面が出てきます。

小生のOSはWindows 10 64bitのため、”64-bit installer”をクリックすると、下記ファイルがダウンロードされました。

実行すると、以下の画面になります。
“Next”を押します。

インストール先フォルダを変更しない場合は、そのまま”Install”を押します。

“Close”を押して終了します。

heroku CLIからherokuにログイン

コマンドプロンプトを起動し、heroku loginを実行します。

以下の画面が表示されます。

“Log in”を押して、メールアドレスとパスワードを入力すると、以下のようにログインが完了します。

コマンドプロンプト上でも、成功している旨のメッセージが表示されます。

デプロイ(アプリの配備)

runtime.txtの作成

以下のような記載をしたファイル runtime.txt を作成します。
バージョンは python –version で確認します。

<runtime.txt>

python-3.7.6

requirements.txtの作成

gunicorn(*1)と、importしているモジュールを記載した requirements.txtを作成します。
ただし、画像処理用のモジュールである”PIL”をインポートしますが、”PIL”のままではだめで、Pillowとする必要がありました。おそらくPillowの前身がPILであり、区別するためにそうなっているのかと思われます。
なお、hello.pyをデプロイする場合は、”Pillow”は必要ありません。

<requirements.txt>

gunicorn
flask
Pillow

gnicornのように、このファイルには、importに指定したもの以外に導入が必要なものも記載が必要になります。
一般のサイトには、そのことも考えて、pip freeze > requirements.txt を実行して作成する方法が多数公開されていますが、Anacondaを導入している小生の環境では、エラーが多発しました。
Anacondaには、導入物が大量にあるためで、herokuでは扱っていないらしいライブラリ・モジュールを導入しようとしてエラーになったようです。
Anacondaのケースのように、かえって必要以上のものをデプロイしようとしてエラーになることもあるので、pip freeze > requirements.txt が万能の方法であるとは言えません。

*1 WSGIサーバーと呼ばれるPython固有の
  Webアプリケーションサーバー
  WSGIはWeb Server Gateway Interfaceの略。
  gnicorni以外にもいくつかある。

Procfileの作成

他のサイトではProcfileにpythonを記載しているものを見かけましたが、pythonではなく、gunicornが必要でした。
前述のようにgnicorn以外のWSGIサーバを使っている場合は、それが必要になります。

<Procfile>

web: gunicorn rainbow:app --log-file=-

上記の “rainbow”は、メインプログラム rainbow.py で、”app”は下記のように代入しているFlaskクラスのオブジェクトを指定しています。

app = Flask(__name__)

hello.pyをデプロイする場合は、 以下のProcfileを作成します。

web: gunicorn hello:app --log-file=-

デプロイ~実行

herokuアプリの名前を “boh-testapp-001” としてデプロイする場合、下記の手順となります。
なお、小生は以下をコマンドプロンプトで実行しました。

: herokuアプリを作成
heroku create boh-testapp-001 --buildpack heroku/python

: gitのローカルレポジトリ作成
git init

: herokuのリモートレポジトリの作成(?)
heroku git:remote -a boh-testapp-001

: herokuのリモートレポジトリとローカルレポジトリのリンクしリモートレポジトリ名"heroku"とする
git remote add heroku https://git.heroku.com/boh-testapp-001.git  
: または、上のを訂正したい場合は、
git remote --set-url heroku https://git.heroku.com/boh-testapp-001.git

: 全ファイルをローカルレポジトリに登録
git add .
git commit -m "<comment>"

: herokuのリモートレポジトリmasterブランチにpush
git push heroku master

herokuアプリの実行は以下で行います。
Webブラウザが起動されて、アプリが実行されます。

heroku open

送信ボタンが表示されるので、押すと以下の画面が表示されます。

アプリの実行がうまくいかないとき

アプリの実行がうまくいかなかったときは、まず、どんなエラーが発生しているかを、以下で確認します。

heroku logs

エラーが発生したときには、どんな場合でもそうですが、まずは一番最初に発生しているエラーに着目して原因究明しましょう

下記のような crash のエラーが常に発生してしまうときは、dynoというコンテナ(アプリを実行する1つの入れ物)を再起動(restart)すると良いようです。

2020-07-28T12:16:15.634251+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=boh-testapp-001.herokuapp.com request_id=fa086a94-16b3-41e7-bbee-d61f3e54362b fwd="211.124.115.230" dyno= connect= service= status=503 bytes= protocol=https

再起動(restart)は以下のように実行します。
“web.1″はdynoの1番目を意味します。
dynoが複数ある場合は、web.2 ~についても実行します。

heroku restart web.1 --app boh-testapp-001

作業中に遭遇したトラブル

Flaskのルール違反

Flaskを使う場合、ルールとして以下のものがありましたが、それに従っていなかったためにうまく動作しませんでした。
他にもルールがあると思いますので、他のサイトをご参照ください。

・render_templateを使う場合は、templatesディレクトリを作って、テンプレートに使うHTMLファイルをそこに配置する必要がある
・プログラム中で、例えばPillowのim.save()などで画像を生成する場合、staticディレクトリを作成してherokuにデプロイする必要がある

 staticディレクトリがない場合、空のディレクトリが必要になるため、例えば”.gitkeep”などの空のファイルを echo > static/.gitkeep などとして作成し、git add ~を実行してherokuにデプロイする必要があります。

アプリ画面が表示されず応答待ちが続いてタイムアウトエラーが発生する

gunicornがデプロイされていなかったことが原因でした。
Procfileにgunicornを追加してgit add Procfile ~ git push heroku master を行います。

heroku logsに下記エラーが出るModuleNotFoundError: No module named ‘hogehoge’

hogehogeモジュールの不足が原因でした。
requirement.txtに追記して再実行します。

heroku logsに下記エラーが出るModuleNotFoundError: No module named ‘PIL’

モジュール名はPILではなくPillowにする必要がありました。
requirement.txtに追記して再実行します。

heroku logsに下記エラーが出るError R10 (Boot timeout) ->Web process failed to bind to $PORT within 60 seconds of launch

herokuのポート5000での実行がうまくいっておらず、接続タイムアウトになりました。
このケースの場合、原因はいろいろありますが、このときは、gunicornの導入が必要でした。
Flaskを使ったらポート5000で起動するはずなのですが、gunicornのようなWSGIサーバーも必要らしいです。
ここは、今後調査を行います。

heroku logsに下記エラーが出るModuleNotFoundError: No module named ‘hogehoge’

Procfileに記載のFlaskクラスのオブジェクト名に誤りがありました。

heroku logsに下記エラーが出るFileNotFoundError: [Errno 2] No such file or directory: ‘hogehoge’

以下のような原因が考えられます。

・プログラム中で生成されるファイルの格納ディレクトリがない
 格納ディレクトリに、.gitkeepファイルを配置してgit add~してデプロイします。

・git add, commit漏れしているファイルやディレクトリがある
 漏れているものをgit add~してデプロイします。

0

コメント

タイトルとURLをコピーしました