Cloud Runのリクエスト最大サイズ(32MiB)を突破する！

# はじめに
Cloud Runは、コンテナイメージを簡単にデプロイしてアプリケーションを実行できる便利なサービスです。
しかし、何も考えずにサービスを構築すると、思わぬ落とし穴にはまることがあります。

私は以前、ファイルをアップロードするAPIを作成した際、とある問題に直面しました。

*〜回想〜*

「よっしゃ、FastAPIを使ってファイルをアップロードするAPIを作成したやで」

「Cloud Runへのデプロイが完了した！動作確認しよーっと、curl commandぽちー」

「はっ…？ **413 Request Entity Too Large** が返ってきた…！？」
「開発環境では問題なく動作したのになんでや…！？」

*〜回想終〜*

どうやら、リクエストサイズ（今回でいうところのアップロードするファイルのサイズ）が大きすぎてCloud Run側でリクエストが失敗しているようでした。

Cloud Runでは、リクエストサイズが最大32MiBに制限されています。このため、大きなリクエストサイズを扱う際には工夫が必要です。

そこで、今回の記事では、Cloud Runのリクエスト最大サイズ上限を突破する方法を調べてみました。


# 結論
いきなり結論ですが、ずばりするべきことはコレです。

**HTTP/2対応すること**




こちらの公式ドキュメントをみていただくと分かる通り、以下の記述がされています。

https://cloud.google.com/run/quotas?hl=ja


| 説明 | 上限 |
| --- | --- | 
|HTTP/1 リクエストの最大サイズ | HTTP/1 サーバーを使用する場合は 32 MiB。HTTP/2 サーバーを使用する場合は無制限。|

Cloud Runは HTTP/2 をサポートしていますが、デフォルトでは HTTP/2 リクエストが HTTP/1 にダウングレードされてコンテナに送信されます。このため、HTTP/1の制限（最大32MiB）に引っかかってしまいます。
しかし、設定を変更して **HTTP/2を有効にすれば、この制限を突破**できます。

では、どのようにして HTTP/2 を有効化するのでしょうか。

調査した結果、以下の2つの方法が見つかりました：

* パターン1: APIサーバー自体を HTTP/2 対応に直す
* パターン2:マルチコンテナ構成でNginxを利用

それぞれの方法について、具体的に解説していきます。


## パターン1: APIサーバー自体を HTTP/2 対応に直す
1つ目のパターンは、APIサーバー自体をHTTP/2対応させるように修正する方法です。

とは言っても、やること自体はそんなに多くありません。
1. 必要に応じてWebサーバーを変更
2. Cloud Run側の設定で `HTTP/2リクエストを受ける` にチェック


### 1. Webサーバーを変更
私はWebサーバーに `uvicorn` を使用していました。　`uvicorn` は HTTP/2 をサポートしておりません（2024年12月19日現在）。
そこで、Webサーバーとして `hypercorn` を使用するように変更しました。


```text
├── app
│    ├── main.py
│    ├── Dockerfile
│    └── requirements.txt
└── docker-compose.yml
```

```Dockerfile:Dockerfile
FROM python:3.12-bullseye

WORKDIR /opt/app
COPY requirements.txt requirements.txt 
RUN pip install -r requirements.txt

WORKDIR /app
COPY ./ /app/

# ここをuvicornからhypercornに修正する
CMD ["hypercorn", "main:app", "--bind", "0.0.0.0:8088", "--access-logfile", "-", "--error-logfile","-","--workers","1"]

```

```py:main.py
from fastapi import FastAPI, File, UploadFile
from google.cloud import storage

app = FastAPI()
gcs_client = storage.Client()
bucket = gcs_client.bucket("gcs-bucket")


@app.post("/upload")
def upload(
    file: UploadFile = File(...),
):
    blob = bucket.blob(f"{file.filename}")
    blob.upload_from_file(file.file, content_type=file.content_type)
    
    return {"message": f"{file.filename} uploaded."}
```

```txt:requirements.txt
fastapi[standard]==0.115.6
google-cloud-storage==2.19.0
hypercorn==0.17.3
```

### 2. Cloud Run側の設定で `HTTP/2 エンドツーエンドを使用する` にチェック
APIサーバー側をHTTP/2対応したら、Cloud Runにdeployする際、
ネットワーキングにある `HTTP/2 エンドツーエンドを使用する` にチェックを入れるだけです。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1954229/9e76204e-c83f-3602-5185-247b75055180.png)





### （余談）TLSの設定
TLSの管理はCloud Runが自動で行ってくれます。

なので、もし開発環境でHTTP/2での動作確認をしたい場合は、自分で証明書を発行して設定する必要があります。
今回私は「自己署名入り証明書」（通称: オレオレ証明書）を発行しました。

詳しくはこのサイトに載っていたりするので、適宜参考にしていただければと思います。

https://www.hivelocity.co.jp/blog/46149/



```sh
$ apt install -y mkcert

$ mkcert -install
$ mkcert sample.com

$ mv sample.com-key.pem ~/cert/sample.com-key.pem
$ mv sample.com.pem ~/cert/sample.com.pem
```

そして、docker-compose.ymlを以下のように書き換えます。


```yaml:docker-compose.yml
services:
  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    image: sample
    container_name: sample
    restart: always
    volumes:
      - ./app:/app
      - type: bind
        source: ~/cert/
        target: /cert/
        read_only: true
    command:
      hypercorn main:app --bind 0.0.0.0:8088 --workers 1 --certfile /cert/sample.com.pem --keyfile /cert/sample.com-key.pem
    ports:
      - "8088:8088"
    env_file:
      - .env

```


あとはdockerを立ち上げれば、HTTP/2でリクエストができます。
```sh
$ docker compose up --build
```




## パターン2: マルチコンテナ構成でNginxを利用
2つ目のパターンは、Nginxを用いてHTTP/2リクエストを処理する方法です。
たとえAPIサーバーが HTTP/1 にしか対応していなくても、Nginxが HTTP/2 リクエストを受け取り、APIサーバーには HTTP/1 でリクエストを転送することで、HTTP/2 通信が可能になります。

このアプローチは、2023年5月にマルチコンテナが正式にGA（一般提供）となったことで実現可能となりました。

https://cloud.google.com/blog/ja/products/serverless/cloud-run-now-supports-multi-container-deployments


やること自体は以下の通りです。

1. Nginxを HTTP/2 対応で設定
2. マルチコンテナ構成でCloud Runのサービスを作成
3. Cloud Run側で `HTTP/2 エンドツーエンドを使用する` 設定を有効化



### 1. NginxをHTTP/2対応で設定
まずは、Nginxを用意します。以下のようにファイルを作成してみましょう。

```text
├── nginx
│    ├── nginx.conf
│    └── Dockerfile

(他のファイルはすでに用意してあるものとします)
├── app
│    ├── main.py
│    ├── Dockerfile
│    └── requirements.txt
└── docker-compose.yml
```


```Dockerfile:Dockerfile
FROM nginx:1.27.3-alpine-slim
EXPOSE 8080
COPY ./nginx.conf /etc/nginx/conf.d/nginx.conf
```

```text:nginx.conf
server {
    listen 8080;
    http2  on;
    client_max_body_size 1G;

    location / {
        proxy_pass   http://localhost:8088;
    }
}
```

この時注目するべきポイントとしては、
* `http2` をonにする
* `client_max_body_size` を好みの値にする


という点です。特に、 `client_max_body_size` はデフォルトで1MBなので、リクエストしたいサイズに合わせて設定しないと、Nginx側で 413 (Request Entity Too Large) エラーが発生します。

https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size


### 2. マルチコンテナ構成でCloud Runのサービスを作成
続いて、マルチコンテナ構成でCloud Runのサービスを作成してください。
以下に設定の例を挙げておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1954229/54fe280e-0c8f-4405-f070-b20629d50996.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1954229/10a56774-aa92-2a96-8616-58e0c31c734d.png)




また、 `app` コンテナでは環境変数に `PORT` を指定する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1954229/7ef43fc3-aee4-232d-5e88-a0177ff07abe.png)


### 3. Cloud Run側で `HTTP/2 エンドツーエンドを使用する` 設定を有効化
ここはパターン1の時と同じです。 `HTTP/2 エンドツーエンドを使用する` にチェックを入れてください。



以上の設定を行うことで、実際に32MiB以上あるリクエストも受けることができるようになります。

```sh
$ curl -v -X POST 'https://sample.asia-northeast1.run.app/upload' \
--form 'file=@large_file.txt;type=text/plain'

...

* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs

...

{"message":"large_file.txt uploaded."}
```


# まとめ
Cloud Runでリクエスト最大サイズを突破するには HTTP/2 化が必要になってきます。
そのために取ることのできる手段は

* `hypercorn` などを使い、HTTP/2 対応のAPIサーバーを立てる
* Nginxを間に挟んで、HTTP/2 でリクエストを受けられるようにする


が挙げられます。
（あくまで個人の感想ですが）やってみると意外とすんなり対応ができるので、お好みの方法で413エラーを回避してみてください。


# 最後に

最後まで読んでくださり、ありがとうございました！

明日以降の記事もお楽しみに！

https://qiita.com/advent-calendar/2024/intimatemerger