初心者がstreamlitを使って採点アプリを作ってみた#6

<!-- 目次部分(リンクになるところ) -->
# 目次
1. [はじめに](#anchor1)
1. [アプリデプロイまでの流れ](#anchor2)
1. [Google Cloud Buildを利用する準備](#anchor3)
    1. [Dockerイメージのビルド]
    1. [Dockerイメージのプッシュ]
    1. [Cloud Runへのデプロイ]
    1. [環境変数の設定]
    1. [imagesセクション]
1. [Dockerfileの作成](#anchor4)
    1. [ベースイメージの指定]
    1. [環境変数の設定]
    1. [必要なシステムパッケージのインストール]
    1. [作業ディレクトリの設定]
    1. [必要なファイルのコピーと依存関係のインストール]
    1. [ソースコードのコピー]
    1. [ポートの公開]
    1. [Streamlitアプリケーションの実行]
1. [ファイル構成](#anchor5)
1. [終わりに](#anchor6)

<a id="anchor1"></a>
# はじめに
streamlitを使って、採点アプリを作ってみた記録の第６回目の投稿になります。
前回までの記事が気になった方は

第１回：<https://qiita.com/0dn09g3y726519/items/d78397d3c174f4388687>
第２回：<https://qiita.com/0dn09g3y726519/items/e95bae2595b951ddbecc>
第３回：<https://qiita.com/0dn09g3y726519/items/6fc96e928c4c910d3ed9>
第４回：<https://qiita.com/0dn09g3y726519/items/a4230330eed6a7359c1d>
第５回：<https://qiita.com/0dn09g3y726519/items/c40aff0cbfd5669b2af7>

こちらをご覧ください。
下図のような構成のアプリを作っています。

###
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821078/62a8f101-668e-21a8-4eb6-a02ebee17c33.png)


3回目までの記事で機能の紹介が終わりましたので、今回のアプリ制作活動の中で一番苦労したデプロイについてご紹介したいと思います。サーバーって何？ってレベルの初心者が、いろいろ調べながら頑張ってCI/CDができる環境を作って、アプリのデプロイまでたどり着きましたので、今後どなたかの参考になれば幸いです。
（初心者ゆえおそらく間違っているところも多々あると思いますので、間違いに気づかれた方はぜひコメントください。勉強させていただきたいと思います。）

前回までで、googlecloudとgithubの連携が無事に終わりましたので、ここからgoogle cloud build, runを使用してstreamlitで作成したアプリをデプロイしていこうと思います！！

<a id="anchor2"></a>
# アプリデプロイまでの流れ
アプリをデプロイするまでの流れを簡単に図にしてみました。
（間違っていたら、すみません。コメントでご指摘ください。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821078/721558ef-e490-499e-b86c-33e8ecc7166c.png)
アプリの流れとしては、
①：streamlitのコード群をgithubにpushすると、git hub actionsにmain.ymlが指示を出して、google cloud buildでアプリをデプロイするようにします。

②google cloud buildは指示を受けて、cloudbuild.yamlの記載に従い、Dcokerfileをビルドして、イメージをgoogle cloud runが使用できるように、google cloud内にでpushします。(requirements.txtもここで実行環境を整えるために、使用されます。)

③google cloud runはイメージをもとに、アプリをデプロイして、だれでも見えるURLを出力します。


これまでの記事の中で、streamlitのコード群・main.yml・requirements.txtは作成してきましたので、あとはcloudbuild.yaml, Dockerfileを作成すれば、デプロイができるようになります。

<a id="anchor3"></a>
# Google Cloud Buildを利用する準備
google cloud buildを使用して、Docker file, requirements.txtからイメージをビルドして、streamlitの実行環境を作成してもらいます。そのための指示をgoogle cloud buildに与えるため、cloudbuild.yamlというフ
ァイルを作成していきます。

先に全体のコードを記載しておきます。

```php:cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/streamlit-app', '.']
    dir: 'src'

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/streamlit-app']

  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'streamlit-app'
      - '--image'
      - 'gcr.io/$PROJECT_ID/streamlit-app'
      - '--platform'
      - 'managed'
      - '--region'
      - 'asia-east1'
      - '--allow-unauthenticated'
      - '--set-env-vars'
      - 'AUTH_USER=${_AUTH_USER},AUTH_PASSWORD=${_AUTH_PASSWORD}'
      - '--memory'
      - '8Gi'
      - '--cpu'
      - '2'
      - '--max-instances'
      - '1'
      - '--min-instances'
      - '0'
    env:
      - '_AUTH_USER=${_AUTH_USER}'
      - '_AUTH_PASSWORD=${_AUTH_PASSWORD}'

images:
  - 'gcr.io/$PROJECT_ID/streamlit-app'
```
ここからそれぞれ解説していきます。

### 1.Dockerイメージのビルド
ここでは、ビルドとデプロイのための一連のコマンドを指定しています。
```php:cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/streamlit-app', '.']
    dir: 'src'
```
- name: どのビルダー（今回はdocker）を使うかを指定します。
- args: Dockerコマンドの引数を定義しています。buildはイメージをビルドするためのコマンドです。-tはイメージのタグ付けオプションで、gcr.io/$PROJECT_ID/streamlit-appという名前でタグ付けします。
- dir: Dockerfileがあるディレクトリ（src）を指定しています。

### 2.Dockerイメージのプッシュ
ビルドしたイメージをgoogle container registryにプッシュします。これによって、ほかのgoogle cloud runでイメージを使用できるようになります。

```php:cloudbuild.yaml
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/streamlit-app']
```

### 3.Cloud Runへのデプロイ
gcloudコマンドを使用して、ビルドしたイメージをCloud Runにデプロイしています。
```php:cloudbuild.yaml
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'streamlit-app'
      - '--image'
      - 'gcr.io/$PROJECT_ID/streamlit-app'
      - '--platform'
      - 'managed'
      - '--region'
      - 'asia-east1'
      - '--allow-unauthenticated'
      - '--set-env-vars'
      - 'AUTH_USER=${_AUTH_USER},AUTH_PASSWORD=${_AUTH_PASSWORD}'
      - '--memory'
      - '8Gi'
      - '--cpu'
      - '2'
      - '--max-instances'
      - '1'
      - '--min-instances'
      - '0'

```
- run deploy: Cloud Runにアプリケーションをデプロイするコマンドです。
- streamlit-app: デプロイするサービス名を指定します。
- --image: 使用するDockerイメージを指定します。
- --platform: Cloud Runの「マネージド」環境を指定します。
- --region: デプロイ先のリージョンを指定します。ここではasia-east1です。
- --allow-unauthenticated: 認証なしでアクセスを許可します。
- --set-env-vars: 環境変数を指定します。ここでは認証用のユーザー名とパスワードを設定しています。
- --memory: メモリの割り当て（8Gi）を指定しています。
- --cpu: 割り当てるCPU数を指定しています。
- --max-instances: 最大インスタンス数を1に制限しています。
- --min-instances: 最小インスタンス数を0に設定し、リクエストがないときにインスタンスをゼロにします。

今回は使用する人が限られているため、最大インスタンス数は１にしました。また、最小インスタンス数は0にしないと常時アプリが起動してしまい、その分コストが高くなります。
私はこれで数千円損しました。常時起動が必要なければ、最小インスタンス数は０にしましょう。
メモリに関して、今回LLMを使用していて、アプリ立ち上げ時にgoogle cloudからLLMのggufファイルを展開しているため、8Gと大きいメモリを使用しています。この部分は用途に合わせて、カスタマイズください。


### 4.環境変数の設定
Cloud Runにデプロイする際に、環境変数を設定しています。これらはデプロイ時に設定される値で、AUTH_USERとAUTH_PASSWORDが含まれています。

```php:cloudbuild.yaml
    env:
      - '_AUTH_USER=${_AUTH_USER}'
      - '_AUTH_PASSWORD=${_AUTH_PASSWORD}'
```

### 5.imagesセクション
ここでは、Google Cloud Buildが作成したDockerイメージをCloud Buildコンソールに記録しています。これにより、後でイメージを参照したり再利用したりできます。

```php:cloudbuild.yaml
images:
  - 'gcr.io/$PROJECT_ID/streamlit-app'
```

これでcloudbuild.yamlを使用して、アプリをGoogle Cloud Buildでデプロイする準備が完了しました。
次に、アプリの実行環境を整えるために、Dockerfileを作成していきます。

先にコードの全体を載せます。

<a id="anchor4"></a>
# Dockerfileの作成
Google Cloud Buildが作成するイメージの元となるDockerfileを作成していきます。
```php:Dockerfile
# ベースイメージ
FROM python:3.12.3-slim-bookworm

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# 環境変数にポートを設定
ENV PORT 8080
# 環境変数にユーザー認証情報を設定
ENV AUTH_USER=${_AUTH_USER}
ENV AUTH_PASSWORD=${_AUTH_PASSWORD}

# 必要なシステムパッケージをインストール
RUN apt-get update && apt-get install -y \
    libjpeg-dev \
    zlib1g-dev \
    build-essential \
    cmake \
    libgl1-mesa-glx \
    libglib2.0-0 \
    python3-dev \
    git \
    && rm -rf /var/lib/apt/lists/*

# 作業ディレクトリの設定
WORKDIR /src

# 必要なファイルをコピー
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# ソースコードをコピー
COPY . .

# ポートの公開
EXPOSE 8080


# ストリーミングAPIアプリケーションの実行
CMD ["sh", "-c", "streamlit run streamlit_app.py --server.port=${PORT} --server.address=0.0.0.0"]
```
ここからそれぞれ解説していきます。

### 1.ベースイメージの指定
```php:Dockerfile
FROM python:3.12.3-slim-bookworm
```
- FROM: Pythonのスリムなイメージを使っています。このイメージは、余分なパッケージが含まれておらず、軽量で効率的です。
- slim-bookworm: Debianベースの軽量イメージで、基本的な依存関係をインストールできます。

### 2.環境変数の設定
```php:Dockerfile
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PORT 8080
ENV AUTH_USER=${_AUTH_USER}
ENV AUTH_PASSWORD=${_AUTH_PASSWORD}
```
- PYTHONDONTWRITEBYTECODE: Pythonが.pycファイルを生成しないようにする環境変数です。不要なファイルの生成を防ぎます。
- PYTHONUNBUFFERED: Pythonの出力をバッファリングせず、リアルタイムで出力を表示する設定です。ログをリアルタイムで見るために役立ちます。
- PORT: Streamlitアプリが動作するポート番号を設定しています（Google Cloud Runでデフォルトでポート8080を使用）。
- AUTH_USERとAUTH_PASSWORD: ユーザー認証のための環境変数です。これにより、認証を組み込んだアプリケーションのデプロイが可能です。

### 3.必要なシステムパッケージのインストール
```php:Dockerfile
RUN apt-get update && apt-get install -y \
    libjpeg-dev \
    zlib1g-dev \
    build-essential \
    cmake \
    libgl1-mesa-glx \
    libglib2.0-0 \
    python3-dev \
    git \
    && rm -rf /var/lib/apt/lists/*

```
- apt-get update: パッケージリストの更新。
- libjpeg-dev, zlib1g-dev: 画像関連の処理に必要なライブラリ。
- build-essential, cmake: コンパイルツールとCMake。もしPythonパッケージにCやC++で書かれた拡張機能が含まれていれば、これが必要になります。
- libgl1-mesa-glx, libglib2.0-0: 一部の画像処理やGUIアプリケーションで必要なライブラリ。
- python3-dev: Pythonの開発関連のパッケージで、C拡張モジュールをビルドするために必要です。
- git: ソースコード管理のためにGitをインストールしています。

### 4.作業ディレクトリの設定
```php:Dockerfile
WORKDIR /src
```
- WORKDIR: コンテナ内での作業ディレクトリを/srcに設定しています。ここにソースコードや依存関係が置かれます。

### 5.必要なファイルのコピーと依存関係のインストール
```php:Dockerfile
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
```
- COPY requirements.txt ./: requirements.txtをコンテナ内の作業ディレクトリ（/src）にコピーします。
- RUN pip install: requirements.txtに記載されたPythonパッケージをインストールします。--no-cache-dirを使うことで、キャッシュを残さず軽量なイメージにします。

### 6.ソースコードのコピー
```php:Dockerfile
COPY . .
```
- カレントディレクトリ（ホスト側）にあるすべてのファイルをコンテナの作業ディレクトリにコピーします。

### 7.ポートの公開
```php:Dockerfile
EXPOSE 8080
```
- コンテナが使用するポート番号8080を公開します。Cloud Runでは、このポートがデフォルトで使用されます。

### 8.Streamlitアプリケーションの実行
```php:Dockerfile
CMD ["sh", "-c", "streamlit run streamlit_app.py --server.port=${PORT} --server.address=0.0.0.0"]
```
- CMD: コンテナが起動したときに実行されるコマンドです。Streamlitアプリを指定されたポート（PORT環境変数）で実行します。
- --server.address=0.0.0.0: これにより、外部からアクセス可能なアドレスでアプリを公開します。

これで必要なファイルはすべて、作成が完了しました。
GitHubに必要なファイルをプッシュすることで、自動でアプリがデプロイされるはずです。
最後に、ファイルの構成を記載しておきます。

<a id="anchor5"></a>
# ファイル構成
```docker
Test_app/
│
├── .github/
│   └── workflows/
│       └── main.yml
│
├── src/
│   ├── pages/
│   │   ├── first_page.py
│   │   └── second_page.py
│   │
│   ├── streamlit_main.py
│   ├── Dockerfile
│   └── requirements.txt
│
└── cloudbuild.yaml
```
このようなファイル構成になっています。
この状態を作成して、ファイルの中身を記述して、google cloud, githubでの設定を過去の記事通りきちんと行えば、githubにpushするだけで、自動でgit hub actionsが回り、アプリをデプロイしてくれます。
今後アプリにアップデートを行う場合も、変更箇所をgitにあげれば、再度検証して自動でデプロイしてくれます。
ぜひ自身の環境でお試しください。

<a id="anchor6"></a>
# 終わりに
ここまでお付き合いくださり、ありがとうございました。
アプリの作成からデプロイまで、右も左もわからない状態からなんとかやり遂げることができました。
いろいろと誤った情報を載せてしまったかもしれませんので、間違いがあればコメントでなんでもご指摘ください。

これからも記事を書き続けていこうと思うので、どなたかの参考になれば幸いです。