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からデプロイする方法としています。
Gitからデプロイをする場合は、足りないBuildPackを選んだり、そのための設定ファイルを作ったり、ビルド後のSlugサイズが500MiBまでと制約が多いです。
例えば、pipでInstallしたライブラリは、Defaultの「heroku/python
」というBuildPackでそのままInstall可能ですが、condaを使用してInstallしたライブラリを入れた場合は、Heroku Buildpacksから、conda
等で検索を実行し、別途必要なビルドパックを探し、そのための設定ファイルも用意する必要があります。
👧:制限がいっぱいあって、面倒くさそうね…
(2) Dockerでデプロイ
Dockerでデプロイする方法です。コンテナレジストリを使用する方法とheroku.ymlを使用する方法があります。ここではコンテナレジストリを使用する方法を説明します。
👧:こっちの方が、見た感じスマートね!
(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
というフレームワークを使用して作成されています。
(2) 要求事項
■要求事項
- python3.8+ (python or annaconda)
- git
- Docker / Docker desktop (about version 20.10.7)
- Heroku Account
- Heroku CLI
■コマンド確認
以下、コマンドが正しく使えるか試しにバージョンでも確認しておきましょう。
🤖:コマンドのパラメータは、大文字❛
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)まで順番に確認しながらやっていきましょう。
(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)
🤖: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
🤖: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アドレスからの受信を許可するという意味です。
# 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
🤖:dockerコンテナのサーバへは、アクセス出来たかな?
👧:またまた、できました🙆!ただ、dockerコマンドがパラメータが多くて、少々メンドイわね🙁ソースコード変更したりすると、何度も同じパラメータを打つことにならない?そーゆーの私、指が痛くてイヤなんですけど😠💨
🤖:コマンド打ちが、少し楽になる方法を後で教えます。とりあえず、次いきましょう。いよいよHerokuへデプロイですよ!
(4) Herokuへデプロイ
localhostでアクセスすることができたら、次はHerokuへデプロイしましょう!
(4-1) Dockerfile更新
Herokuの注意点ですが、『Web プロセスは、Herokuにより設定された「$PORT
」上でHTTPトラフィックをListenする必要があります。Dockerfile内のEXPOSEは考慮されません』とのことなので、Listen Portを「$PORT
」へ変更する必要があります。
# 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番
」を使うように、設定ファイルを作成します。
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なども利用して世界へ発信していくと良いかもしれませんね。
お疲れ様でした!