Cloud Run Functions 第二世代でPuppeteerを動かしてみる【GAS】

以前、Google Apps Scriptで新生Google Cloud Run Functions第二世代(以下、GCF)はリクエストして使えるのか?の実験をして、動かせることを確認。前回は単純にGCFに対してGASからリクエストしてるだけでした。

今回、こちらも第一世代の時に実践したPuppeteerでスクレイピングを第二世代でもやってみようとチャレンジした所、結構嵌りどころがあったので、ここでまとめています。

今回利用するサービス

環境構築のレベルで嵌りどころがあり、調査した結果、第一世代と第二世代では土台で動かしてるコンテナに大きな違いがあり、そのままでは以前のやり方は通用しません。

また今回の手法を使うことでブラウザ上のソースからのインライン編集が出来なくなりますので注意が必要です。IAMによる実行制限も掛けています。

Google Apps ScriptでCloud Run Functionsを操作する【GAS】

Google Apps Scriptでスクレイピングを極める【GAS】

Google Apps ScriptでCloud Functionsに実行制限付でアクセス 2021年版【GAS】

事前準備

サービスアカウントの作成

Cloud Run Functionsに標準でDefaultのサービスアカウントもあるのですが、今回はゼロから用意しておきます。

作成上の注意点

2024年より、新規のGoogle Cloudテナント作成時におけるデフォルトの組織ポリシー変更が発生しており、検証環境を作ってもらったはいいけれど、いざサービスアカウントを作成しようとすると作成権限が無いとして作れないといったケースが発生しています。

対象になるポリシーは「Disable service account key creation」であり、デフォルトで有効化されてしまっています。テナント作成担当者に伝えて、このポリシーをfalseにしてもらう必要があります。

他にも別のドメインのユーザをテナントのIAMに追加するものもできなくなっているので、他のドメインユーザをプロジェクトに参画させる場合には解除が必要です。

作成手順

以下の手順でGCP上でサービスアカウントを作成します。

  1. GCP画面の左サイドバーより、IAMと管理⇒サービスアカウントを開く

  2. 上部にあるサービスアカウントの作成をクリックする

  3. 適当なサービスアカウント名、説明文を入れて作成して続行をクリックする

  4. このサービス アカウントにプロジェクトへのアクセスを許可するでは、ロールはとりあえず「編集者をつける

  5. ほかは省略するので、完了をクリックして終わらせる

  6. 一覧に作ったサービスアカウントが出てくるので、アカウントのメアドを控えておく。

この時作成したサービスアカウントを後のGCF環境準備で選択します。

図:サービスアカウントを作成中

IAMのロール付与について

これまでCloud Runのコンテナに関する読み書きのロールは暗黙的にアクセス権限が付与されていました。しかし、2024/11/25のGoogle Cloudからのメールに「2025/1/25以降は、対象のユーザにロールとしてroles/artifactregistryが最低でも必要」といったアナウンスがありました。詳細についてはこちらのブログが参考になります。

対象ユーザのロールがIAM上でオーナーや編集者という基本ロールの場合影響を受けないのですが、同一テナント内でアクセス許可した他のユーザの場合、これら基本ロールがついていないと影響を受けることになります。

影響を受けると、Cloud Runコンテナに対する読み取りや作成・デプロイなどが出来なくなるので、他のユーザでこれらの作業を行っていた場合、突然コンテナが動かなくなったであったり、変更できなくなったと焦ることになります。今のうちに明示的に権限を付与しておくと良いでしょう。

対象はIAMにてCloud Run デベロッパー等の権限がついてるアカウントで、

  1. IAMを開いて対象ユーザアカウントの編集を開く(Cloud Run デベロッパーを今回あらかじめ付けてあります)
  2. プリンシパルを編集を開く
  3. 権限編集画面になるので、別のロールを追加をクリックする
  4. Cloud Runコンテナに対する編集権限であれば、ロールを選択のフィルタに「artifactregistry.write」を入れる(artifactregistryだけだと出てこないので注意)
  5. Artifact Registry書き込みという権限が出てくるので選択する
  6. 保存をクリックする

これで、対処は完了になります。実際にGCFでデプロイ後に必要な権限は、Cloud Run 起動元 (roles/run.invoker)がアレば十分なのですが、このあたりは開発体制に応じてつけると良いでしょう。

図:権限を明示的に追加する必要がある

環境の準備

Cloud Runと統合されたことで多少手順がこれまでと異なっています。また、Puppeteerを動かす場合はこの手順で作っても、そのままでは動かすことが出来ません。これは第二世代は軽量なコンテナで最小限の構成となっていて、Puppeteerがインストール出来ず、結果エラーとなってしまいます。よって、次項のデプロイ手順が別途必要となります。

  1. 右上のハンバーガーメニュー(≡)をクリックし、サーバーレス項目にあるCloud Runをクリック
  2. 上部にある「関数を作成」をクリックする
  3. インライン エディタで関数を作成するの選択状態のままにする
  4. サービス名には適当な名前を入れます(今回はpuppeteer-coassignとして入力しました)
  5. リージョンは今回はus-central1を選択します。
  6. エンドポイントURLが出ているのでコピーする(例:https://puppetman-xxxxx.us-central1.run.app)。GAS側で利用します。
  7. ランタイムはNode.js22をとりあえず今回は選んでいます。
  8. 認証では今回、「認証が必要」を選択します。これでIAMで許可した人だけが実行出来ます。
  9. 課金は「リクエストベース」を選択します。
  10. 今回はテストなのでコンテナ・ボリューム・ネットワーキング・セキュリティの設定に於いて、リソースでは、メモリ2GB CPUは1で設定します。
  11. 続けて実行環境に於いては第2世代を選択します。
  12. セキュリティタブでは作成しておいたサービスアカウントを選択します。
  13. 作成をクリックする
  14. Cloud Build APIを有効にしろと出てくるので、有効にするをクリックする

これで、とりあえずの環境だけは用意できました。

図:第二世代でコンテナを作成する

ローカル開発環境の準備

gcloudコマンドをインストール

Node.js環境で動くツールであるのでNode.jsでプロジェクトは作成するのですが、後述の「デプロイ」にて、ローカルのindex.jsなどのファイルをコンテナに対して送り込む必要があります。そこでここではgcloud CLI をインストールして使えるようにしておきます。macOSを自分は使ってるので、以下はmacOSの手順になります。

  1. こちらのページにアクセスしてmacOS版をダウンロードする(M1はARM64, Apple M1 siliconを選択)

  2. ダブルクリックして解凍する

  3. 解凍して出来たgoogle-cloud-sdkというフォルダは自分の書類フォルダにでも移動する

  4. ターミナルを起動して、3.の解凍先のフォルダまで移動しておく

    cd Desktop/google-cloud-sdk
  5. 中に入ってるinstall.shを実行する
    ./install.sh

    Do you want to help improve the Google Cloud CLIと問われるますが、「改善のために匿名の使用統計情報を送信するかどうか」なので、nでも問題ないです。

  6. Do you want to continue?と問われるので、Yと入力してEnterを実行

  7. Enter a path to an rc file to update, or leave blank to useと出ますが、blankのままEnterで問題ないです。

  8. Download and run Python 3.11 installer?と出るので、Yと入力してEnterを実行し、Pythonをインスコします。

  9. macOSのユーザパスワードを入力して続行します。

  10. 完了したら、一旦ターミナルは終了させておき、再度ターミナルを起動します(これをしないとgcloudコマンドが使えない)

これでインストールが完了します。もし、解凍先フォルダを他に移動してしまうとパスが通っていない状態なので、以下の作業をしなおしてパスを通し直す必要があります。

  1. 対象のフォルダをクリックした状態で、option + command + cでフルパスを取得できる

  2. ターミナルを起動する

  3. open ~/.zshrcを実行して設定ファイルを開く

  4. ファイルが開かれるので、中に記述されてるGoogle Cloud SDKやshell command completion for gcloudに記述されてるパスを書き直す

  5. 上書き保存する

  6. source ~/.zshrcで反映する

これで、gcloudコマンドが使えるようになります。

図:gcloud cliのインストールの様子

図:パスを通し直す

初期化しておく

続けて、ターミナル上でGoogle Cloudに接続し最初の認証作業を済ませておきます。

  1. ターミナルを起動する

  2. 以下のコマンドを実行する

    gcloud init
  3. You must log in to continue. Would you like to log inと聞かれるのでYを入力してEnterを実行する

  4. Chromeが起動するので自身のGoogleアカウントにログインして認証を許可する

  5. This account has a lot of projects! Listing them all can take a whileと出るので、Enter a project IDを選択するので、1を入力する

  6. 続けて、Google CloudのCloud Run Functionを使ってるプロジェクト番号を入力してEnterを実行する

これで初期化が完了しました。「gcloud auth application-default login」でも後から認証作業が可能です。

図:プロジェクト番号を入れて接続する

図:ログイン認証の様子

GAS側の事前準備

Google Cloud側プロジェクトと連結

Google Apps ScriptのプロジェクトをGCP側と連結する手順は以下の通り。これは、ScriptApp.getIdentityToken()でGoogle Cloud側アクセス用のトークンを取得する為に必要です。

  1. Google Cloud Consoleを開く
  2. 左上にある▼をクリックする
  3. ダイアログが出てくるので、新規プロジェクトを作るか?既存のプロジェクトを選択する。この時、Google Workspaceであれば選択元は「自分のドメイン」を選択する必要があります。
  4. プロジェクト情報パネルから「プロジェクト番号」をコピーする
  5. 同じくプロジェクトIDもコピーしておく(こちらはコード内で利用します)
  6. 対象のGoogle Apps Scriptのスクリプトエディタを開く
  7. サイドバーからプロジェクト設定を開く
  8. プロジェクトを変更ボタンをクリック
  9. GCPのプロジェクト番号に、4.の番号を入れてプロジェクトを設定をクリック

図:プロジェクト番号をコピーしておきます

図:プロジェクト変更画面

Google Cloud Consoleを弄ってみる

appsscript.jsonを書き換える

スクリプトエディタの左サイドバーから「プロジェクト設定」を開き、「appsscript.json」マニフェスト ファイルをエディタで表示するにチェックを入れて、appsscript.jsonを表示する。

その後そのファイルを開き、以下のように記述を行います。必須の作業です。これをしてしまうと、今後メソッドを追加したときに追加の認証は手動で、Scopeを入れてあげないと認証されないので要注意。

"oauthScopes": [
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/drive"
]

これを入れてあげないと401などのエラーになってしまうので要注意。前述の作業の結果必要となっているものなので、必ず記述を変更しましょう。追加のスコープが必要な場合には、ここに記述の追記が必要となるので要注意。GASでメソッドを追加時に動かない場合は、スコープが足りない結果です。

ソースコード

package.json

今回はPuppeteer-coreを追加する必要があるので、既存のpackage.jsonに対してそれだけを追加しています。2025/7/17時点では、22.0.0が最新安定バージョンです。

{
  "name": "puppeteer-on-run",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "functions-framework --target=getScraping"
  },
  "dependencies": {
    "@google-cloud/functions-framework": "^3.0.0",
    "puppeteer-core": "^22.0.0"
  }
}

index.js

今回はテストなのでPuppeteer-coreを起動して、成功したら200を返し失敗したら500を返すだけの単純なプログラムです。後述のコンテナデプロイ作業で用意されるコンテナ内にはChromeが存在しているため、Puppeteerを使うとエラーとなってしまったので、Puppeteer-Coreを使っています。

ヘッドレスで動かし、尚且つそのブラウザのフルパスをexectablePathで指定します。

const puppeteer = require('puppeteer-core');
const functions = require('@google-cloud/functions-framework');

functions.http('getScraping', async (req, res) => {
    const url = "https://www.google.com";
    let browser;
    try {
        browser = await puppeteer.launch({
            executablePath: '/usr/bin/google-chrome',
            headless: true,
            args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu' ]
        });

        const page = await browser.newPage();
        await page.goto(url);

        console.log("Successfully accessed the page.");
        res.status(200).send("ページに正常にアクセスしました。");
    } catch (error) {
        console.error("スクレイピング中にエラーが発生しました:", error);
        res.status(500).send("スクレイピング中にエラーが発生しました。");
    } finally {
        if (browser) {
            await browser.close();
        }
    }
});

Dockerfile

第一世代の時には無かったファイルです。このファイル一体何をしているのかというと、

  • 最小限の既存コンテナに対して、puppeteerを動かすのに必要な周辺のモジュールを追加インストールする為の設計書
  • FROMで指定してるのがPuppeteer専用のイメージで、これをダウンロードしてデフォルトのイメージと置き換えています。
  • npm installでpackage.jsonの中身を追加インストールしています
  • 但しインストール作業は管理者権限でないと出来ないため、一時的にrootにしています。
  • インストール後安全な一般ユーザーに戻しています

これが嵌りどころで、今回デフォルトイメージのままデプロイして、エラーに沢山遭遇して頭を悩ませていた部分です。このDockerfileを持って、イメージを置き換えてしまいます。尚、ファイルに拡張子は無いので作成時は注意が必要です。

FROM ghcr.io/puppeteer/puppeteer:22.0.0

# 作業ディレクトリを設定
WORKDIR /usr/src/app

# npm install とファイルのコピーは root 権限で実行
USER root
COPY package*.json ./
RUN npm install
COPY . .

# アプリケーションを実行する前に、安全な一般ユーザーに戻す
USER pptruser

CMD ["npm", "start"]

デプロイと実行

デプロイ手順

さて、デプロイする為のgcloudコマンドとファイル群が用意できました。作成したコンテナに対してデプロイを実行して、ファイルの内容を押し込む必要があります。尚、ウェブ上のUIのソースには初期のコードが表示されてますがこの画面からはもう編集出来ませんし、誤ってデプロイすると古いコンテナに置き換わってしまうので使わないようにしましょう。

  1. ターミナルを起動する
  2. index.jsやpackage.json, Dockerfileが入ってるフォルダへ移動する(今回はデスクトップ上のpuppetmanというフォルダに格納しました)
    cd Desktop/puppetman
  3. Cloud Run Functionを動かしてるプロジェクトの名前を確認しておきます(画面最上部左側のプルダウンに表示されてる)
  4. まずは、ファイル類をpushしてイメージを置き換える作業をします
    gcloud builds submit --tag gcr.io/GCFを動かしてるプロジェクトの名前/GCFのプロジェクト名

    GCFを動かしてるプロジェクト名がそれに該当し、GCFのプロジェクト名は関数作成時付けた名前がソレに該当します(今回ならばpuppeteer-coassign)

  5. 次に置き換えたコンテナでアプリとしてデプロイを実行します
    gcloud run deploy puppeteer-coassign --image gcr.io/GCFを動かしてるプロジェクトの名前/GCFのプロジェクト名 --region=us-central1

    リージョンがus-central1なので最後に、リージョン指定を加えています。

  6. 無事に完了すると、エンドポイントURLと同じURLが表示されて完了するハズです。

GASから実行する

GAS側はデプロイされたURLに対してリクエストを送って返り値を受け取ってメッセージを表示するだけ。run_gcffunction関数を実行してみると初回認証が走り、認証が完了するとエンドポイントURLを叩く。

実行結果を受け取って実行ログに表示されるという仕組みです。

図:無事にリクエストが通りました

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)