はじめに
Cloud Runは、コンテナイメージを簡単にデプロイしてアプリケーションを実行できる便利なサービスです。
しかし、何も考えずにサービスを構築すると、思わぬ落とし穴にはまることがあります。
私は以前、ファイルをアップロードするAPIを作成した際、とある問題に直面しました。
〜回想〜
「よっしゃ、FastAPIを使ってファイルをアップロードするAPIを作成したやで」
「Cloud Runへのデプロイが完了した!動作確認しよーっと、curl commandぽちー」
「はっ…? 413 Request Entity Too Large が返ってきた…!?」
「開発環境では問題なく動作したのになんでや…!?」
〜回想終〜
どうやら、リクエストサイズ(今回でいうところのアップロードするファイルのサイズ)が大きすぎてCloud Run側でリクエストが失敗しているようでした。
Cloud Runでは、リクエストサイズが最大32MiBに制限されています。このため、大きなリクエストサイズを扱う際には工夫が必要です。
そこで、今回の記事では、Cloud Runのリクエスト最大サイズ上限を突破する方法を調べてみました。
結論
いきなり結論ですが、ずばりするべきことはコレです。
HTTP/2対応すること
こちらの公式ドキュメントをみていただくと分かる通り、以下の記述がされています。
説明 | 上限 |
---|---|
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対応させるように修正する方法です。
とは言っても、やること自体はそんなに多くありません。
- 必要に応じてWebサーバーを変更
- Cloud Run側の設定で
HTTP/2リクエストを受ける
にチェック
1. Webサーバーを変更
私はWebサーバーに uvicorn
を使用していました。 uvicorn
は HTTP/2 をサポートしておりません(2024年12月19日現在)。
そこで、Webサーバーとして hypercorn
を使用するように変更しました。
├── app
│ ├── main.py
│ ├── Dockerfile
│ └── requirements.txt
└── docker-compose.yml
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"]
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."}
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 エンドツーエンドを使用する
にチェックを入れるだけです。
(余談)TLSの設定
TLSの管理はCloud Runが自動で行ってくれます。
なので、もし開発環境でHTTP/2での動作確認をしたい場合は、自分で証明書を発行して設定する必要があります。
今回私は「自己署名入り証明書」(通称: オレオレ証明書)を発行しました。
詳しくはこのサイトに載っていたりするので、適宜参考にしていただければと思います。
$ 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を以下のように書き換えます。
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でリクエストができます。
$ docker compose up --build
パターン2: マルチコンテナ構成でNginxを利用
2つ目のパターンは、Nginxを用いてHTTP/2リクエストを処理する方法です。
たとえAPIサーバーが HTTP/1 にしか対応していなくても、Nginxが HTTP/2 リクエストを受け取り、APIサーバーには HTTP/1 でリクエストを転送することで、HTTP/2 通信が可能になります。
このアプローチは、2023年5月にマルチコンテナが正式にGA(一般提供)となったことで実現可能となりました。
やること自体は以下の通りです。
- Nginxを HTTP/2 対応で設定
- マルチコンテナ構成でCloud Runのサービスを作成
- Cloud Run側で
HTTP/2 エンドツーエンドを使用する
設定を有効化
1. NginxをHTTP/2対応で設定
まずは、Nginxを用意します。以下のようにファイルを作成してみましょう。
├── nginx
│ ├── nginx.conf
│ └── Dockerfile
(他のファイルはすでに用意してあるものとします)
├── app
│ ├── main.py
│ ├── Dockerfile
│ └── requirements.txt
└── docker-compose.yml
FROM nginx:1.27.3-alpine-slim
EXPOSE 8080
COPY ./nginx.conf /etc/nginx/conf.d/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) エラーが発生します。
2. マルチコンテナ構成でCloud Runのサービスを作成
続いて、マルチコンテナ構成でCloud Runのサービスを作成してください。
以下に設定の例を挙げておきます。
また、 app
コンテナでは環境変数に PORT
を指定する必要があります。
3. Cloud Run側で HTTP/2 エンドツーエンドを使用する
設定を有効化
ここはパターン1の時と同じです。 HTTP/2 エンドツーエンドを使用する
にチェックを入れてください。
以上の設定を行うことで、実際に32MiB以上あるリクエストも受けることができるようになります。
$ 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エラーを回避してみてください。
最後に
最後まで読んでくださり、ありがとうございました!
明日以降の記事もお楽しみに!