Twitterクローンを作ります #9 Firebase Hosting + Cloud Run 環境へデプロイ

クライアント側もサーバ側も徐々に準備が整って来たので、ここらでクラウド環境へデプロイできるようにしておきましょう。

今回はサーバ側をCloud Runで実行、クライアント側をFirebase Hostingで配信、という構成にしていきます。

※ 注意 ※
課金が発生する可能性があります。

# 下準備

GCPにプロジェクトを作成しておきます。

https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=ja&visit_id=637827559990846511-2689374964&rd=1

また、dockerも利用可能にしておいてください。

# Firebase Hosting

まずFirebase CLIをインストールします。ログインもしておきましょう。

```
$ npm install -g firebase-tools
$ firebase login
```

次にFirebaseの設定ファイルを生成します。

```
$ cd /path/to/project/client
$ firebase init
```

`firebase init`を実行するとウィザード形式で様々な質問をされます。
以下のように回答していきます。

```
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i>
 to invert selection, and <enter> to proceed)

❯◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
のみを選択してEnter


? Please select an option: (Use arrow keys)

❯ Use an existing project 
を選択


? Select a default Firebase project for this directory: (Use arrow keys)

❯ toitta-dev (toitta-dev) 
を選択(自分の環境に合わせてください)



? What do you want to use as your public directory? (public) 
dist を入力してEnter


? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) 
y を入力してEnter


? Set up automatic builds and deploys with GitHub? (y/N) 
N を入力してEnter


? File dist/index.html already exists. Overwrite? (y/N) 
N を入力してEnter

```

これで、下記のような`client/.firebaserc`と`client/firebase.json`が生成されたはずです。

```client/.firebaserc
{
  "projects": {
    "default": "toitta-dev"
  }
}
```

```client/firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
```

とりあえずデプロイしてみましょう。下記を実行します。

```
$ cd /path/to/project/client
$ npm run build
$ firebase deploy
```

`firebase deploy`を実行すると進捗状況が表示され、最後に `Hosting URL: https://toitta-dev.web.app` のようにURLが表示されると思いますのでアクセスしてみましょう。
APIが動作していないので挙動がおかしいですがいままで作成してきた画面が表示されたと思います。

# Cloud Run

次はサーバー側をCloud Runで動かすようにしていきましょう。下記のチュートリアルが参考になります。

https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-python-service

まずは、gunicornをインストールします。下記にも記載がありますが、`flask run`を開発目的以外で利用するのは推奨されていません。

https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/tutorial/deploy.html

下記のように requirements.txt を修正します。

```server/requirements.txt
Flask
pymongo[srv]
gunicorn
```

いつものようにインストールとconstraints.txtの更新を行いましょう。

```
$ cd /path/to/project/server
$ pip install -r requirements.txt -c constraints.txt
$ pip freeze > constraints.txt
```

次に下記の `Dockerfile` と `.dockerignore` を作成します。

```dockerfile:server/Dockerfile
# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.10-slim

# Allow statements and log messages to immediately appear in the Knative logs
ENV PYTHONUNBUFFERED True

# TODO 
ENV MONGO_CREDENTIAL admin:XXXXX

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Install production dependencies.
RUN pip install --no-cache-dir -r requirements.txt -c constraints.txt

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
# Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
```

```server/.dockerignore
Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__
.pytest_cache
server-env
```

これでdocker上で実行する準備ができました。実際にローカルのdocker上で動かしてみましょう。

```
$ cd /path/to/project/server
$ docker build --tag toitta:test .
$ docker run --rm -p 9090:8080 -e PORT=8080 toitta:test
```

これで立ち上がっているはずです。
リクエストを送ってみましょう。

```
$ curl -X POST -H "Content-Type: application/json" -d '{"identifier":"hoge", "password":"fuga"}' localhost:9090/api/login

{"_id":"622c82265313db38daf489aa","display_name":"hoge","email":"hoge@example.com","username":"hoge"}
```

無事 docker 上でも動かすことができました。(`server/__init__.py`が悪さして最初は動かなかったので修正しました...)

それではこちらをCloud Run上で動作させてみましょう。

```
$ cd /path/to/project/server
(↓ toitta-dev の部分はGCPの自身のプロジェクト名に変更が必要)
$ gcloud builds submit --tag gcr.io/toitta-dev/toitta-api
$ gcloud run deploy toitta-api --image gcr.io/toitta-dev/toitta-api
```

`API [run.googleapis.com] not enabled on project ...` などが表示されたら y を入力してください。
進捗が表示され、最後に `Service URL: https://toitta-api-mehiivqyda-an.a.run.app`などが表示されると思うので、確認してみましょう。

```
$ curl -X POST -H "Content-Type: application/json" -d '{"identifier":"hoge", "password":"abc"}' https://toitta-api-mehiivqyda-an.a.run.app/api/login

{"_id":"622c82265313db38daf489aa","display_name":"hoge","email":"hoge@example.com","username":"hoge"}
```

うまくいきましたね！

# Firebase Hosting と Cloud Run の連携

https://firebase.google.com/docs/hosting/cloud-run?hl=ja#python

を参考に、/apiのリクエストをCloud Runに向けてあげましょう。firebase.jsonを修正します。

```client/firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/api/**",
        "run": {
          "serviceId": "toitta-api",
          "region": "asia-northeast1"
        }
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
```

再度画面をデプロイします

```
$ cd /path/to/project/client
$ firebase deploy
```

![スクリーンショット 2022-03-13 1.28.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/46c37684-10f3-d11d-150e-3895c755884d.png)

YES!! ちゃんとAPIも利用できていますね!

今回はここまでにしましょう。