Flask + HerokuでLINE Botを開発 (Dockerを使用したデプロイ編)

# はじめに
- 本記事では、事前にビルドされたDockerイメージをHerokuにデプロイする作業を行った後に、heroku.ymlを作成し、Heroku で Docker イメージをビルドさせる。
- LINE Developersへの登録や、Herokuへの登録は既に完了しているものとする。もし、済んでいない方がいれば、[こちら](https://qiita.com/Sosuke1019/items/2a1d03f93e24a237796f)に概要や参考ページを説明しているので参考にしてみてください。

## 基礎知識
- Dockerを使ったHerokuのデプロイ方法は2つある。
    1. Container Registryを使用し、事前にビルドされたDockerイメージをHerokuにデプロイする
    1. heroku.ymlを使用してHerokuでDockerイメージをビルドし、Herokuにデプロイする

## 環境
- Docker
- git
- Python
- Flask==2.2.3
- line-bot-sdk==2.4.2
- gunicorn==20.1.0

## 手順
1. アプリケーション(LINE Bot）の準備
1. Heroku Container Registryで、ビルド済みのDocker イメージを Heroku にデプロイする。
    2-1. コンテナレジストリにログイン
    2-2. アプリのディレクトリに移動し、Herokuアプリを作成する
    2-3. イメージをビルドし、Container Registry にプッシュする
    2-4. イメージをアプリにリリースする
    2-5. "Messaging API設定"画面から検証する
1. heroku.ymlを使用してHerokuでDockerイメージをビルドし、Herokuにデプロイする
    3-1. ルートディレクトリに`heroku.yml` ファイルを作成する
    3-2. ファイルを自分のリポジトリにコミットする
    3-3. アプリのスタックを`container`に設定する
    3-4. アプリをHerokuにプッシュする



# 1. アプリケーション(LINE Bot）の準備
## ディレクトリ構成
> sample-app/
　├ lineapp/
　│　　　├app.py
　│　　　├requirements.txt
　│　　　└wsgi.py
　├ .dockerignore
　└ Dockerfile
## サンプルコード
以下のコードをコピペしてください。
```python:app.py
import os
from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)

#環境変数取得
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]

line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)

@app.route("/")
def hello_world():
    return "hello world!"

@app.route("/callback", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))

if __name__ == "__main__":
#    app.run()
    port = int(os.getenv("PORT"))
    app.run(host="0.0.0.0", port=port)
```
- app.py：今回は、https://github.com/line/line-bot-sdk-python にあるオウム返しを行うシンプルなLINE Botを使用する。少し変更を加えた上記のコードをコピペすれば上手く動く。

```python:requirements.txt
Flask==2.2.3
line-bot-sdk==2.4.2
openai==0.27.4 
gunicorn==20.1.0
```
- requirementx.txt：プロジェクトの依存関係を一覧にしたテキストファイル。このファイルに必要なパッケージとそのバージョンを記述する。

```python:wsgi.py
from app import app as application
```
- wsgi.py：PythonのWebアプリケーションで使用されるWSGIサーバーへのエントリーポイント。
具体的には、app.pyファイルから'app'というモジュールを読み込み、WSGIアプリケーションオブジェクトを作成し、WSGIサーバー(今回はgunicorn)を起動する役割を果たしている。これにより、Webアプリケーションが実際に動作し、HTTPリクエストを処理し、応答を返すことができる。
※WSGIとは、WebサーバーとWebアプリケーション間の標準化されたインターフェースであり、WebアプリケーションがWebサーバーと連携できるようにする。

``` dockerfile:Dockerfile
#Grab the latest alpine image
FROM alpine:latest

# Install python and pip
RUN apk add --no-cache --update python3 py3-pip bash
ADD ./lineapp/requirements.txt /tmp/requirements.txt

# Install dependencies
RUN pip3 install --no-cache-dir -q -r /tmp/requirements.txt

# Add our code
ADD ./lineapp /opt/lineapp/
WORKDIR /opt/lineapp

# Expose is NOT supported by Heroku
# EXPOSE 5000 		

# Run the image as a non-root user
RUN adduser -D myuser
USER myuser

# Run the app.  CMD is required to run on Heroku
# $PORT is set by Heroku			
CMD gunicorn --bind 0.0.0.0:$PORT wsgi 
```
- Dockerfile：Dockerイメージのビルド手順を記述するためのファイル。指定された手順に従って、ベースイメージをベースに新しいイメージが構築される。ビルドプロセスでは、ベースイメージの上に必要なファイルがコピーされ、必要なパッケージがインストールされ、最終的に実行するコマンドが指定される。これにより、Dockerイメージ内でアプリケーションを実行するための環境が構築される

```.dockerignore
.git/
```
- .dockerignore：Dockerイメージのビルド時に無視するファイルやディレクトリを指定するための設定ファイル。.dockerignoreは.gitignoreのようにdockerで無視するファイルを指定することができる。



# 2. Heroku Container Registryで、ビルド済みのDockerイメージを Heroku にデプロイする
はじめに、Dockerのインストール（例：docker ps）とHerokuへのログイン（heroku login）を確認する。
```
$ docker ps
$ docker login
```

## 2-1. コンテナレジストリにログイン
```
$ heroku container:login
```

## 2-2. アプリのディレクトリに移動し、Herokuアプリを作成する
今回は“sample-app” という名前の新しいアプリを作成するため、`heroku create sample-app`を実行する。違うアプリ名で作成したい場合、"sample-app"の部分を変更する(例. heroku create hoge)。
```
$ cd sample-app
$ heroku create sample-app
```

## 2-3. イメージをビルドし、Container Registry にプッシュする
イメージをビルドしてContainer Registryにプッシュするには、ディレクトリにDockerfileが含まれていることを確認して、次のコマンドを実行する。
```
$ heroku container:push web
```

## 2-4. イメージをアプリにリリースする
Container Registryにイメージをプッシュした後、次のコマンドを使用して新しいリリースを作成できる。
```
$ heroku container:release web
```
## 2-5. "Messaging API設定"画面から検証する
検証して"成功"が出れば、デプロイの完了です!
※以下のことを行ってから検証してください。
- Herokuに環境変数の設定
    ```
    $ heroku config:set YOUR_CHANNEL_SECRET="チャンネルシークレットの文字列" --app sample-app
    $ heroku config:set YOUR_CHANNEL_ACCESS_TOKEN="アクセストークンの文字列" --app sample-app
    ```
- `https://sample-app.herokuapp.com`の後ろに`/callback`を付ける
- webhookの利用をオンにすること。
![Screenshot 2023-06-27 at 17-08-40 LINE Developers.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601652/1c203aae-95b7-6514-c401-dc5e50aac984.png)


# 3. heroku.ymlを使用してHerokuでDockerイメージをビルドし、Herokuにデプロイする
## 3-1. ルートディレクトリに`heroku.yml` ファイルを作成する
- heroku.ymlとDockerfileは同じディレクトリにあります。
- `web:`の部分で、アプリのwebプロセス用にビルドするDockerイメージを指定しています。
- ここでは、runセクションを含めずに、HerokuはDockerfileで指定したCMDを使用しています。
```yml:heroku.yml
build:
  docker:
    web: Dockerfile
```
## 3-2. ファイルを自分のリポジトリにコミットする
```
$ git add heroku.yml
$ git commit -m "Add heroku.yml"
```
## 3-3. アプリのスタックを`container`に設定する
```
$ heroku stack:set containe
```
## 3-4. アプリをHerokuにプッシュする
```
$ git push heroku master
```
ここまで問題なく出来たら、再度"Messaging API設定"画面から検証して見てほしい。"成功"が出れば問題なく、HerokuでDockerイメージをビルド出来ている。



# Docker関連の用語
- Dockerfile：テンプレートファイルを作るための設計書
- Dockerイメージ：Dockerコンテナの動作環境となるテンプレートファイル。Dockerコンテナを実行するためにはDockerイメージが必要。Dockerイメージはクラウド上のレジストリ「Docker Hub」からダウンロードする、もしくは自分で作成することもできる。
- Dockerコンテナ：テンプレートファイルに基づいてアプリケーションを実行する環境・インスタンス。1つのDockerイメージを実行すると1つのDockerコンテナが作成される。厳密にいうとDockerイメージを走らせる（run）ことによって実行環境であるDockerコンテナが生成され、アプリケーションが実行される。
- Registry：Dockerイメージの配布場所。Docker イメージのアップロードやダウンロードを行うことができる。Docker HubやQuay(キー）やDocker HubなどがRegistryにあたる。 

【Dockerコンテナを作成する手順】
1. Dockerfileを作成する。(Dockerイメージの設計図。)
1. Dockerfileをbuildすることで、Dockerイメージを作成する。(テンプレートファイル)
1. Dockerイメージをrun(実行可能な状態にする)して、Dockerコンテナを作成する。(アプリケーションの実行環境)
"
![Screenshot 2023-06-27 at 21-23-57 【入門】Dockerイメージ（images）の仕組みとコマンド一覧まとめ - カゴヤのサーバー研究室.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601652/2e6bb096-531d-5904-6032-c9d6a9b96f02.png)
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　[出典：カゴヤのサーバー研究室](https://www.kagoya.jp/howto/cloud/container/dockerimage/)



# おわりに 
　本記事では、まず初めにビルド済みのDockerイメージをHerokuにデプロイする方法について説明し、次にHerokuでDockerイメージをビルドし、デプロイする方法を説明した。私はチームでLINE Botを作成したため、Dockerでデプロイする方法を取った。それぞれ異なる環境で、複数のアプリケーションをインストールする必要がある開発だったため、コンテナ技術にはかなり助けられた。同じ境遇の方の参考になれば嬉しい。また、私が以前書いた、[こちらの記事](https://qiita.com/Sosuke1019/items/2a1d03f93e24a237796f)では、Gitを使用してLINE BotをHerokuにデプロイする方法を説明したので、こちらの方法も気になる方には是非参考にして欲しい。
　最後に、文章や説明等に不備がございましたら、教えて頂けますと幸いです。


# 参考記事
- [Dockerによるデプロイ_Heroku公式](https://devcenter.heroku.com/ja/categories/deploying-with-docker)
- [Container Registry および Runtime (Docker デプロイ)_公式サイト](https://devcenter.heroku.com/ja/articles/container-registry-and-runtime#logging-in-to-the-registry)
- [サンプルコード_Heroku公式](https://github.com/heroku/alpinehelloworld)
- [heroku.yml を使用して Docker イメージをビルドする
](https://devcenter.heroku.com/ja/articles/build-docker-images-heroku-yml)