新しい勤務先がGoogle Cloudメインなのでそれになれる為に各種リソースをおさわりしています。
今回は無料枠があるけどあまり利用していなかったCloud Runを活用してサイト監視のシステムを作ってみました。
Cloud Runは、コンテナを走らせてくれる環境で、普通にDockerファイルを用意してやれば動きます。 今回は自分のサービスのハイレベル監視をこれで動かそうと言う事で、Seleniumによるアクセスを行うコンテナを作りました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
FROM ubuntu:24.04 WORKDIR /app EXPOSE 8000 RUN apt-get update && apt-get install -y \ python3 \ python3-pip \ wget \ unzip \ dbus \ libcups2 \ libvulkan1 \ xdg-utils \ libatk-bridge2.0-0 \ libgtk-3-0 \ libasound2t64 \ libu2f-udev \ libcurl4 \ fonts-liberation \ libgbm1 \ libnss3-tools \ fonts-ipafont-gothic \ fonts-ipafont-mincho \ fonts-noto-cjk \ language-pack-ja \ uvicorn RUN wget -q https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_127.0.6533.99-1_amd64.deb && \ dpkg -i google-chrome-stable_127.0.6533.99-1_amd64.deb && \ apt-get install -f -y; \ rm google-chrome-stable_127.0.6533.99-1_amd64.deb RUN wget -q https://storage.googleapis.com/chrome-for-testing-public/127.0.6533.99/linux64/chromedriver-linux64.zip && \ unzip chromedriver-linux64.zip \ mv chromedriver-linux64/chromedriver /usr/local/bin/; \ rm chromedriver-linux64.zip; \ rm chromedriver-linux64 -r COPY src/ ./app/ COPY ./requirements.txt ./app/ RUN pip3 install -r ./app/requirements.txt --no-cache-dir --break-system-packages RUN apt-get purge -y --auto-remove build-essential python3-pip wget unzip make gcc lib*-dev RUN apt-get clean RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* CMD /etc/init.d/dbus start&&export XDG_RUNTIME_DIR=/run/user/$(id -u)&&mkdir -p $XDG_RUNTIME_DIR&&chmod 700 $XDG_RUNTIME_DIR&&chown $(id -un):$(id -gn) $XDG_RUNTIME_DIR&&export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus&&dbus-daemon --session --address=$DBUS_SESSION_BUS_ADDRESS --nofork --nopidfile --syslog-only&sleep 1&&uvicorn app.main:app --host 0.0.0.0 --port 8000 |
Dockerfileはこんな感じになりました。
ベースはUbuntu24,04を使い、アプリは/appに配置することにしました。
Selenium+Chromeを中で動かすためにaptで結構色々ライブラリやら日本語フォントやら入れています。 Ubuntuミニマムだと数十MBのイメージがで来ますが、これだと1.5GB位(Artifacts Registoryの仮想サイズで600MB+なので無料枠を微妙にはみ出します)
開発環境はDockerとか諸々のツールが入ってるディレクトリの下にsrcディレクトリを切った状態で作業しているので、コンテナを作る時にはsrc以下をまるっとappにコピーしてます。 requirements.txtだけはsrcより上位にあるので追加コピーしてpipで入れて、コンテナサイズ節約のためにaptのパージとか適当にかけてます。
CMDで出すところが非常に長いです。 これは、Chrome使う時にdbusうごいてねーよって怒られたので、dbusを立ち上げた後にuvicornをスタートさせてるためです。 sleep 1は動かしてるインスタンスが小さいので安定のために待機させてます。
個人なら普通にローカルで作ってアップしてでも良いですが、やっぱり仕事の勉強がてらなので、ここはCloud Buildで継続デプロイも構成。 GitHubのマスターブランチにプッシュしたら走るトリガを作って、cloudbuild.yamlを準備。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
steps: - name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: ['-c', 'docker pull us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium:$BRANCH_NAME || exit 0'] - name: 'gcr.io/cloud-builders/docker' args: [ 'build', '-t', 'us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium:$BRANCH_NAME', '--cache-from', 'us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium:$BRANCH_NAME', '-f', 'Dockerfile', '.' ] - name: 'gcr.io/cloud-builders/docker' args: [ 'push', 'us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium:$BRANCH_NAME' ] - name: 'gcr.io/cloud-builders/gcloud' args: [ 'run', 'deploy', '--project', '$PROJECT_ID', 'selenium', '--image', 'us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium:$BRANCH_NAME', '--region', 'us-west1', '--platform', 'managed', '--max-instances', '1', '--min-instances', '0', '--memory', '1Gi', '--timeout', '3m', '--cpu', '1', '--concurrency', '1', '--port', '8000', '--allow-unauthenticated', '--service-account', 'Runが走るサービスアカウント' ] - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk' entrypoint: bash args: - -ceux - | gcloud artifacts docker images list us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium --include-tags --filter="TAGS!=latest" --format="value(DIGEST)" | xargs -n1 -I{} gcloud artifacts docker images delete --quiet --async us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium@{} images: - us-west1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/selenium:$BRANCH_NAME options: logging: CLOUD_LOGGING_ONLY |
最初のDocker PULLは2回目以降のビルドを高速化するために既存のコンテナを取ってきてキャッシュ利用するためのおまじないです。 このビルドだと初期は5分くらいかかりますが、これやると半分くらいで収まります。
で、DockerをビルドしてArtifacts Registoryにpush。
ターゲット系がus-west1で纏まってるのは無料枠が大抵このリージョンになるので合わせている都合です。 仕事では普通にasia-northeast1。
pushしたコンテナを使ってRunをデプロイしてます。
自分用ケチケチなので1Giメモリ1CPUで暴走課金防止のためにタイムアウトも3mと短め、Max-instace1でMin0なので結構コールドスタートになります。 今回はFastAPI側に認証機構を持たせるのでallow-unauthenticatedを指定してます。 内部から呼び出すだけの場合はこれを外して適当なIAM設定の所からコールするのが良いです。
で、サービスアカウント指定は、Runを呼び出すんじゃ無くRunが使うサービスアカウントなので、例えばCloud Storageに保存するならストレージ書き込みロール、Cloud Loggingに書き込むなら同様にログ書き込みロールを付けるだけで、Run実行などは不要です。 Google Cloudのサービス特に呼ばないならそれこそ何も権限が無い状態でも動きます、デフォのComputeは権限が凄く付いていて危なっかしいので、必ず権限を削った物を用意します。
デプロイ後に追加のgcloud sdk使った処理がありますが、こちらはArtifacts Registoryからlatest以外を削除してしまう処理です。 リポジトリ自体にもライフサイクル削除機能がありますが、最低1日1回走るって言う機能なので、テストで頻繁にpushしてると削除されるまでの時間でも結構なサイズが溜まってしまうのでデプロイの最後で消しています。 通常はRegistory側の設定だけでこれは不要ですね。
これで、src配下に置いたソース入りDockerがCloud Run上で動いてくれるので、あとはSchedulerなりなんなりで叩いて走らせます。
アプリ側は基本形はこんな感じでやってます
1 2 3 4 5 6 7 8 9 |
driver = None @asynccontextmanager async def lifespan(app: FastAPI): global driver with ChromeDriverFactory.create_driver() as driver: yield app = FastAPI(lifespan=lifespan) |
複数のAPI機能で唯一のchromedriverを使い回すためにグローバルにdriverを作り、コンテキストマネージャのライフスパンを使ってアプリ起動時にドライバを生成し、終了時に落とすためにwithでドライバ呼んで中はyieldのみという感じに。 ChromeDriverFactory.create_driverは各種オプション指定してChrome webdriverを作ってwebdriverを返してます。
後は各種APIを実装、グローバルに置いてるdriverを使い回していく感じ。
この構成でコールドスタートだと10秒くらいかかりますが、まぁ許容かなと。 Cloud Runは立ち上げからレスポンスを返すまでの課金ですが、インスタンス自体は即座に止まらずにアイドルで一定時間残るので、連続でリクエストを投げていく分にはそれほど問題になりません。 表示テスト、ログインテスト、編集テスト、編集結果反映確認テスト、削除テストと順番に投げていくと初回がコールドスタートで15秒とかかかるけど、その後のは2~3秒で進んでいって、サイトがちゃんと動くねテストが一通り30秒とかで終わります。 Cloud Schedulerも3ジョブまで無料(Githubに一つ取られてるのでウチの空きは2個だったので、テスト一式を起動するジョブを1本設定)なので、ほぼ無料で毎時にサイト動作確認とか出来ます。 ちょっとDockerファイルサイズがはみ出してる都合でArtifacts Registoryの課金が月に数円程度の感じ。
リソースのヘルスステータスはモニタ出来ますが、データ壊れておかしくなってるとか言うケースでは検知出来ないので、Selenium等の実際のUI操作テストは有用なので、程よいコスト感で回せるCloud Run+Schedulerのシステムは良い感じですね。
(40)