GoogleCloudのCI/CDを使った、サーバレスAPIのデプロイ

# はじめに
クラウド初心者がサーバレスAPIを構築するのに、めちゃくちゃ時間がかかってしまったので、備忘録として残します。
少しでも自分のような方の手助けになれたら幸いです。

# アーキテクチャ
* Python 3.10 (FastAPI 0.89.1)
* Cloud Run (コンテナ型サーバレスサービス)
* Cloud Build (CI/CD)
* ソース管理はGitHub

# やりたいこと
まずディレクトリ構成は以下のような感じ。
~~~
/ 
├ app/
│  ├ api/
│  │  └ 実際の処理
│  ├ database/
│  │  └ DB設定
│  ├ models/
│  │  └ DBモデル
│  ├ routers/
│  │  └ ルーティング
│  ├ schemas/
│  │  └ APIのレスポンスクラス
│  ├ utils/
│  │  └ 共通関数
│  └ main.py FastAPIをインスタンス化している
└ conf/
   ├ dev/
   │  └ 開発環境用設定ファイル
   └ prd/
      └ 本番環境用設定ファイル
~~~
当初はこれをCloudFunctionsにデプロイしようとしていましたが、
エントリポイントを指定しなければならないこと、そもそもFlask向きだったことが理由で、FastAPIが動かず…。
エントリポイントとは、実際に関数が動く場所のことでこれを指定することによりCloudFunctionsが動きます。
つまり、動く関数を固定しているのでFastAPIのルーティングを指定してもその関数しか動かないというわけです。
有識者の方であればそりゃそうだって話ですよね…w

そこでCloudRunに移行しました。
>CloudRunとは、フルマネージドのサーバーレス プラットフォームであらゆる言語（Go、Python、Java、Node.js、.NET、Ruby）で記述されたスケーラブルなコンテナ型アプリを構築してデプロイできます。
公式抜粋：https://cloud.google.com/run?hl=ja

Dockerベースのサーバレス、エントリポイントの指定もない、これであれば動かせそう。
あとCloudBuildを使用すれば、デプロイを自動化できるようだったのでぜひ有効活用しよう。

ということで以上やりたいこととその経緯でした。

# 実際にデプロイしてみる
## requirements.txtの用意
デプロイする度に新しいコンテナイメージを作成するので、デプロイ時にライブラリのDL・依存関係の指定をしてあげる必要があります。
なので、それを記したファイルを用意してあげましょう。
場所はどこでもいいです。
~~~shell
$ pip freeze -r requirements.txt
~~~

## Dockerfileの用意
コンテナイメージの作成をするために、Dockerfileを用意します。
今回デプロイしたときの中身はこんな感じ。
~~~Dockerfile:Dockerfile
# Pythonのイメージを指定
FROM python:3.10-slim

# バイナリレイヤ下での標準出力とエラー出力を抑制
ENV PYTHONUNBUFFERED True

# ローカルのソースコードをコンテナにコピー
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# 環境変数の指定 (confの設定がdevとprdで分かれているので指定) 
ENV CONFIGENV dev

# ライブラリのインストール
RUN pip install -r requirements.txt

# 別途パッケージのインストールが必要な場合 (今回はOpenCSV関連のパッケージが必要だったので一応記載)
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y libopencv-dev 

# FastAPIを動かすためのコマンド gunicornを使用
CMD gunicorn -k uvicorn.workers.UvicornWorker app.main:app
~~~
後述しますが、後ほどこのDockerfileを指定するので`Dockerfile.dev`や`Dockerfile.prd`とすることで、開発/本番環境分けをすることができます。

最終的なディレクトリ構成は以下のようになるかと。
~~~
/ 
├ app/
│  ├ api/
│  │  └ 実際の処理
│  ├ database/
│  │  └ DB設定
│  ├ models/
│  │  └ DBモデル
│  ├ routers/
│  │  └ ルーティング
│  ├ schemas/
│  │  └ APIのレスポンスクラス
│  ├ utils/
│  │  └ 共通関数
│  └ main.py
├ conf/
│  ├ dev/
│  │  └ 開発環境用設定ファイル
│  └ prd/
│     └ 本番環境用設定ファイル
├ requirements.txt
└ Dockerfile
~~~
追加で`.dockerignore`を用意しそこにファイル名を記載すると、デプロイ時はそのファイルは除外してビルドされます。

## コンソール画面からデプロイの設定
コンソール画面からCloudRunを開きます。
![スクリーンショット 2023-02-16 121519.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/ccedb6b0-4fd8-756c-f905-b695621370c6.jpeg)
「サービスの作成」を選択。

![スクリーンショット 2023-02-16 121942.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/a36ec1a3-b7fe-1d0d-8f54-eb1309a83346.jpeg)
「ソースリポジトリから新しいリビジョンを継続的にデプロイする」を選択し、「CLOUD BUILDの設定」をします。
そうすると、右から設定項目ウィンドウがニュルっと出てきます。

![スクリーンショット 2023-02-16 122321.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/e76819d0-62ad-4f66-a82e-6b8d350d1473.jpeg)
ここでリポジトリと接続します。
「接続されたリポジトリを管理します」から、GitHubと連携してリポジトリの設定をします。
問題なく連携でき、対象のリポジトリを選択したら次へ行きます。

![スクリーンショット 2023-02-16 122612.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/ee58033f-d9fd-d9dd-d385-5bedff22a2be.jpeg)
次にブランチの選択。対象のブランチを選択もしくは入力し、「次のブランチと一致します」と出たらOKです。

![スクリーンショット 2023-02-16 122807.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/2e476710-f1d9-e603-9e04-89285cb2fe40.jpeg)

最後にBuild Typeの選択です。
「Dockerfile」を選択し、ソースの場所を入力。Dockerfile名を指定してあげます。
先ほどの開発/本番環境分けの場合は、ここに``/Dockerfile.dev``や``/Dockerfile.prd``と指定する形になります。
そして保存。

以下各種設定項目です。
| 項目 | 設定値 | 備考 |
|:-:|:-:|:-:|
| サービス名 | お好きな名前をどうぞ ||
| リージョン | asia-northeast1(東京) ||
| CPU の割り当てと料金 | リクエストの処理中にのみ CPU を割り当てる | ※1 |
| サービス名 | お好きな名前をどうぞ | |
| 自動スケーリング | 最小数：0 最大数100 | ※2 |
| アクセス制限 | すべて | すぐテストしたいので |
| 認証 | 未認証の呼び出しを許可 | こっちじゃないとAPIとして公開できない |

(※1)「CPUを常に割り当てる」場合、APIとして応答速度が速くなります。ただし、一定の稼働時間を超えると料金が発生するので注意
(※2)最小数を「1」以上にすると、いわゆるスタンバイモードみたいな感じになります。つまりインスタンスが起動しっぱなしになります。こちらも一定稼働時間を超えると料金が発生するので注意

最後に「作成」。

![スクリーンショット 2023-02-16 161415.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/4346582b-3bd6-aa56-e539-e01b41c881d3.jpeg)
デプロイが始まりました。

![スクリーンショット 2023-02-16 161447.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/1982ecfa-1a73-55b0-532b-dc18d107e892.jpeg)
CloudBuildの方も動いていますね。

:::note warn
デプロイ時の注意
Gitリポジトリにpushする際、`.gitignore`を使用してアップするソースファイルを制御しますが、ここに記載があるファイルもデプロイ時の対象とならないので注意してください。デプロイする際はお忘れなく。
:::

ということで![スクリーンショット 2023-02-16 162330.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3032307/51806de5-c8b1-cc10-80c6-67f5b140bc68.jpeg)
デプロイ完了です！問題なく動いています。
完了まで大体5分くらいでしょうか。軽いものであればもっと早く終わります。
以降は対象のブランチにpushしてあげるだけで自動でデプロイが走ります。
もちろんPRからのマージでも問題なくデプロイされます。

また、Cloud Buildのページから自動デプロイを無効にしたり手動でデプロイなんかもできます。

# 料金
## Cloud Run
https://cloud.google.com/run/pricing?hl=ja
* 毎月最初の180,000vCPU秒は無料
* 毎月最初の360,000GiB秒は無料
* 毎月200万リクエストは無料

あまり想像はつきませんが、上の方に記載した設定であればほぼほぼ料金は発生しません。
本番環境用に少しスペックや設定を上げると月々大体1500~3000円くらいのランニングコストがかかります。
CPUを割り当て続けたり、インスタンスを起動し続けたりもそうですね。
200万リクエストはよほど使われるAPIでない限りそうそう来ないと思います。

Calculatorで設定値入力して月々かかるお金を算出できるので詳しく知りたい方は確認してみましょう。
https://cloud.google.com/products/calculator?hl=ja

## Cloud Build
https://cloud.google.com/build/pricing?hl=ja
* ビルド処理時間 1日120分まで無料
* 以降1分ごと$0.003/分

1ビルド5分と見積もっても1日20回前後は無料でビルドできますね。（そんなにしないか）

# 最後に
ここまで読んでいただきありがとうございました。
私のような初心者の方の手助けになれたら嬉しいです。
ご指摘などぜひお待ちしております。:bow: