Cloud Run で APIサーバー を動かす時の初歩的な落とし穴

# 概要
Cloud Runにサービスを作成し、FastAPIでリクエストを処理する。これはその時の失敗談です。
Cloud Runを使い慣れている方にとっては当たり前かもしれません。。

# APIの仕様
リクエストを受け取ってスクレイピング処理などを実行します。
スクレイピングに時間がかかるため、 **処理が完了する前に** クライアントへレスポンスを返します。

```python
from fastapi import FastAPI
import asyncio
from stock_scraping_service import stock_scraping_service
from dividend_scraping_service import dividend_scraping_service
from spreadsheet_update_service import spreadsheet_update_service
from market_update_service import market_update_service
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = ["http://localhost:3000"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET"],
    allow_headers=["*"],
)

async def async_update_stock_info(
    stock: bool, dividend: bool, market: bool, spreadsheet: bool
):
    if stock:
        await stock_scraping_service.stock_scraping()

    if dividend:
        await dividend_scraping_service.dividend_scraping()

    if market:
        await market_update_service.update_market_and_industries()

    if spreadsheet:
        await spreadsheet_update_service.spreadsheet_update()

@app.get("/update_stock_info")
async def update_stock_info(
    stock: bool = True,
    dividend: bool = True,
    market: bool = True,
    spreadsheet: bool = True,
):
    try:
        print(
            f"stock: {stock}, dividend: {dividend}, market: {market}, spreadsheet: {spreadsheet}"
        )
        # 非同期でこのタスクが実行される
        asyncio.create_task(
            async_update_stock_info(stock, dividend, market, spreadsheet)
        )
        # 上記タスクの完了を待たずにレスポンスを返す
        return {"message": "保有株情報を更新中です"}
    except Exception as e:
        return {"message": "エラーです"}
```

# 何があったか
ローカルのDocker上では毎回正常に動くのに、Cloud Run上では安定せず、正常に動いたり失敗したりします。特に失敗しやすいのはスクレイピング処理で、Chrome Driverが起動せずエラーになります。

```python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


def boot_driver():
    # Chrome オプションの設定
    chrome_options = Options()
    chrome_options.add_argument("--no-sandbox")  # Chrome の保護機能を無効化する（Docker環境で動かすため）
    chrome_options.add_argument("--headless")  # ヘッドレスモードを有効にする
    chrome_options.add_argument("--disable-gpu")  # GPUを無効にする
    chrome_options.add_argument("--disable-dev-shm-usage")  # 共有メモリ使用を無効化

    # Docker環境専用の記述
    chrome_driver_path = "/usr/local/bin/chromedriver"

    # ドライバーの起動
    driver = webdriver.Chrome(
        options=chrome_options,
        executable_path=chrome_driver_path,
    )

    return driver

```

下記のいずれかのエラーが出ます。

```text
selenium.common.exceptions.SessionNotCreatedException: Message: session not created: DevToolsActivePort file doesn't exist
```

```text
selenium.common.exceptions.WebDriverException: Message: chrome not reachable
```

# 原因と解決方法
## Chromeのオプション
エラー文でググると下記のChromeのオプション設定を追加しろ、という内容の記事がたくさん出てきます。
DockerでSeleniumを使う際は、確かにこれらの記述は必要なようです。

```python
chrome_options.add_argument("--no-sandbox")  # Chrome の保護機能を無効化する（Docker環境で動かすため）
chrome_options.add_argument("--headless")  # ヘッドレスモードを有効にする
chrome_options.add_argument("--disable-gpu")  # GPUを無効にする
chrome_options.add_argument("--disable-dev-shm-usage")  # 共有メモリ使用を無効化
```

しかし私のコードには既にこれらのオプションは存在します。

## Cloud Runの設定
原因がわからず困り果てていたところ、Stack Overflowに[このような投稿](https://stackoverflow.com/questions/75620600/error-when-running-selenium-in-a-thread-in-cloud-run)を見つけました。
そう、原因はコードではなく、Cloud Runの設定にあったのです！

リビジョンの設定を確認すると、CPUの割り当て設定が「リクエストの処理中にのみ CPU を割り当てる」になっていました。

これを「CPUを常に割り当てる」に変更すると処理が毎回成功するようになりました。

![スクリーンショット 2024-01-08 14.29.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1217064/1269e853-715d-654f-2eeb-ffa30b17609b.png)

### なぜCPUを常に割り当てる必要があるのか
おそらくリクエストの処理中とは **リクエストを受け取ってからレスポンスを返すまで** なのでしょう。
私のAPIは **処理が完了する前にレスポンスを返す** 仕様だったため、処理が完了する前にCPUの割り当てがなくなってしまうのです。

### なぜ時々成功していたのか
「リクエストの処理中にのみ CPU を割り当てる」の設定の時でも、処理によっては成功していました。
おそらくレスポンスを返してからCPUの割り当てが無くなるまでに少し猶予があり、その間に処理が完了していたのだと思います。

# 最後に
当時の私はローカルとCloud Runで実行環境が異なるために起こる問題だと思い込んでいたため、解決までにかなりの時間を費やしてしまいました。Cloud Run上でしか再現できないためデバッグも大変でした。。
この記事が同じように悩んでいる人の役に立てば幸いです。