4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Intimate MergerAdvent Calendar 2024

Day 19

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

Last updated at Posted at 2024-12-18

はじめに

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対応させるように修正する方法です。

とは言っても、やること自体はそんなに多くありません。

  1. 必要に応じてWebサーバーを変更
  2. 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
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"]

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."}
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

(余談)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を以下のように書き換えます。

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(一般提供)となったことで実現可能となりました。

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

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

1. NginxをHTTP/2対応で設定

まずは、Nginxを用意します。以下のようにファイルを作成してみましょう。

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

(他のファイルはすでに用意してあるものとします)
├── app
│    ├── main.py
│    ├── Dockerfile
│    └── requirements.txt
└── docker-compose.yml
Dockerfile
FROM nginx:1.27.3-alpine-slim
EXPOSE 8080
COPY ./nginx.conf /etc/nginx/conf.d/nginx.conf
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のサービスを作成してください。
以下に設定の例を挙げておきます。

image.png

image.png

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

image.png

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エラーを回避してみてください。

最後に

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

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

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?