概要
Cloud Runにサービスを作成し、FastAPIでリクエストを処理する。これはその時の失敗談です。
Cloud Runを使い慣れている方にとっては当たり前かもしれません。。
APIの仕様
リクエストを受け取ってスクレイピング処理などを実行します。
スクレイピングに時間がかかるため、 処理が完了する前に クライアントへレスポンスを返します。
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が起動せずエラーになります。
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
下記のいずれかのエラーが出ます。
selenium.common.exceptions.SessionNotCreatedException: Message: session not created: DevToolsActivePort file doesn't exist
selenium.common.exceptions.WebDriverException: Message: chrome not reachable
原因と解決方法
Chromeのオプション
エラー文でググると下記のChromeのオプション設定を追加しろ、という内容の記事がたくさん出てきます。
DockerでSeleniumを使う際は、確かにこれらの記述は必要なようです。
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にこのような投稿を見つけました。
そう、原因はコードではなく、Cloud Runの設定にあったのです!
リビジョンの設定を確認すると、CPUの割り当て設定が「リクエストの処理中にのみ CPU を割り当てる」になっていました。
これを「CPUを常に割り当てる」に変更すると処理が毎回成功するようになりました。
なぜCPUを常に割り当てる必要があるのか
おそらくリクエストの処理中とは リクエストを受け取ってからレスポンスを返すまで なのでしょう。
私のAPIは 処理が完了する前にレスポンスを返す 仕様だったため、処理が完了する前にCPUの割り当てがなくなってしまうのです。
なぜ時々成功していたのか
「リクエストの処理中にのみ CPU を割り当てる」の設定の時でも、処理によっては成功していました。
おそらくレスポンスを返してからCPUの割り当てが無くなるまでに少し猶予があり、その間に処理が完了していたのだと思います。
最後に
当時の私はローカルとCloud Runで実行環境が異なるために起こる問題だと思い込んでいたため、解決までにかなりの時間を費やしてしまいました。Cloud Run上でしか再現できないためデバッグも大変でした。。
この記事が同じように悩んでいる人の役に立てば幸いです。