Docker コンテナに Python ＋ Flask の Webアプリケーションを構築する

# Docker コンテナに Python ＋ Flask の Webアプリケーションを構築する
[前回の記事](https://qiita.com/Yu-Chihuahua/items/02cb5dfddb8b24393f14)で、Windows に Docker の開発環境を構築しました。
この環境を利用して、Python ＋ Flask の Webアプリケーションを構築していこうと思います。

## 今回のゴール
- Docker コンテナに Python ＋ Flask の Webアプリケーションを構築する
- Windows からローカルホストでアクセスして「Hello World」を表示する

## ディレクトリ構成
今回のディレクトリ構成は下記のようになります。
```web_app003```という名前で Webアプリケーションを構築するものとします。
なお、ディレクトリ作成や、ファイルを編集するコマンドは説明から省きます。
```diff_shell:WSL2 Ubuntu
  /home/appuser

  ├── workspace
  │   ├── docker-compose.yaml
+ │   └── web_app003
+ │       ├── Dockerfile
+ │       └── opt
+ │           ├── flask_start.sh
+ │           └── web_app
+ │               └── src
+ │                   ├── app_controller.py
+ │                   └── templates
+ │                       └── index.html
```

## 手順
### 資材作成
1. ```docker-compose.yaml```ファイルを修正
    ```docker-compose.yaml```ファイルを下記のように修正します。
    - ```web_app003```ディレクトリ配下の```Dockerfile```をビルドする
    - Flask アプリケーションを5000番ポートで公開する
    ```yaml:docker-compose.yaml
    version: '3'
    services:
    
      ##############################################################################
      # Web Application container（Python + Flask）
      ##############################################################################
      web_app003:
        build: "./web_app003/"
        tty: true
        ports:
          - "5000:5000" # Flask port
        image: "web_app003:develop"
        container_name: "web_app003"
    ```
    <br>

1. ```Dockerfile```ファイルを作成
    ```Dockerfile```ファイルを下記のように作成します。
    - Amazon Linux 2023 をベースイメージとする
    - ミドルウェアをインストールする
    - Python のライブラリをインストールする
    - 資材を配置し、オーナーとパーミッションを変更する
    - 起動時のコマンド（Flask を起動するシェル）を設定する
    ```shell:Dockerfile
    # Amazon Linux 2023
    FROM public.ecr.aws/amazonlinux/amazonlinux:2023
    
    # ミドルウェアをインストール
    RUN dnf install -y \
        util-linux \
        shadow-utils \
        vim-enhanced-2:9.* \
        sudo-1.9.* \
        python3.11-3.11.* \
        python3.11-pip-22.3.* \
        python3.11-devel-3.11.*
    
    # Python のライブラリをインストール
    RUN pip3.11 install flask==3.0.*
    RUN pip3.11 install flask_socketio==5.3.*
    RUN pip3.11 install flask-cors==4.0.*
    RUN pip3.11 install flask-wtf==1.2.*
    
    # ホストOS と同じユーザーを追加する
    # 1000 : host OS $(id -g)
    RUN groupadd -g 1000 appuser
    # 1000 : host OS $(id -u)
    RUN adduser -m appuser -u 1000 -g appuser
    # Add SUDO group.
    RUN groupadd sudo
    # Add SUDO authority.
    RUN usermod -aG sudo appuser
    
    # 資材を配置する
    WORKDIR /opt/web_app/
    RUN mkdir -p /opt/web_app/
    COPY ./opt/web_app/ /opt/web_app/
    COPY ./opt/flask_start.sh /opt/
    
    # オーナーとパーミッションを変更する
    RUN chown appuser:appuser /opt/web_app -R
    RUN chmod 775 /opt/web_app -R
    
    # 起動時のコマンドを設定する
    CMD ["sh","/opt/flask_start.sh"]
    ```
    <br>

1. Flask 起動シェルを作成
    Flask を起動するシェルを下記のように作成します。
    ```shell:flask_start.sh
    #!/bin/bash

    ################################################################################
    # Flask アプリケーションを起動するシェルスクリプト
    ################################################################################
    
    su -l appuser -c "cd /opt/web_app/src/; python3.11 app_controller.py"
    ```
    <br>

1. Flask のプログラムを作成
    Flask の Controller プログラムを下記のように作成します。
    ```python:app_controller.py
    from flask import Flask, render_template
    app = Flask(__name__, static_folder="/opt/web_app/src/templates/static/")
    
    @app.route("/")
    def index():
        """
        初期ページ表示
        """
        return render_template("index.html", name="Flask")
    
    if __name__ == "__main__":
        """
        Flask アプリケーション起動
        """
        app.run(debug=False, host="0.0.0.0", port=5000)
    ```
    <br>

1. Flask のテンプレートを作成
    Flask のテンプレートファイルを下記のように作成します。
    ```html:index.html
    <!DOCTYPE html>
    <html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Hello World</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <h1>こんにちは。{{name}} さん</h1>
    </body>
    </html>
    ```

### 動作確認
1. Docker Compose でビルド
    Docker Compose で Python ＋ Flask のカスタムイメージをビルドします。
    ```shell:WSL2 Ubuntu
    cd /home/appuser/workspace
    docker-compose build
    ```

    下記のように出力されれば成功です。
    ```shell:WSL2 Ubuntu
    [+] Building 24.3s (21/21) FINISHED                                                                     docker:default
     => [web_app003 internal] load build definition from Dockerfile                                                   0.0s
     => => transferring dockerfile: 1.14kB                                                                            0.0s
     => [web_app003 internal] load metadata for public.ecr.aws/amazonlinux/amazonlinux:2023                           0.8s
     => [web_app003 internal] load .dockerignore                                                                      0.0s
    ～～～～～～～～～～
     => [web_app003 16/16] RUN chmod 775 /opt/web_app -R                                                              0.3s
     => [web_app003] exporting to image                                                                               1.0s
     => => exporting layers                                                                                           1.0s
     => => writing image sha256:de46db898d41de76a65984e68d0490cee977fed692e7e455266c41d1b6d27232                      0.0s
     => => naming to docker.io/library/web_app003:develop
    ```
    <br>

1. Docker Compose で起動
    Docker Compose で Python ＋ Flask の Dockerコンテナを起動します。
    ```shell:WSL2 Ubuntu
    cd /home/appuser/workspace
    docker-compose up -d
    ```

    下記のように出力されれば成功です。
    ```shell:WSL2 Ubuntu
    [+] Building 0.0s (0/0)                             docker:default
    [+] Running 1/1
     ✔ Container web_app003  Started                            10.3s
     ```
    <br>

1. ローカルホストでアクセス
    Windows のブラウザからローカルホストでアクセスします。
    Flask は5000番ポートで公開しているので、URLは```http://localhost:5000/```となります。
    下記の画面が表示されれば成功です。
    ![スクリーンショット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3767768/0be93c48-dafc-fa28-c6f6-25b0fa5834b8.png)

## 最後に
ローカル開発環境を Docker コンテナで構築すれば、個人の OS などに依存せず、コマンド1つで環境構築をできるメリットがあると感じました。
更に、Docker Compose で PostgreSQL などのデータベースコンテナも横並びに立ち上げれば、ローカル開発環境でサーバ環境に近い単体テストを実施することも可能になり、開発効率が飛躍的にアップする可能性があります。