Pythonだけで作る！手軽に動くWebアプリを公開するプラットフォームの作り方

## 0. はじめに
この記事は [BrainPad Advent Calender 2023](https://qiita.com/advent-calendar/2023/brainpad) 15日目の記事です。

はじめまして。
株式会社ブレインパッド アプリケーション開発ユニットの [@atsushi_takamiya](https://qiita.com/atsushi_takamiya) と [@satoshi-goto](https://qiita.com/satoshi-goto) です。

本記事は、新卒でBPにASEとして入社１・２年目の２人で開発したアプリの環境構築と概要についてまとめた記事になります。

## 1. 概要
本記事で紹介するDokkuを利用したDash民主化アプリは、個人開発者が手軽にDashダッシュボードにアプリを乗せ、社内で公開することを可能にします。このアプリでは、Dockerfileを使用せず、シンプルなプッシュ操作だけでアプリを社内公開できるため、開発者は手間をかけずに自身のDashアプリを社内で共有できます。

BPからDashを用いた[新サービス「FAST」](https://www.brainpad.co.jp/services/professionals/bp-fast.html)が登場しました。これに伴い、社内のデータサイエンティストにDashの利用を促進する取り組みの一つとしてDash民主化アプリを作製しました。このアプリは、個人開発者が作成したDashアプリを社内で手軽に公開するためのニーズに応えることを目指しています。

将来的に、Dash民主化アプリは重要な役割を果たし、まず新卒研修においては、アプリを活用して新入社員にDASHの効果的な利用方法を教育することが期待されます。同時に、アプリを通じて技術共有を推進し、社内での円滑な情報共有を促進することで、同僚やチームメンバーがアイディアやデータをより簡単かつ効果的に共有できる環境が整備されるでしょう！　

## 2. 使用した技術
次に、DASH民主化アプリの主要技術について解説します。
図1はDASH民主化アプリの構造です。
DASH民主化アプリは、ユーザーが画面上で操作（Dokkuアプリの作成など）をすると、FastAPIがVMホスト上でDokkuコマンドを実行し、Dokkuアプリの管理ができる構造になっています。

![イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616103/6125a84e-4e7f-d880-eb9b-3b8deb408611.png)
図1. DASH民主化アプリの構造
<br>

- Dash
  - DashはPythonのウェブアプリケーションフレームワークで、PlotlyやFlaskを基にした直感的な構文で、データ可視化やダッシュボードを簡単かつ迅速に作成できます。
  参考: [Dash Documentation & User Guide | Plotly](https://dash.plotly.com/)
  - Dash民主化アプリも、Dashを利用して登録されたアプリの一覧・アクセスできるダッシュボードの作成を行いました。
  参考: [dash-app-gallery | GitHub](https://github.com/AnnMarieW/dash-app-gallery)
  <br>
- FastAPI
  - FastAPIは、Pythonのモダンかつ高性能なAPI開発フレームワークであり、型ヒントと自動生成されるAPIドキュメントにより迅速な開発と高い可読性を提供します。async/await構文をサポートし、高速な実行性能と豊富な機能セットを備えています。
  参考: [FastAPI](https://fastapi.tiangolo.com/)
  - Dash民主化アプリからdokkuの操作を行うために利用しています。詳細は、後の環境構築を参照。
  参考: [DokkuAPI | GitHub](https://github.com/nunombispo/DokkuAPI)
  <br>
- Dokku
  - Dokkuは、Heroku風のプラットフォームを提供するオープンソースのPaaS（Platform as a Service）ツールです。Dokkuを使用すると、簡単かつ迅速に自己ホスト型のクラウドプラットフォームをセットアップでき、さまざまなアプリケーションやサービスをデプロイすることができます。 Gitを利用してコードのデプロイを行うことができ、専門的なインフラストラクチャの知識が不要な点が特徴です。
  参考: [Dokku - The smallest PaaS implementation you've ever seen](https://dokku.com/)
  - Dash民主化アプリは、軽量さとシンプルな使い易さが求められていたため、アプリの登録・管理にDokkuを利用しています。

## 3. アーキテクチャ
図２はDASH民主化アプリのアーキテクチャを表しています。
以下の手順でDokkuアプリを作成するリクエストを処理します。

![アーキテクチャ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616103/1a707e91-b74f-2861-656a-7dda031efd62.png)
図2. Dokkuアプリを作成するリクエストの処理フロー
<br>

1. アプリ登録
    ユーザーが画面上でアプリを登録すると、DashアプリがFastAPIにDokkuアプリを作成するリクエストを送信します。
        <br>
1. リクエスト
    FastAPIがDashアプリからリクエストを受信すると、VMホスト上でDokkuアプリを作成するDokkuコマンドを実行します。
    <br>
1. アプリ作成
    VMホスト上でDokkuアプリを作成するDokkuコマンドを実行され、Dokkuアプリが作成されます。
    <br>
1. ソースコードのpush
    ユーザーがDokkuアプリのリポジトリにソースコードをpushし、アプリが起動します。


## 4. 環境構築
GCPのCompute Engineを使用して環境を構築します。
Dash民主化アプリでは、Compute EngineのVMインスタンス上にDokku環境を構築しています。

### Compute Engineの設定
1. VMインスタンスの立ち上げ
    GCPコンソール画面からVMインスタンスを立ち上げます。
    参考: [VMインスタンスの作成と開始 | Compute Engine のドキュメント | Google Cloud](https://cloud.google.com/compute/docs/instances/create-start-instance?hl=ja)
    <br>
1. IPアドレスの設定
    DNSの設定で使用するIPアドレスを発行します。
    参考: [静的外部 IP アドレスの予約 | Compute Engine のドキュメント | Google Cloud](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address?hl=ja)
    <br>
1. DNSの設定
    Cloud DNSを使用してドメインを取得します。
    参考: [Cloud DNS を使用してドメイン名の DNS レコードを設定する | Cloud DNS のドキュメント | Google Cloud](https://cloud.google.com/dns/docs/set-up-dns-records-domain-name?hl=ja)

### Dokkuのインストール

1. Dokkuの初期設定
    以下のコマンドを実行して先ほどCloud DNSで取得したドメインを設定します。
    ```
    dokku domains:set-global {ドメイン名}
    ```
    <br>
1. APIキーの作成
    バックエンド（FastAPI）でDokkuコマンドを実行するので、APIキーを作成します。
    参考: [APIキーを作成する | ドキュメント | Google Cloud ](https://cloud.google.com/docs/authentication/api-keys?hl=ja#:~:text=API%20%E3%82%AD%E3%83%BC%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B,-API%20%E3%82%AD%E3%83%BC%E3%82%92&text=Google%20Cloud%20%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E3%81%A7%E3%80%81%5B%E8%AA%8D%E8%A8%BC,%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AB%E7%A7%BB%E5%8B%95%E3%81%97%E3%81%BE%E3%81%99%E3%80%82&text=%5B%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%81%AE%E4%BD%9C%E6%88%90%5D%20%E3%82%92,%E5%88%97%E3%81%8C%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82)


### cronの設定
コンテナをビルドした時に、コンテナイメージが`/var/lib/docker/overlay2/`以下にイメージレイヤー単位で保存されます。dokkuアプリにソースコードをpushした時やvmインスタンスを起動した時などに`/var/lib/docker/overlay2/`以下に不要なコンテナイメージが蓄積してしまいます。
cronで不要なキャッシュを削除するスクリプトを定期実行することでこの問題を解決できます。
1. script.shの作成
    使用していないコンテナのキャッシュを削除するコマンドを記載します。
    ```linux:script.sh
    /usr/bin/docker system prune -a --force
    ```
    <br>
1. cronジョブの設定
    `crontab -e`でcronの設定をします。
    ```
    crontab -e
    ```
    タスクを実行するタイミングを以下の5項目（数字）で設定します。
    `*`は全ての範囲を表します。
    以下の例は、毎日8:00に`script.sh`を実行するように設定しています。
    - 分: 0 ~ 59
    - 時: 0 ~ 23
    - 日: 1 ~ 31
    - 月: 1 ~ 12
    - 曜日: 0 ~ 7
    ```
    # 分 時 日 月 曜日
    0 8 * * * bash script.sh
    ```
    <br>
1. タスク一覧の表示
    設定したcronジョブを確認することができます。
    ```
    crontab -l
    ```

### ディレクトリマッピングの設定
アプリの登録内容を保存するために、VMホスト上の永続ディレクトリをマウントします。
参考: [Persistent Storage - Dokku Documentation](https://dokku.com/docs/advanced-usage/persistent-storage/)
1. `var`ディレクトリのマウント
    以下のコマンドを実行して永続ディレクトリ`var`をマウントします。
    ```
    dokku storage:mount {アプリ名}
    ```
    <br>
1. マウント先一覧の表示
    以下のコマンドを実行してマウント先を確認できます。
    ```
    storage:report {アプリ名}
    ```



### Dokkuアプリの管理（DASH民主化アプリの作成）
以下の手順で、画面上でDokkuアプリを管理することができます。
1. DokkuコマンドAPIの作成
    VMホスト上でDokkuコマンドを実行するためにFastAPIでDokkuコマンドAPIを作成します。DashアプリでDokkuコマンドを使用できるようになります。
     参考: [DokkuAPI | GitHub](https://github.com/nunombispo/DokkuAPI)
     <br>
1. Dokkuアプリの作成
    DashアプリとFastAPI用にそれぞれDokkuアプリを作成します。
    参考: [アプリケーション管理 | Dokku のドキュメント](https://dokku.com/docs/deployment/application-management/)
     ```
     # Dokkuアプリの作成
     dokku apps:create {Dokkuアプリ名}
     ```
     <br>
1. それぞれのDokkuアプリにソースコードをpushする
    ローカルにDokkuアプリのリポジトリを追加してソースコードをpushします。
    ```
    # リポジトリの追加
    git remote add dokku dokku@{Dokkuサーバー}:{Dokkuアプリ名}

    # ソースコードのpush
    git push dokku
    ```

## 5. デモンストレーション
DASH民主化アプリに`sample-app`という名前でアプリを作成してみます。
（※プライバシーの問題で画像の一部を隠しています。）
1. アプリの登録
    ユーザーは画面上で必要な項目を入力してアプリの登録を行います。
    
    ![アプリ登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616103/86969712-c590-347e-8087-2b8e6c06f62b.png)
    図3. アプリ登録画面
    <br>
    アプリを登録できていることが確認できました🎉
    
    ![アプリ一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616103/dfc0eae7-52f5-5cf0-610d-ef98c836f27f.png)
    図4. アプリ一覧
    <br>

1. ソースコードのpush
    アプリを登録した後、ローカルからDokkuアプリのリポジトリにソースコードをpushします。
    ```
    # リポジトリの追加
    git remote add dokku dokku@{Dokkuサーバー}:sample-app
    
    # ソースコードのpush
    git push dokku
    ```
    <br>
1. アプリの閲覧
    DASH民主化アプリから登録したアプリを閲覧します。
    無事、ソースコードをpushでき、アプリが起動していることを確認できました🎉
    
    ![sample-app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616103/b151ceb7-9bbf-ce9e-1fc5-c3144fc21356.png)
    図5. sample-app



## 6. おまけ

### Artifact Registoryの設定
GCPのArtifact Registoryを使用してPythonパッケージの管理する方法です。有償のパッケージなどをセキュアに管理することができます。
1. 認証情報を設定する（推奨: キーリングを使用した認証）
    Artifact Registoryを使用するために、まずは認証情報の設定をします。GCPのドキュメントでは、キーリングを使用した認証が推奨されています。
    参考: [Python パッケージ リポジトリに対する認証の設定 | Artifact Registry のドキュメント | Google Cloud](https://cloud.google.com/artifact-registry/docs/python/authentication?hl=ja#keyring)
    <br>
1. Artifact Registoryにリポジトリを作成する
    Pythonパッケージを保存するリポジトリを作成します。
    参考: [標準リポジトリを作成する | Artifact Registry のドキュメント | Google Cloud](https://cloud.google.com/artifact-registry/docs/repositories/create-repos?hl=ja)
    <br>
1. ローカルPCからPythonパッケージをアップロードする（例: twine）
    ローカルPCのPythonパッケージをArtifact Registoryにアップロードする。
    ```
    python3 -m twine upload --repository-url {リポジトリのURL} {ファイルのパス}
    ```
    <br>
1. requirements.txtの書き方
    Artifact Registoryから優先的にPythonパッケージを使用するようにする。
    `{key}`にはサービスアカウントのjsonキーをダウンロードして、base64コマンドでエンコードしたものを入力する。
    参考: [Python パッケージ リポジトリに対する認証の設定 | Artifact Registry のドキュメント | Google Cloud](https://cloud.google.com/artifact-registry/docs/python/authentication?hl=ja)
    ```text:index.txt
    --index-url {key}
    --extra-index-url https://pypi.python.org/simple
    ```
    ```text:requirement.txt
    -r index-url.txt
    
    {pythonパッケージ} == {バージョン}
    ...
    ```

## 7. おわりに
社内では、データサイエンティストの方々が続々とdashアプリを登録してくださっています。
まだまだ課題は山積みですが、より多くの方々に使っていただけるよう尽力していきます！

ここまで読んでいただきありがとうございました！

## 8. 参考
[新サービス「FAST」](https://www.brainpad.co.jp/services/professionals/bp-fast.html)
[Dash Documentation & User Guide | Plotly](https://dash.plotly.com/)
[dash-app-gallery | GitHub](https://github.com/AnnMarieW/dash-app-gallery)
[FastAPI](https://fastapi.tiangolo.com/)
[DokkuAPI | GitHub](https://github.com/nunombispo/DokkuAPI)
[Dokku - The smallest PaaS implementation you've ever seen](https://dokku.com/)
[VMインスタンスの作成と開始 | Compute Engine のドキュメント | Google Cloud](https://cloud.google.com/compute/docs/instances/create-start-instance?hl=ja)
[静的外部 IP アドレスの予約 | Compute Engine のドキュメント | Google Cloud](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address?hl=ja)
[Cloud DNS を使用してドメイン名の DNS レコードを設定する | Cloud DNS のドキュメント | Google Cloud](https://cloud.google.com/dns/docs/set-up-dns-records-domain-name?hl=ja)
[APIキーを作成する | ドキュメント | Google Cloud ](https://cloud.google.com/docs/authentication/api-keys?hl=ja#:~:text=API%20%E3%82%AD%E3%83%BC%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B,-API%20%E3%82%AD%E3%83%BC%E3%82%92&text=Google%20Cloud%20%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E3%81%A7%E3%80%81%5B%E8%AA%8D%E8%A8%BC,%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AB%E7%A7%BB%E5%8B%95%E3%81%97%E3%81%BE%E3%81%99%E3%80%82&text=%5B%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%81%AE%E4%BD%9C%E6%88%90%5D%20%E3%82%92,%E5%88%97%E3%81%8C%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82)
[Python パッケージ リポジトリに対する認証の設定 | Artifact Registry のドキュメント | Google Cloud](https://cloud.google.com/artifact-registry/docs/python/authentication?hl=ja#keyring)
[標準リポジトリを作成する | Artifact Registry のドキュメント | Google Cloud](https://cloud.google.com/artifact-registry/docs/repositories/create-repos?hl=ja)
[Python パッケージ リポジトリに対する認証の設定 | Artifact Registry のドキュメント | Google Cloud](https://cloud.google.com/artifact-registry/docs/python/authentication?hl=ja)
[アプリケーション管理 | Dokku のドキュメント](https://dokku.com/docs/deployment/application-management/)
[Persistent Storage - Dokku Documentation](https://dokku.com/docs/advanced-usage/persistent-storage/)