7
14

More than 1 year has passed since last update.

【Heroku】Python Webサーバをおっ立てよう!BIN!

Last updated at Posted at 2021-08-08

2022年11月28日より「Heroku」の無償プランが廃止になりました。
この記事は「Heroku」に無償プランがあったときの記事になります。現在は、有料プランになりますが、手順などはほぼ同じでできると思います。
Heroku の価格:https://jp.heroku.com/pricing

➊ はじめに

Herokuは、無料でwebサーバをホスティングできるPaaS型サービスです。クレカの登録もなく無料で使わせてくれるので、安心してありがたく使わせて頂いています。「webアプリを作ってローカルで遊ぶだけでなく、全世界に公開したら何となくワクワクさも増えるよね」ということで、Herokuにwebサーバをおっ立てましょう。BIN!

➋ やること

PythonコードのwebアプリをHerokuにデプロイします。

🤖:はじめまして。ロボットの「ボロット」です。
👧:挨拶はいいから、Herokuへのデプロイ方法を早く教えなさい!
🤖:では早速………とは言え、いきなりHerokuへwebサーバをおっ立てようと焦ってはいけません。急がば回れ、ものには手順があります。少しずつ手順をこなしていきましょう。

➌ Herokuへのデプロイ方法

Herokuへのデプロイ方法は、「Gitからデプロイ」と「Dockerでデプロイ」の2種類があります。

🤖:まずは、Herokuへのデプロイ方法について確認をしておきましょう。Herokuにデプロイする方法は、以下2つの方法があります。
👧:要点のみにしてね。私は忙しいんだから。

(1) Gitからデプロイ

Gitからデプロイする方法です。Gitとしては、Heroku GitリポジトリGitHubリポジトリを使用することができます。下図では、GitHubからデプロイする方法としています。
github.png
Gitからデプロイをする場合は、足りないBuildPackを選んだり、そのための設定ファイルを作ったり、ビルド後のSlugサイズが500MiBまでと制約が多いです。

例えば、pipでInstallしたライブラリは、Defaultの「heroku/python」というBuildPackでそのままInstall可能ですが、condaを使用してInstallしたライブラリを入れた場合は、Heroku Buildpacksから、conda等で検索を実行し、別途必要なビルドパックを探し、そのための設定ファイルも用意する必要があります。

👧:制限がいっぱいあって、面倒くさそうね…

(2) Dockerでデプロイ

Dockerでデプロイする方法です。コンテナレジストリ​を使用する方法とheroku.ymlを使用する方法があります。ここではコンテナレジストリを使用する方法を説明します。
docker.png

👧:こっちの方が、見た感じスマートね!

(3) 結局どっちがいいの?

👧:で?2つあるのは分かったから、結局どっちがいいの?
🤖:結論から言うと、Dockerでデプロイする方法の方が良いです

ボロット目線で恐縮ですが、以下にメリット・デメリットを簡単にまとめました。Dockerでデプロイする方法の方がメリットが大きいですし、Docker自体、大変便利で他でも色々お役に立つので、知らない方はこの際に覚えちゃっても良いかもしれません。

デプロイ方法 メリット デメリット
Gitからデプロイ ・Dockerの知識がなくてもデプロイ可能 ・Slugサイズの制限がある
・ビルド構築が大変になりがち
・開発と本番環境が完全一致しない
Dockerでデプロイ ・Slugサイズの制限なし
・Dockerイメージのビルドは柔軟性あり
・開発と本番環境が同じ動作
・Dockerの知識が必要

➍ python webアプリ

👧:んで。気にはなっていたんだけど、webアプリどーすんの?私作るのヤダよ🙁。
🤖:はい。ボロット自作webアプリである(mnister)を利用して、Heroku Deploy Kitをご用意しました。webアプリについては、簡単なコードなのでソースコードを参照するか、過去の記事を参考にしてください😌。
👧:ボロいwebアプリだかなんだか知れないけど、とりあえず、webアプリの内容には興味がないからいいわ😒。
🤖:ハイ、リョウカイシマスタ😑………

(1) mnisterとは

mnisterとは、フロントエンドにJavaScript、バックエンドにPythonを用い、ブラウザ側で手書きした文字画像をサーバ側へ送信し推論して返却する自作webアプリケーションです。なおバックエンドのwebアプリケーションは、Flaskというフレームワークを使用して作成されています。
mnister.png

(2) 要求事項

■要求事項

■コマンド確認

以下、コマンドが正しく使えるか試しにバージョンでも確認しておきましょう。

🤖:コマンドのパラメータは、大文字❛V❜と小文字❛v❜があるので注意してくださいね。
👧:あら、似てるんだからいいじゃない!
🤖:ダメです…

python -V
Python 3.8.11
git --version
git version 2.25.1
docker -v
Docker version 20.10.7, build f0df350
heroku -v
heroku/7.56.1 linux-x64 node-v12.21.0

(3) webアプリInstall

mniserをgit cloneでローカルにDLします。

git clone https://github.com/PoodleMaster/mnister_HerokuDeployKit

ワークディレクトリを変更します。

cd mnister_HerokuDeployKit

ライブラリをインストールします。

pip install -r requirements.txt

➎ Herokuデプロイへの道

それでは、(1)~(4)まで順番に確認しながらやっていきましょう。
heroku.png

(1) Flaskで起動確認

まずは、pythonでwebアプリを簡単に作れるフレームワーク(Flask)を使用して、webアプリ(mnister)をLocalhostで実行します。

python server.py
実行ログ
 * Serving Flask app 'server' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:9000/ (Press CTRL+C to quit)

 👉 http://127.0.0.1:9000

🤖:Flaskから起動したローカルサーバへは、アクセス出来ましたか?
👧:ほーい、できたよ🙆!mnisterアプリも動作したよ😊。
🤖:では、次はGunicornで起動しましょう!

(2) Gunicornで起動確認

HTTPサーバー(Gunicorn)を使用して、webアプリフレームワーク(Flask)+webアプリ(mnister)をLocalhostで実行します。

起動方法
gunicorn --bind=localhost:8000 server:app
実行ログ
[2021-07-28 20:04:18 +0900] [18210] [INFO] Starting gunicorn 20.0.4
[2021-07-28 20:04:18 +0900] [18210] [INFO] Listening at: http://127.0.0.1:8000 (18210)
[2021-07-28 20:04:18 +0900] [18210] [INFO] Using worker: sync
[2021-07-28 20:04:18 +0900] [18212] [INFO] Booting worker with pid: 18212

 👉 http://127.0.0.1:8000

🤖:Gunicornで起動したローカルサーバへは、アクセス出来ましたか?
👧:同じことを何度もさせないで😤!でけたけど、何が違うの😡⁉
🤖:FlaskのWebフレームワークは、便利なビルトインWebサーバーを備えているけど、一度に1つのリクエストしか処理できないんだ。Gunicornを使うと任意のPython Webアプリを並列して実行できるようになるんだ。とりあえず、次はdockerでコンテナ化してみよう!

(3) Dockerコンテナで起動確認

Dockerを使用してHTTPサーバ(Gunicorn)+webアプリフレームワーク(Flask)+webアプリ(mnister)をコンテナ化しLocalhostで実行します。

👧:Dockerって、最近良く聞くけど、なんだか良く分からないんだけど😅………
🤖:とりあえず、こちらから Dockerの超基礎のお勉強をお願い致します😌。
👧:ふぇーい😗

(3-1) Dockerfile作成

docker buildコマンドで、イメージを作るため「Dockerfile」という設定ファイルを作成します。ここに環境構築するためのコマンド類を記載します。

今回サンプルのwebアプリは、pipでInstallするライブラリばかりだったので、condaコマンドは不要でしたが、とりあえずcondaコマンドも実行できるイメージを利用することとします。Docker Hubから「continuumio/anaconda3」をPullするため、FROM指定します。

最後の行の「--bind=0.0.0.0」というのは、全てのIPアドレスからの受信を許可するという意味です。

Dockerfile
# base
FROM continuumio/miniconda3:4.9.2

# proxy配下で実行している場合は、以下を有効化しProxyサーバを指定してください。
## apt-get
# ENV http_proxy  "http://proxy.server.com:8080"
# ENV https_proxy "http://proxy.server.com:8080"

## pip
# ENV HTTP_PROXY  "http://proxy.server.com:8080"
# ENV HTTPS_PROXY "http://proxy.server.com:8080"

# init
RUN apt-get update --allow-releaseinfo-change
RUN apt-get install -y build-essential
RUN apt-get install -y libjpeg-dev
RUN pip install --upgrade pip

# install conda package
# condaコマンドも使えます。
# RUN conda install gcc_linux-64 gxx_linux-64

# install pip package
RUN pip install Pillow==7.2.0
RUN pip install tensorflow-cpu==2.5.0
RUN pip install Flask==2.0.1
RUN pip install gunicorn==20.1.0

# app
WORKDIR /bg
COPY server.py .
COPY colab_mnist.hdf5 .
COPY static static/
COPY templates templates/

EXPOSE 5000
CMD ["gunicorn", "--bind=0.0.0.0:5000", "server:app"]

(3-2) dockerイメージの作成

Dockerfileが作成できたら、dockerイメージを作成しましょう。

sudo docker build -t img-mnister .
実行結果
[+] Building 254.8s (20/20) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 915B                                                                               0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load metadata for docker.io/continuumio/miniconda3:latest                                           3.4s
 => [auth] continuumio/miniconda3:pull token for registry-1.docker.io                                              0.0s
 => [internal] load build context                                                                                  0.1s
 => => transferring context: 7.80MB                                                                                0.1s
 => [ 1/14] FROM docker.io/continuumio/miniconda3@sha256:592a60b95b547f31c11dc6593832e962952e3178f1fa11db37f43a2a  0.0s
 => CACHED [ 2/14] RUN apt-get update                                                                              0.0s
 => CACHED [ 3/14] RUN apt-get install -y build-essential                                                          0.0s
 => CACHED [ 4/14] RUN apt-get install -y libjpeg-dev                                                              0.0s
 => CACHED [ 5/14] RUN pip install --upgrade pip                                                                   0.0s
 => CACHED [ 6/14] RUN pip install Pillow==7.2.0                                                                   0.0s
 => [ 7/14] RUN pip install tensorflow-cpu==2.5.0                                                                239.6s
 => [ 8/14] RUN pip install Flask==2.0.1                                                                           2.8s
 => [ 9/14] RUN pip install gunicorn==20.1.0                                                                       2.5s
 => [10/14] WORKDIR /bg                                                                                            0.0s
 => [11/14] COPY server.py .                                                                                       0.0s
 => [12/14] COPY colab_mnist.hdf5 .                                                                                0.1s
 => [13/14] COPY static static/                                                                                    0.0s
 => [14/14] COPY templates templates/                                                                              0.0s
 => exporting to image                                                                                             6.1s
 => => exporting layers                                                                                            6.1s
 => => writing image sha256:52fe0852439e2126eab7663362b6ceba0925d8f5b05c01e3497c770b9216f189                       0.0s
 => => naming to docker.io/library/img-mnister             

👧:あれ?さっき作ったDockerfileは使用しないの?
🤖:docker buildコマンドの最後の.が、Dockerfileのある場所を表しています。つまり、カレントディレクトリにあるDockerfileは使用しています。

(3-3) dockerコンテナの作成・開始

docker runコマンドは、「コンテナの作成を行うdocker createコマンド」と「コンテナの開始を行うdocker startコマンド」を一括で実施するコマンドです。dockerイメージが作成できたら、docker runコマンドで、dockerコンテナを作成・開始させましょう。

sudo docker run -it --publish=5000:5000 --name="con-mnister" img-mnister
実行結果
[2021-07-30 13:41:41 +0000] [1] [INFO] Starting gunicorn 20.1.0
[2021-07-30 13:41:41 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2021-07-30 13:41:41 +0000] [1] [INFO] Using worker: sync
[2021-07-30 13:41:41 +0000] [8] [INFO] Booting worker with pid: 8

 👉 http://127.0.0.1:5000

🤖:dockerコンテナのサーバへは、アクセス出来たかな?
👧:またまた、できました🙆!ただ、dockerコマンドがパラメータが多くて、少々メンドイわね🙁ソースコード変更したりすると、何度も同じパラメータを打つことにならない?そーゆーの私、指が痛くてイヤなんですけど😠💨
🤖:コマンド打ちが、少し楽になる方法を後で教えます。とりあえず、次いきましょう。いよいよHerokuへデプロイですよ!

(4) Herokuへデプロイ

localhostでアクセスすることができたら、次はHerokuへデプロイしましょう!

(4-1) Dockerfile更新

Herokuの注意点ですが、『Web プロセスは、Herokuにより設定された「​$PORT」上でHTTPトラフィックをListenする必要があります。​Dockerfile内の​EXPOSEは考慮されません』とのことなので、Listen Portを「$PORT」へ変更する必要があります。

Dockerfile
# Dockerfileの最終行を次に書き換える
CMD gunicorn --bind 0.0.0.0:$PORT server:app

(4-2) Heroku CLI

Container Registr にログインします。

sudo heroku container:login
実行結果
Login Succeeded

mnister-webという名称で、Herokuアプリを作成します。
ユニークである必要があるので、Herokuアプリ名称が他の方に取られていたら、別名にしてください。

sudo heroku create mnister-web
実行結果
Creating ⬢ mnister-web... done
https://mnister-web.herokuapp.com/ | https://git.heroku.com/mnister-web.git

イメージをビルドし、Container Registryにプッシュします。

sudo heroku container:push web -a mnister-web
実行結果
=== Building web (/home/popcorn/mnister_HerokuDeployKit/Dockerfile)
[+] Building 2.1s (19/19) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 1.02kB                                                                             0.0s
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load metadata for docker.io/continuumio/miniconda3:latest                                           1.8s
 => [internal] load build context                                                                                  0.1s
 => => transferring context: 7.80MB                                                                                0.1s
 => [ 1/14] FROM docker.io/continuumio/miniconda3@sha256:592a60b95b547f31c11dc6593832e962952e3178f1fa11db37f43a2a  0.0s
 => CACHED [ 2/14] RUN apt-get update                                                                              0.0s
 => CACHED [ 3/14] RUN apt-get install -y build-essential                                                          0.0s
 => CACHED [ 4/14] RUN apt-get install -y libjpeg-dev                                                              0.0s
 => CACHED [ 5/14] RUN pip install --upgrade pip                                                                   0.0s
 => CACHED [ 6/14] RUN pip install Pillow==7.2.0                                                                   0.0s
 => CACHED [ 7/14] RUN pip install tensorflow-cpu==2.5.0                                                           0.0s
 => CACHED [ 8/14] RUN pip install Flask==2.0.1                                                                    0.0s
 => CACHED [ 9/14] RUN pip install gunicorn==20.1.0                                                                0.0s
 => CACHED [10/14] WORKDIR /bg                                                                                     0.0s
 => CACHED [11/14] COPY server.py .                                                                                0.0s
 => CACHED [12/14] COPY colab_mnist.hdf5 .                                                                         0.0s
 => CACHED [13/14] COPY static static/                                                                             0.0s
 => CACHED [14/14] COPY templates templates/                                                                       0.0s
 => exporting to image                                                                                             0.0s
 => => exporting layers                                                                                            0.0s
 => => writing image sha256:c3adae5e069a951b99b0fa50a708c0d92045e36b49801ac164882b62819264a8                       0.0s
 => => naming to registry.heroku.com/mnister-web/web                                                           0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
=== Pushing web (/home/popcorn/mnister_HerokuDeployKit/Dockerfile)
Using default tag: latest
The push refers to repository [registry.heroku.com/mnister-web/web]
fc5ee76742ef: Pushed
45ee37fa32e6: Pushed
95dd8dd0f97d: Pushed
d5c9db00cffd: Pushed
cf6f7fd36bf7: Pushed
32c94721f3b8: Pushed
d2f49e7a3555: Pushed
4a20a88105aa: Pushed
eae386b112f7: Pushed
e9ca61d9199c: Pushed
83ea5b6fba93: Pushed
fc58f36c22d8: Mounted from mnister-web/web
4067850c57b2: Mounted from mnister-web/web
e7a951258ef9: Mounted from mnister-web/web
692e8918cbe0: Mounted from mnister-web/web
814bff734324: Mounted from mnister-web/web
latest: digest: sha256:d7a25246b9372c6bd7b9da9b69da75f43dd4af4e1bdd4a329a5dc77fa54314a3 size: 3687
Your image has been successfully pushed. You can now release it with the 'container:release' command.

イメージからコンテナをwebサービスにリリースします。

sudo heroku container:release web -a mnister-web
実行結果
Releasing images web to app-mnister-web... done

 👉 https://mnister-web.herokuapp.com/

🤖:各々自分で作成したHerokuアプリ名のURLへアクセスしてくださいね。ところで、Heroku上のwebサーバへアクセス出来たかな?
🙆:うんはい。できました!簡単ですな😉ドヤッ
🤖:おめでとうございます👏パチパチ

➏ おまけ   (Docker Compose CLIの利用)

ソースコードを修正した場合などは、➎-(3)のイメージ作成とコンテナ開始を何回かやり直すこととなると思いますが、その度にdocker buildコマンド、docker runコマンドをシコシコ叩くのは面倒ですよね。いいのあります。docker composeコマンドを使うと、これらをまとめて実行することができます!

👧:あれ⁉docker-composeコマンドなら使ったことあるような🤔?
🤖:ハイフンなしのdocker composeコマンドの方が新しいです。docker composeコマンドとdocker-composeコマンドは、ほぼ同じもなので、気にしないでください。

(1) 設定ファイル作成

イメージ名「img-mnister」、コンテナ名「con-mnister」、アクセスポート「5000番」を使うように、設定ファイルを作成します。

docker-compose.yaml
version: '3'
services:
  web:
    build: .
    ports:
    - "5000:5000"
    image: img-mnister
    container_name: con-mnister

docker-compose.yamlをもっと勉強したい方は、「こちら」へどうぞ。

(2) 既存コンテナ停止と削除

➎-(3-3)で同名のコンテナを既に作っているので、ここでいきなり次のSTEPであるdocker compose upコマンドを実行するとエラーとなります。

sudo docker compose up
[+] Running 1/2
 ⠿ Network mnister_HerokuDeployKit_default  Created                                                                     0.6s
 ⠏ Container con-mnister               Creating
Error response from daemon: Conflict. The container name "/con-mnister" is already in use by container "4e8a871238c839525e8a61b29687451c01cfd6a34f0f9a1944acddd66f7884f7". You have to remove (or rename) that container to be able to reuse that name.

そのため、先にdocker rmコマンドで「con-mnister」コンテナを削除しておきます。ちなみに、コンテナを削除するためには停止をしておく必要があります。コンテナを停止していない場合は、docker stopコマンドで停止させます。

  • con-mnister」コンテナ停止
sudo docker stop con-mnister
  • con-mnister」コンテナ削除
sudo docker rm con-mnister

(3) イメージ作成&コンテナ作成・開始

sudo docker compose upコマンドで、イメージ作成とコンテナを作成・開始を実施しますが、既にイメージがある場合はビルドは行いません。また、既にコンテナがある場合も作成は行わず、コンテナの開始のみ実施します。

初回や、ソースコードを変更していない状態での再起動などを行う場合に、以下のコマンドを使用します。

sudo docker compose up

ソースコード等を修正してビルドが必要な場合は、強制ビルドのオプションをつければ、イメージ作成、コンテナ作成・開始を実施します。

sudo docker compose up --build

🤖:docker composeコマンドは、他にももっと色々なことができますので、色々調べて見ると約に立つものがあると思います。
👧:時間があったら調べておきます🤔。
🤖:やならないな😒。

➐ 以上

webアプリを作ってもlocalhostのみで運用していては、なんか夢がないですよね。やっぱり自分が考えたwebアプリが全世界の人々が使えるというワクワクをHerokuは叶えてくれます。ただ、どうやって全世界の人々へ使って貰えるように発信していくのかは別問題ですが…自信を持って本当に良いサービスであれば、SNSなども利用して世界へ発信していくと良いかもしれませんね。

お疲れ様でした!

7
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
14