【GCP初心者向け】Cloud Runでサーバーレスな超簡易Web APIを無料で作る

自分用の簡易なWeb APIが必要になり、GCPのCloud Runを初めて触ったので、記録として手順をまとめて公開しておくことにしました。

# この記事で作るもの
GCP初心者向けの記事です。Cloud RunをWeb APIとして使用し、ローカルから**「こんにちは」とPOSTリクエストすると「こんにちはと言いましたね。」と返してくる超簡単なWeb API**を作ります。
![00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/c3f30743-2702-b2f6-b7d2-ca45614fbf95.jpeg)
ここで作るのは簡単なAPIですが、要するに入力と出力を整えれば、あとはいくらでも応用して処理が書けるので、その重要なインターフェース部分を作る手順です。

# はじめに ～Cloud Runとは～
Web APIをサーバーレスで簡単に実現するには、**AWS LambdaとAPI Gatewayを組み合わせる**、または**GCP Cloud FunctionsやGAE**という場合が多いかと思います。
ただしカスタムのライブラリが必要となる場合は**AWS Fargate**、更に無料でやるのは**GCP Cloud Run**という選択肢になります。

Cloud Runは**呼び出されたときにコンテナを実行するというサービス**です。要は、カスタムのライブラリを詰め込んだり、カスタムのデータを持たせて読み込ませて処理させる、といった使い方が可能です。またWeb API化でき、公開可能です。

AWSでいうとFargateやLambda（コンテナ）とAPI Gatewayが一緒になったサービスで、**GCPの場合は永久無料枠が用意されている**といった感覚です。なお詳しいユースケースは以下に記載されてます。
https://cloud.google.com/serverless-options/?hl=ja

ただし公開するWeb APIとなると、**永久無料枠の場合はアクセス過多の際にスペックが貧弱すぎる**ため向いていません。
そこで自分がバッチ処理で使うWeb APIとして、Cloud Runを使うまでの手順を記載していきます。

## Cloud Runの永久無料枠
Cloud Runは永久無料枠が用意されています。他のクラウドサービスはここまで太っ腹じゃないですね。
> 200 万リクエスト（1 か月あたり）
> 360,000 GB 秒のメモリ、180,000 vCPU 秒のコンピューティング時間
> 1 GB の北米からの下り（外向き）ネットワーク（1 か月あたり）
>
> https://cloud.google.com/run/pricing?hl=ja

コンテナをずっと起動させておく場合はお金がかかってしまいますが、**呼び出されたときのみコンテナを起動させる、1日に数回走るバッチ処理で使用するといった使い方であれば無料でいける**でしょう。

# Cloud RunでWeb APIを作ろう

実際にCloud RunをWeb APIとして動かすまでには、以下の手順が必要になります。

1. **POSTリクエストを受け付けるコードを書く**
2. **Web APIサーバーとしてのDockerfileを書く**
3. **GCP上でDockerコンテナイメージをビルド＋プッシュする（Container Registory）**
4. **Cloud Runで新規サービスを作成し、イメージをデプロイする**

PythonスクリプトとDockerfileについては、以下のとても分かりやすい記事をベースにして、POSTリクエストに対応させるために書き換えています。
[Cloud Runに入門してみる](https://qiita.com/Canon11/items/cc461dc57d5d9fbb5ca9)

## POSTリクエストを受け付けるコードを書く
まずは簡単に、POSTリクエストで送られてきたデータを処理し、返却するコードを書きます。json形式でリクエストされたdataキーの値を読み、「あなたは○○と送ってきましたね。」と返してきます。Python＋flaskで書きます。

例えばローカルに`./run-webapi`ディレクトリを作り、その配下に、以下のコードが書かれた`hello-api.py`を配置しておきます。

```python:hello-api.py
import os
from flask import Flask, request

app = Flask(__name__)


@app.route('/', methods=['POST'])
def post_response():
    request_dict = request.get_json()
    req_data = str(request_dict['data'])
    res_message = f'あなたは{req_data}と送ってきましたね。'
    return res_message


if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
```

## Web APIサーバーとしてのDockerfileを書く
次に`./run-webapi`配下に、以下の`Dockerfile`を配置します。これでコンテナ起動時に`hello-api.py`が実行され、コンテナはWeb APIサーバーとなります。

```dockerfile:Dockerfile
FROM python:3.7

ENV APP_HOME /hello-api
WORKDIR $APP_HOME
COPY . .

RUN pip install Flask gunicorn
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 hello-api:app
```

## GCP上でのコンテナイメージのビルド

### プロジェクト作成
まずはGCPのコンソールから新しいプロジェクトを作成します。（既存のプロジェクトにコンテナをビルドしたい人はもちろんスキップ）
![01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/aa9ec4eb-dec6-93a3-3a1a-a6758c326ee3.jpeg)

プロジェクト名は`hellowebapi`になりました。

### コンテナイメージのビルド＋ブッシュ
次にGCP上にコンテナイメージをビルドし、保存します。**ローカルでビルドする必要はありません**。
コンソールで、`./run-webapi`配下に移動し、Google Cloud SDKのコマンドを入力します。
（※Google Cloud SDKがインストールされている前提です。詳しくはこちら https://cloud.google.com/sdk/docs/install?hl=JA ）

```sh:
$ cd ./run-webapi
$ gcloud builds submit --tag gcr.io/hellowebapi/hello-api --project hellowebapi
```
ビルド＋プッシュが完了した段階で、Container Registory上に`hello-api`の名前でコンテナイメージが保存されます。
![02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/35b8db67-c360-399a-3dcd-82c1b528aea1.jpeg)


## Cloud Runに登録済みコンテナをデプロイする
最後に、Container Registoryに登録したコンテナをCloud Run上にデプロイし、**ようやくWeb APIを作ります**。

ウェブコンソール画面上の「サービスの作成」をクリックします。
![03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/55963ad9-e51a-e47e-a0e1-29e895baf853.jpeg)

### ①サービスの設定
デプロイメント プラットフォーム：`Cloud Run（フルマネージド）`
リージョン：`us-central1 (Iowa)`
サービス名：`webapi-test`
![04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/619f1e7c-e040-4c9d-b1a2-21588d547b22.jpeg)

### ②サービスの最初のリビジョンの構成
既存のコンテナ イメージから 1 つのリビジョンをデプロイする：`hello-api`の`latest`
![05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/3078b540-8db0-7113-8aff-c1c58e59c134.jpeg)

### ③このサービスをトリガーする方法の構成
HTTP上り（内向き）：`全てのトラフィックを許可する`
認証：`未認証の呼び出しを許可`
![06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/2a7087b5-6a7e-33a0-0dd5-0461a8690915.jpeg)

なおここでは一旦、全ての認証を許可します。当然ですが**セキュリティ上、本来は自分用のAPIの場合には認証をつけた方が良い**ですが、認証手順をすっ飛ばして使うために、アドレスを公開しないことを前提に未認証を許可します。

上記の設定を入力後、「**作成**」ボタンをクリックします。

1分後くらいに、ステータスが緑色になってURL「`https://webapi-test-xxxxxx.a.run.app`」が表示されます。**これがWeb APIのURL**となります。
![07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/95d8d53f-33e0-2f70-2d9b-5e98409f8771.jpeg)

## Web APIを試す
ではローカル環境から、いま作ったCloud Run上のWeb APIを呼び出してみましょう。
JupyterNotebook上で以下のコードを実行してみます。

```python:JupyterNotebook上
import requests
import json

json_data = json.dumps({'data':'「ローカル環境からこんにちは」'})
response = requests.post(
    'https://webapi-test-xxxxxx.a.run.app', # 自分のWeb APIのURLに書き換え
    json_data,
    headers={'Content-Type': 'application/json'}
)

print(response.text)
```

以下の結果が返ってくれば成功です。

```:結果
あなたは「ローカル環境からこんにちは」と送ってきましたね。
```

ちなみにコンテナの初回起動には数秒くらいかかります。初回から連続でリクエストを送る場合は、既にコンテナが起動しているのでレスポンスは速いです。
**コンテナはしばらくすると自動で停止**します。そうして**料金が抑えられる**わけですね。


## コンテナが自動でスケールしないようにするために

Cloud Runの標準スペックは貧弱で、vCPU 1コア、RAMが256MBが標準です。そのため重い処理が走る場合、自動でスペックをスケールさせる機能があります。
まったりでもよいから無料の範囲で抑えたい、という場合は、スケールをさせないようにする必要があります。

ウェブコンソール画面上の「新しいリビジョンの編集とデプロイ」を押します。
![08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/e6cd69b8-7370-5856-e819-6709fa679339.jpeg)
「自動スケーリング」の「インスタンスの最大数」を`1`に設定し、「デプロイ」を押します。
![09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/928728/7fa88de5-32e4-d8b8-d628-343bd3b94647.jpeg)
そうすることで、重い処理だとしてもスケールせずに1インスタンスで処理をするようになります。

## 注意事項：ストレージ容量に気を付けましょう
Cloud Runが無料とは言え、気を付けなければいけないちょっとした落とし穴があります。
登録済みのコンテナイメージはContainer Registoryで見られますが、**実際のファイルはCloud Storageに保存されています**。
Cloud Storageの永久無料枠は5GBですので、**ポンポンとコンテナイメージを作っているとすぐに一杯になる**ので気を付けた方が良いですね。

# おわりに
Cloud Runを利用すればものすごく簡単にWeb APIが無料で作れます。
私は「**コンテナに簡易DBのような割と大きめのデータを持たせておき処理をする**」という必要があり、Cloud FunctionsではなくCloud Runを使う場面があり、重宝しました。

2020年12月現在、AWS Lambdaでもコンテナに対応したそうですので試してみたいですが、外部公開のAPIまでとなるとまだAPI Gatewayが必要なので、どうしてもコストがかかります。その点、Cloud Runの無料枠は外部APIさえ作れてしまいますので、0円アプリを作るのに良いですね。